@rimori/client 1.3.1 → 1.4.3

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.
Files changed (148) hide show
  1. package/.prettierignore +35 -0
  2. package/README.md +77 -71
  3. package/dist/cli/scripts/init/dev-registration.d.ts +1 -1
  4. package/dist/cli/scripts/init/dev-registration.js +4 -4
  5. package/dist/cli/scripts/init/main.js +1 -1
  6. package/dist/cli/scripts/init/package-setup.d.ts +1 -1
  7. package/dist/cli/scripts/init/package-setup.js +3 -3
  8. package/dist/cli/scripts/init/router-transformer.js +19 -12
  9. package/dist/cli/scripts/init/vite-config.d.ts +2 -2
  10. package/dist/cli/scripts/init/vite-config.js +2 -2
  11. package/dist/cli/scripts/release/release-config-upload.js +9 -9
  12. package/dist/cli/scripts/release/release-db-update.d.ts +1 -1
  13. package/dist/cli/scripts/release/release-db-update.js +9 -9
  14. package/dist/cli/scripts/release/release-file-upload.js +2 -2
  15. package/dist/cli/scripts/release/release.js +2 -2
  16. package/dist/cli/types/DatabaseTypes.d.ts +2 -2
  17. package/dist/components/CRUDModal.d.ts +1 -1
  18. package/dist/components/CRUDModal.js +3 -3
  19. package/dist/components/MarkdownEditor.js +16 -16
  20. package/dist/components/Spinner.js +2 -2
  21. package/dist/components/ai/Assistant.js +7 -8
  22. package/dist/components/ai/Avatar.d.ts +2 -2
  23. package/dist/components/ai/Avatar.js +14 -7
  24. package/dist/components/ai/EmbeddedAssistent/AudioInputField.js +5 -6
  25. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.d.ts +1 -1
  26. package/dist/components/ai/EmbeddedAssistent/CircleAudioAvatar.js +1 -2
  27. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.d.ts +1 -2
  28. package/dist/components/ai/EmbeddedAssistent/TTS/MessageSender.js +4 -2
  29. package/dist/components/ai/EmbeddedAssistent/TTS/Player.js +1 -1
  30. package/dist/components/ai/EmbeddedAssistent/VoiceRecoder.js +2 -3
  31. package/dist/components/audio/Playbutton.js +10 -7
  32. package/dist/components/components/ContextMenu.d.ts +1 -1
  33. package/dist/components/components/ContextMenu.js +19 -16
  34. package/dist/components.d.ts +10 -10
  35. package/dist/components.js +10 -10
  36. package/dist/core/controller/AIController.d.ts +2 -2
  37. package/dist/core/controller/AIController.js +20 -18
  38. package/dist/core/controller/ExerciseController.d.ts +52 -0
  39. package/dist/core/controller/ExerciseController.js +73 -0
  40. package/dist/core/controller/ObjectController.js +5 -5
  41. package/dist/core/controller/SettingsController.d.ts +22 -7
  42. package/dist/core/controller/SettingsController.js +73 -8
  43. package/dist/core/controller/SharedContentController.d.ts +3 -3
  44. package/dist/core/controller/SharedContentController.js +38 -20
  45. package/dist/core/controller/VoiceController.js +6 -4
  46. package/dist/core/core.d.ts +15 -14
  47. package/dist/core/core.js +7 -7
  48. package/dist/fromRimori/EventBus.js +23 -23
  49. package/dist/fromRimori/PluginTypes.d.ts +4 -4
  50. package/dist/hooks/UseChatHook.d.ts +3 -3
  51. package/dist/hooks/UseChatHook.js +9 -3
  52. package/dist/index.d.ts +10 -10
  53. package/dist/index.js +9 -9
  54. package/dist/plugin/AccomplishmentHandler.d.ts +5 -5
  55. package/dist/plugin/AccomplishmentHandler.js +31 -27
  56. package/dist/plugin/AudioController.d.ts +1 -1
  57. package/dist/plugin/AudioController.js +6 -6
  58. package/dist/plugin/Logger.d.ts +5 -0
  59. package/dist/plugin/Logger.js +65 -13
  60. package/dist/plugin/PluginController.d.ts +7 -1
  61. package/dist/plugin/PluginController.js +32 -27
  62. package/dist/plugin/RimoriClient.d.ts +39 -14
  63. package/dist/plugin/RimoriClient.js +60 -31
  64. package/dist/plugin/StandaloneClient.d.ts +1 -1
  65. package/dist/plugin/StandaloneClient.js +35 -16
  66. package/dist/plugin/ThemeSetter.js +4 -4
  67. package/dist/providers/PluginProvider.js +44 -14
  68. package/dist/utils/Language.js +57 -57
  69. package/dist/utils/PluginUtils.js +3 -3
  70. package/dist/utils/difficultyConverter.d.ts +1 -1
  71. package/dist/utils/difficultyConverter.js +1 -1
  72. package/dist/utils/endpoint.js +2 -2
  73. package/dist/worker/WorkerSetup.d.ts +1 -1
  74. package/dist/worker/WorkerSetup.js +6 -6
  75. package/eslint.config.js +53 -0
  76. package/example/docs/devdocs.md +50 -40
  77. package/example/docs/overview.md +1 -1
  78. package/example/docs/userdocs.md +4 -1
  79. package/example/rimori.config.ts +51 -49
  80. package/example/worker/vite.config.ts +3 -3
  81. package/example/worker/worker.ts +2 -2
  82. package/package.json +17 -4
  83. package/prettier.config.js +8 -0
  84. package/src/cli/scripts/init/dev-registration.ts +5 -8
  85. package/src/cli/scripts/init/env-setup.ts +1 -1
  86. package/src/cli/scripts/init/file-operations.ts +1 -1
  87. package/src/cli/scripts/init/html-cleaner.ts +2 -5
  88. package/src/cli/scripts/init/main.ts +16 -13
  89. package/src/cli/scripts/init/package-setup.ts +11 -15
  90. package/src/cli/scripts/init/router-transformer.ts +40 -37
  91. package/src/cli/scripts/init/tailwind-config.ts +17 -26
  92. package/src/cli/scripts/init/vite-config.ts +3 -3
  93. package/src/cli/scripts/release/release-config-upload.ts +11 -11
  94. package/src/cli/scripts/release/release-db-update.ts +12 -12
  95. package/src/cli/scripts/release/release-file-upload.ts +3 -3
  96. package/src/cli/scripts/release/release.ts +4 -4
  97. package/src/cli/types/DatabaseTypes.ts +7 -8
  98. package/src/components/CRUDModal.tsx +64 -48
  99. package/src/components/MarkdownEditor.tsx +58 -27
  100. package/src/components/Spinner.tsx +24 -17
  101. package/src/components/ai/Assistant.tsx +70 -70
  102. package/src/components/ai/Avatar.tsx +20 -16
  103. package/src/components/ai/EmbeddedAssistent/AudioInputField.tsx +63 -54
  104. package/src/components/ai/EmbeddedAssistent/CircleAudioAvatar.tsx +14 -5
  105. package/src/components/ai/EmbeddedAssistent/TTS/MessageSender.ts +75 -74
  106. package/src/components/ai/EmbeddedAssistent/TTS/Player.ts +177 -178
  107. package/src/components/ai/EmbeddedAssistent/VoiceRecoder.tsx +109 -94
  108. package/src/components/ai/utils.ts +4 -4
  109. package/src/components/audio/Playbutton.tsx +101 -93
  110. package/src/components/components/ContextMenu.tsx +47 -35
  111. package/src/components.ts +10 -10
  112. package/src/core/controller/AIController.ts +62 -50
  113. package/src/core/controller/ExerciseController.ts +98 -0
  114. package/src/core/controller/ObjectController.ts +15 -10
  115. package/src/core/controller/SettingsController.ts +89 -16
  116. package/src/core/controller/SharedContentController.ts +80 -44
  117. package/src/core/controller/VoiceController.ts +10 -8
  118. package/src/core/core.ts +15 -15
  119. package/src/fromRimori/EventBus.ts +76 -47
  120. package/src/fromRimori/PluginTypes.ts +26 -17
  121. package/src/fromRimori/readme.md +2 -2
  122. package/src/hooks/UseChatHook.ts +25 -15
  123. package/src/index.ts +10 -10
  124. package/src/plugin/AccomplishmentHandler.ts +53 -35
  125. package/src/plugin/AudioController.ts +18 -12
  126. package/src/plugin/Logger.ts +77 -19
  127. package/src/plugin/PluginController.ts +60 -44
  128. package/src/plugin/RimoriClient.ts +133 -69
  129. package/src/plugin/StandaloneClient.ts +51 -24
  130. package/src/plugin/ThemeSetter.ts +5 -5
  131. package/src/providers/PluginProvider.tsx +90 -36
  132. package/src/style.scss +3 -3
  133. package/src/utils/Language.ts +58 -58
  134. package/src/utils/PluginUtils.ts +16 -20
  135. package/src/utils/difficultyConverter.ts +2 -2
  136. package/src/utils/endpoint.ts +3 -2
  137. package/src/worker/WorkerSetup.ts +8 -9
  138. package/tsconfig.json +2 -4
  139. package/dist/components/LoggerExample.d.ts +0 -6
  140. package/dist/components/LoggerExample.js +0 -79
  141. package/dist/core/controller/AudioController.d.ts +0 -0
  142. package/dist/core/controller/AudioController.js +0 -1
  143. package/dist/hooks/UseLogger.d.ts +0 -30
  144. package/dist/hooks/UseLogger.js +0 -122
  145. package/dist/plugin/LoggerExample.d.ts +0 -16
  146. package/dist/plugin/LoggerExample.js +0 -140
  147. package/dist/utils/audioFormats.d.ts +0 -26
  148. package/dist/utils/audioFormats.js +0 -67
@@ -3,93 +3,94 @@ import { ChunkedAudioPlayer } from './Player';
3
3
  type VoiceBackend = (text: string, voice?: string, speed?: number) => Promise<Blob>;
4
4
 
5
5
  export class MessageSender {
6
- private player = new ChunkedAudioPlayer();
7
- private fetchedSentences = new Set<string>();
8
- private lastLoading = false;
9
- private voice: string;
10
- private model: string;
11
- private voiceBackend: VoiceBackend;
6
+ private player = new ChunkedAudioPlayer();
7
+ private fetchedSentences = new Set<string>();
8
+ private lastLoading = false;
9
+ private voice: string;
10
+ private voiceBackend: VoiceBackend;
12
11
 
13
- constructor(voiceBackend: VoiceBackend, voice: string = 'alloy', model = 'openai') {
14
- this.voiceBackend = voiceBackend;
15
- this.voice = voice;
16
- this.model = model;
12
+ constructor(voiceBackend: VoiceBackend, voice: string) {
13
+ if (voice?.split('_').length !== 2) {
14
+ throw new Error("Invalid voice id format '" + voice + "'. Voice id needs to look like <provider>_<voice_id>");
17
15
  }
16
+ this.voiceBackend = voiceBackend;
17
+ this.voice = voice;
18
+ }
18
19
 
19
- private getCompletedSentences(currentText: string, isLoading: boolean): string[] {
20
- // Split the text based on the following characters: .,?!
21
- // Only split on : when followed by a space
22
- const pattern = /(.+?[,.?!]|.+?:\s+|.+?\n+)/g;
23
- const result: string[] = [];
24
- let match;
25
- while ((match = pattern.exec(currentText)) !== null) {
26
- const sentence = match[0].trim();
27
- if (sentence.length > 0) {
28
- result.push(sentence);
29
- }
30
- }
31
- if (!isLoading) {
32
- const lastFullSentence = result[result.length - 1];
33
- const leftoverIndex = currentText.lastIndexOf(lastFullSentence) + lastFullSentence.length;
34
- if (leftoverIndex < currentText.length) {
35
- result.push(currentText.slice(leftoverIndex).trim());
36
- }
37
- }
38
- return result;
20
+ private getCompletedSentences(currentText: string, isLoading: boolean): string[] {
21
+ // Split the text based on the following characters: .,?!
22
+ // Only split on : when followed by a space
23
+ const pattern = /(.+?[,.?!]|.+?:\s+|.+?\n+)/g;
24
+ const result: string[] = [];
25
+ let match;
26
+ while ((match = pattern.exec(currentText)) !== null) {
27
+ const sentence = match[0].trim();
28
+ if (sentence.length > 0) {
29
+ result.push(sentence);
30
+ }
39
31
  }
32
+ if (!isLoading) {
33
+ const lastFullSentence = result[result.length - 1];
34
+ const leftoverIndex = currentText.lastIndexOf(lastFullSentence) + lastFullSentence.length;
35
+ if (leftoverIndex < currentText.length) {
36
+ result.push(currentText.slice(leftoverIndex).trim());
37
+ }
38
+ }
39
+ return result;
40
+ }
40
41
 
41
- public async handleNewText(currentText: string | undefined, isLoading: boolean) {
42
- if (!this.lastLoading && isLoading) {
43
- this.reset();
44
- }
45
- this.lastLoading = isLoading;
42
+ public async handleNewText(currentText: string | undefined, isLoading: boolean) {
43
+ if (!this.lastLoading && isLoading) {
44
+ this.reset();
45
+ }
46
+ this.lastLoading = isLoading;
46
47
 
47
- if (!currentText) {
48
- return;
49
- }
48
+ if (!currentText) {
49
+ return;
50
+ }
50
51
 
51
- const sentences = this.getCompletedSentences(currentText, isLoading);
52
+ const sentences = this.getCompletedSentences(currentText, isLoading);
52
53
 
53
- for (let i = 0; i < sentences.length; i++) {
54
- const sentence = sentences[i];
55
- if (!this.fetchedSentences.has(sentence)) {
56
- this.fetchedSentences.add(sentence);
57
- const audioData = await this.generateSpeech(sentence);
58
- await this.player.addChunk(audioData, i);
59
- }
60
- }
54
+ for (let i = 0; i < sentences.length; i++) {
55
+ const sentence = sentences[i];
56
+ if (!this.fetchedSentences.has(sentence)) {
57
+ this.fetchedSentences.add(sentence);
58
+ const audioData = await this.generateSpeech(sentence);
59
+ await this.player.addChunk(audioData, i);
60
+ }
61
61
  }
62
+ }
62
63
 
63
- private async generateSpeech(sentence: string): Promise<ArrayBuffer> {
64
- const blob = await this.voiceBackend(sentence, this.voice, 1.0);
65
- return await blob.arrayBuffer();
66
- }
64
+ private async generateSpeech(sentence: string): Promise<ArrayBuffer> {
65
+ const blob = await this.voiceBackend(sentence, this.voice, 1.0);
66
+ return await blob.arrayBuffer();
67
+ }
67
68
 
68
- public play() {
69
- this.player.playAgain();
70
- }
69
+ public play() {
70
+ this.player.playAgain();
71
+ }
71
72
 
72
- public stop() {
73
- this.player.stopPlayback();
74
- }
73
+ public stop() {
74
+ this.player.stopPlayback();
75
+ }
75
76
 
76
- private reset() {
77
- this.stop();
78
- this.fetchedSentences.clear();
79
- this.player.reset();
80
- }
77
+ private reset() {
78
+ this.stop();
79
+ this.fetchedSentences.clear();
80
+ this.player.reset();
81
+ }
81
82
 
82
- public setVolume(volume: number) {
83
- this.player.setVolume(volume);
84
- }
83
+ public setVolume(volume: number) {
84
+ this.player.setVolume(volume);
85
+ }
85
86
 
86
- public setOnLoudnessChange(callback: (value: number) => void) {
87
- this.player.setOnLoudnessChange((loudness) => {
88
- callback(loudness);
89
- });
90
- }
87
+ public setOnLoudnessChange(callback: (value: number) => void) {
88
+ this.player.setOnLoudnessChange((loudness) => {
89
+ callback(loudness);
90
+ });
91
+ }
91
92
 
92
- public setOnEndOfSpeech(callback: () => void) {
93
- this.player.setOnEndOfSpeech(callback);
94
- }
95
- }
93
+ public setOnEndOfSpeech(callback: () => void) {
94
+ this.player.setOnEndOfSpeech(callback);
95
+ }
96
+ }
@@ -1,198 +1,197 @@
1
1
  export class ChunkedAudioPlayer {
2
-
3
- private audioContext!: AudioContext;
4
- private chunkQueue: ArrayBuffer[] = [];
5
- private isPlaying = false;
6
- private analyser!: AnalyserNode;
7
- private dataArray!: Uint8Array;
8
- private shouldMonitorLoudness = true;
9
- private isMonitoring = false;
10
- private handle = 0;
11
- private volume = 1.0;
12
- private loudnessCallback: (value: number) => void = () => { };
13
- private currentIndex = 0;
14
- private startedPlaying = false;
15
- private onEndOfSpeech: () => void = () => { };
16
-
17
- constructor() {
18
- this.init();
19
- }
20
-
21
- private init(): void {
22
- this.audioContext = new AudioContext();
23
- this.analyser = this.audioContext.createAnalyser();
24
- this.analyser.fftSize = 256; // Set the FFT size (smaller values provide faster updates, larger ones give better resolution)
25
- const bufferLength = this.analyser.frequencyBinCount;
26
- this.dataArray = new Uint8Array(bufferLength); // Array to hold frequency data
2
+ private audioContext!: AudioContext;
3
+ private chunkQueue: ArrayBuffer[] = [];
4
+ private isPlaying = false;
5
+ private analyser!: AnalyserNode;
6
+ private dataArray!: Uint8Array<ArrayBuffer>;
7
+ private shouldMonitorLoudness = true;
8
+ private isMonitoring = false;
9
+ private handle = 0;
10
+ private volume = 1.0;
11
+ private loudnessCallback: (value: number) => void = () => {};
12
+ private currentIndex = 0;
13
+ private startedPlaying = false;
14
+ private onEndOfSpeech: () => void = () => {};
15
+
16
+ constructor() {
17
+ this.init();
18
+ }
19
+
20
+ private init(): void {
21
+ this.audioContext = new AudioContext();
22
+ this.analyser = this.audioContext.createAnalyser();
23
+ this.analyser.fftSize = 256; // Set the FFT size (smaller values provide faster updates, larger ones give better resolution)
24
+ const bufferLength = this.analyser.frequencyBinCount;
25
+ this.dataArray = new Uint8Array(bufferLength); // Array to hold frequency data
26
+ }
27
+
28
+ public setOnLoudnessChange(callback: (value: number) => void) {
29
+ this.loudnessCallback = callback;
30
+ }
31
+
32
+ public setVolume(volume: number) {
33
+ this.volume = volume;
34
+ }
35
+
36
+ public async addChunk(chunk: ArrayBuffer, position: number): Promise<void> {
37
+ console.log('Adding chunk', position, chunk);
38
+ this.chunkQueue[position] = chunk;
39
+ // console.log("received chunk", {
40
+ // chunkQueue: this.chunkQueue.length,
41
+ // isPlaying: this.isPlaying,
42
+ // })
43
+
44
+ if (position === 0 && !this.startedPlaying) {
45
+ this.startedPlaying = true;
46
+ this.playChunks();
27
47
  }
28
-
29
- public setOnLoudnessChange(callback: (value: number) => void) {
30
- this.loudnessCallback = callback;
48
+ }
49
+
50
+ private playChunks(): void {
51
+ // console.log({ isPlaying: this.isPlaying });
52
+ if (this.isPlaying) return;
53
+ if (!this.chunkQueue[this.currentIndex]) {
54
+ // wait until the correct chunk arrives
55
+ setTimeout(() => this.playChunks(), 10);
31
56
  }
57
+ this.isPlaying = true;
32
58
 
33
- public setVolume(volume: number) {
34
- this.volume = volume;
35
- }
36
-
37
- public async addChunk(chunk: ArrayBuffer, position: number): Promise<void> {
38
- console.log('Adding chunk', position, chunk);
39
- this.chunkQueue[position] = chunk;
40
- // console.log("received chunk", {
41
- // chunkQueue: this.chunkQueue.length,
42
- // isPlaying: this.isPlaying,
43
- // })
44
-
45
- if (position === 0 && !this.startedPlaying) {
46
- this.startedPlaying = true;
59
+ this.playChunk(this.chunkQueue[this.currentIndex]).then(() => {
60
+ this.isPlaying = false;
61
+ this.currentIndex++;
62
+ if (this.chunkQueue[this.currentIndex]) {
63
+ this.shouldMonitorLoudness = true;
64
+ this.playChunks();
65
+ } else {
66
+ // console.log('Playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
67
+ setTimeout(() => {
68
+ // console.log('Check again if really playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
69
+ if (this.chunkQueue.length > this.currentIndex) {
47
70
  this.playChunks();
48
- }
71
+ } else {
72
+ this.startedPlaying = false;
73
+ this.shouldMonitorLoudness = false;
74
+ }
75
+ }, 1000);
76
+ }
77
+ });
78
+ }
79
+
80
+ public stopPlayback(): void {
81
+ // console.log('Stopping playback');
82
+ // Implement logic to stop the current playback
83
+ this.isPlaying = false;
84
+ this.chunkQueue = [];
85
+ this.startedPlaying = false;
86
+ this.shouldMonitorLoudness = false;
87
+ cancelAnimationFrame(this.handle);
88
+ }
89
+
90
+ private playChunk(chunk: ArrayBuffer): Promise<void> {
91
+ // console.log({queue: this.chunkQueue})
92
+ if (!chunk) {
93
+ return Promise.resolve();
49
94
  }
50
95
 
51
- private playChunks(): void {
52
- // console.log({ isPlaying: this.isPlaying });
53
- if (this.isPlaying) return;
54
- if (!this.chunkQueue[this.currentIndex]) {
55
- // wait until the correct chunk arrives
56
- setTimeout(() => this.playChunks(), 10);
96
+ // console.log('Playing chunk', chunk);
97
+ return new Promise((resolve) => {
98
+ const source = this.audioContext.createBufferSource();
99
+ this.audioContext.decodeAudioData(chunk.slice(0)).then((audioBuffer) => {
100
+ source.buffer = audioBuffer;
101
+
102
+ // Create a GainNode for volume control
103
+ const gainNode = this.audioContext.createGain();
104
+ gainNode.gain.value = this.volume;
105
+
106
+ // Connect the source to the GainNode, then to the analyser node, then to the destination (speakers)
107
+ source.connect(gainNode);
108
+ gainNode.connect(this.analyser);
109
+ this.analyser.connect(this.audioContext.destination);
110
+
111
+ source.start(0);
112
+ // console.log('Playing chunk', this.currentIndex);
113
+ gainNode.gain.value = this.volume;
114
+ source.onended = () => {
115
+ // console.log('Chunk ended');
116
+ resolve();
117
+ };
118
+
119
+ // Start monitoring loudness only once
120
+ if (!this.isMonitoring) {
121
+ this.isMonitoring = true;
122
+ this.shouldMonitorLoudness = true;
123
+ this.monitorLoudness();
57
124
  }
58
- this.isPlaying = true;
59
-
60
- this.playChunk(this.chunkQueue[this.currentIndex]).then(() => {
61
- this.isPlaying = false;
62
- this.currentIndex++;
63
- if (this.chunkQueue[this.currentIndex]) {
64
- this.shouldMonitorLoudness = true;
65
- this.playChunks();
66
- } else {
67
- // console.log('Playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
68
- setTimeout(() => {
69
- // console.log('Check again if really playback finished', { currentIndex: this.currentIndex, chunkQueue: this.chunkQueue });
70
- if (this.chunkQueue.length > this.currentIndex) {
71
- this.playChunks();
72
- } else {
73
- this.startedPlaying = false;
74
- this.shouldMonitorLoudness = false;
75
- }
76
- }, 1000);
77
- }
78
- });
125
+ });
126
+ });
127
+ }
128
+
129
+ async playAgain(): Promise<void> {
130
+ console.log('Playing again');
131
+ if (this.chunkQueue.length > 0 && !this.isPlaying) {
132
+ this.playChunks();
79
133
  }
80
-
81
- public stopPlayback(): void {
82
- // console.log('Stopping playback');
83
- // Implement logic to stop the current playback
84
- this.isPlaying = false;
85
- this.chunkQueue = [];
86
- this.startedPlaying = false;
87
- this.shouldMonitorLoudness = false;
88
- cancelAnimationFrame(this.handle);
134
+ }
135
+
136
+ private monitorLoudness(): void {
137
+ // Stop monitoring when the flag is false
138
+ if (!this.shouldMonitorLoudness) {
139
+ // console.log('Loudness monitoring stopped.');
140
+ cancelAnimationFrame(this.handle);
141
+ this.loudnessCallback(0);
142
+ this.onEndOfSpeech();
143
+ return;
89
144
  }
90
145
 
91
- private playChunk(chunk: ArrayBuffer): Promise<void> {
92
- console.log({queue: this.chunkQueue})
93
- if (!chunk) {
94
- return Promise.resolve();
95
- }
146
+ // Get the time domain data from the analyser (this is a snapshot of the waveform)
147
+ this.analyser.getByteTimeDomainData(this.dataArray);
96
148
 
97
- // console.log('Playing chunk', chunk);
98
- return new Promise((resolve) => {
99
- const source = this.audioContext.createBufferSource();
100
- this.audioContext.decodeAudioData(chunk.slice(0)).then((audioBuffer) => {
101
- source.buffer = audioBuffer;
102
-
103
- // Create a GainNode for volume control
104
- const gainNode = this.audioContext.createGain();
105
- gainNode.gain.value = this.volume;
106
-
107
- // Connect the source to the GainNode, then to the analyser node, then to the destination (speakers)
108
- source.connect(gainNode);
109
- gainNode.connect(this.analyser);
110
- this.analyser.connect(this.audioContext.destination);
111
-
112
- source.start(0);
113
- // console.log('Playing chunk', this.currentIndex);
114
- gainNode.gain.value = this.volume;
115
- source.onended = () => {
116
- // console.log('Chunk ended');
117
- resolve();
118
- };
119
-
120
- // Start monitoring loudness only once
121
- if (!this.isMonitoring) {
122
- this.isMonitoring = true;
123
- this.shouldMonitorLoudness = true;
124
- this.monitorLoudness();
125
- }
126
- });
127
- });
149
+ // Calculate the RMS (root mean square) of the waveform values to get the perceived loudness
150
+ let sum = 0;
151
+ for (let i = 0; i < this.dataArray.length; i++) {
152
+ const value = this.dataArray[i] / 128.0 - 1.0; // Normalize between -1 and 1
153
+ sum += value * value;
128
154
  }
129
155
 
130
- async playAgain(): Promise<void> {
131
- console.log('Playing again');
132
- if (this.chunkQueue.length > 0 && !this.isPlaying) {
133
- this.playChunks();
134
- }
135
- }
136
-
137
- private monitorLoudness(): void {
138
- // Stop monitoring when the flag is false
139
- if (!this.shouldMonitorLoudness) {
140
- // console.log('Loudness monitoring stopped.');
141
- cancelAnimationFrame(this.handle);
142
- this.loudnessCallback(0);
143
- this.onEndOfSpeech();
144
- return;
145
- }
146
-
147
- // Get the time domain data from the analyser (this is a snapshot of the waveform)
148
- this.analyser.getByteTimeDomainData(this.dataArray);
149
-
150
- // Calculate the RMS (root mean square) of the waveform values to get the perceived loudness
151
- let sum = 0;
152
- for (let i = 0; i < this.dataArray.length; i++) {
153
- const value = this.dataArray[i] / 128.0 - 1.0; // Normalize between -1 and 1
154
- sum += value * value;
155
- }
156
+ const rms = Math.sqrt(sum / this.dataArray.length);
156
157
 
157
- const rms = Math.sqrt(sum / this.dataArray.length);
158
+ // Handle the case where RMS is 0 to avoid log10(0)
159
+ if (rms === 0) {
160
+ // console.log('Current loudness: Silent');
161
+ } else {
162
+ let loudnessInDb = 20 * Math.log10(rms); // Convert to dB
163
+ // console.log('Current loudness:' + loudnessInDb);
164
+ const minDb = -57;
165
+ const maxDb = -15;
158
166
 
159
- // Handle the case where RMS is 0 to avoid log10(0)
160
- if (rms === 0) {
161
- // console.log('Current loudness: Silent');
162
- } else {
163
- let loudnessInDb = 20 * Math.log10(rms); // Convert to dB
164
- // console.log('Current loudness:' + loudnessInDb);
165
- const minDb = -57;
166
- const maxDb = -15;
167
+ if (loudnessInDb < minDb) {
168
+ loudnessInDb = minDb;
169
+ }
170
+ if (loudnessInDb > maxDb) {
171
+ loudnessInDb = maxDb;
172
+ }
167
173
 
168
- if (loudnessInDb < minDb) {
169
- loudnessInDb = minDb;
170
- }
171
- if (loudnessInDb > maxDb) {
172
- loudnessInDb = maxDb;
173
- }
174
+ const loudnessScale = ((loudnessInDb - minDb) / (maxDb - minDb)) * 100;
175
+ // console.log("root:corrent loudness", loudnessScale);
174
176
 
175
- const loudnessScale = ((loudnessInDb - minDb) / (maxDb - minDb)) * 100;
176
- // console.log("root:corrent loudness", loudnessScale);
177
-
178
- this.loudnessCallback(loudnessScale);
179
- }
180
-
181
- // Call this method again at regular intervals if you want continuous loudness monitoring
182
- this.handle = requestAnimationFrame(() => this.monitorLoudness());
183
- }
184
- public reset() {
185
- // console.log('Resetting player');
186
- this.stopPlayback();
187
- this.currentIndex = 0;
188
- this.shouldMonitorLoudness = true;
189
- //reset to the beginning when the class gets initialized
190
- this.isMonitoring = false;
191
- this.isPlaying = false;
192
- this.init();
177
+ this.loudnessCallback(loudnessScale);
193
178
  }
194
179
 
195
- public setOnEndOfSpeech(callback: () => void) {
196
- this.onEndOfSpeech = callback;
197
- }
198
- }
180
+ // Call this method again at regular intervals if you want continuous loudness monitoring
181
+ this.handle = requestAnimationFrame(() => this.monitorLoudness());
182
+ }
183
+ public reset() {
184
+ // console.log('Resetting player');
185
+ this.stopPlayback();
186
+ this.currentIndex = 0;
187
+ this.shouldMonitorLoudness = true;
188
+ //reset to the beginning when the class gets initialized
189
+ this.isMonitoring = false;
190
+ this.isPlaying = false;
191
+ this.init();
192
+ }
193
+
194
+ public setOnEndOfSpeech(callback: () => void) {
195
+ this.onEndOfSpeech = callback;
196
+ }
197
+ }