nixamp 0.27.2 → 0.28.0

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 (145) hide show
  1. package/README.md +39 -16
  2. package/backtoschool/dist/assets/ar-DK3LI2hP.js +1 -0
  3. package/backtoschool/dist/assets/cs-C9cckHFh.js +1 -0
  4. package/backtoschool/dist/assets/da-BpNIZAZo.js +1 -0
  5. package/backtoschool/dist/assets/de-C1DX_kDk.js +1 -0
  6. package/backtoschool/dist/assets/es-bymdOEnB.js +1 -0
  7. package/backtoschool/dist/assets/fi-p1DAJXf3.js +1 -0
  8. package/backtoschool/dist/assets/fr-w1qH9bbD.js +1 -0
  9. package/backtoschool/dist/assets/hi-DnZJakpY.js +1 -0
  10. package/backtoschool/dist/assets/hu-N7uSyLAk.js +1 -0
  11. package/backtoschool/dist/assets/id-CX3MVU3E.js +1 -0
  12. package/backtoschool/dist/assets/index-4LdMSur6.js +93 -0
  13. package/backtoschool/dist/assets/{index-OE2xWZUH.css → index-D06NfY1L.css} +1 -1
  14. package/backtoschool/dist/assets/it-CQ0H28FI.js +1 -0
  15. package/backtoschool/dist/assets/nl-DJqCLHMq.js +1 -0
  16. package/backtoschool/dist/assets/ru-Rk2yagke.js +1 -0
  17. package/backtoschool/dist/assets/sv-1r9eOl4-.js +1 -0
  18. package/backtoschool/dist/assets/uk-CrRsLNHV.js +1 -0
  19. package/backtoschool/dist/assets/vi-B5kxrjP7.js +1 -0
  20. package/backtoschool/dist/assets/zh-BB_S8QpG.js +1 -0
  21. package/backtoschool/dist/index.html +50 -38
  22. package/dist/admin.js +5 -4
  23. package/dist/event-writer.d.ts +39 -0
  24. package/dist/event-writer.js +139 -0
  25. package/dist/i18n.d.ts +26 -0
  26. package/dist/i18n.js +81 -0
  27. package/dist/live-api.d.ts +2 -0
  28. package/dist/live-api.js +37 -2
  29. package/dist/live-interpreter.d.ts +3 -1
  30. package/dist/live-interpreter.js +109 -83
  31. package/dist/live-recovery.d.ts +6 -0
  32. package/dist/live-recovery.js +11 -0
  33. package/dist/locales/ar.d.ts +201 -0
  34. package/dist/locales/ar.js +200 -0
  35. package/dist/locales/cs.d.ts +201 -0
  36. package/dist/locales/cs.js +200 -0
  37. package/dist/locales/da.d.ts +201 -0
  38. package/dist/locales/da.js +200 -0
  39. package/dist/locales/de.d.ts +201 -0
  40. package/dist/locales/de.js +200 -0
  41. package/dist/locales/en.d.ts +201 -0
  42. package/dist/locales/en.js +201 -0
  43. package/dist/locales/es.d.ts +201 -0
  44. package/dist/locales/es.js +200 -0
  45. package/dist/locales/fi.d.ts +201 -0
  46. package/dist/locales/fi.js +200 -0
  47. package/dist/locales/fr.d.ts +201 -0
  48. package/dist/locales/fr.js +200 -0
  49. package/dist/locales/hi.d.ts +201 -0
  50. package/dist/locales/hi.js +200 -0
  51. package/dist/locales/hu.d.ts +201 -0
  52. package/dist/locales/hu.js +200 -0
  53. package/dist/locales/id.d.ts +201 -0
  54. package/dist/locales/id.js +200 -0
  55. package/dist/locales/it.d.ts +201 -0
  56. package/dist/locales/it.js +200 -0
  57. package/dist/locales/nl.d.ts +201 -0
  58. package/dist/locales/nl.js +200 -0
  59. package/dist/locales/ru.d.ts +201 -0
  60. package/dist/locales/ru.js +200 -0
  61. package/dist/locales/sv.d.ts +201 -0
  62. package/dist/locales/sv.js +200 -0
  63. package/dist/locales/uk.d.ts +201 -0
  64. package/dist/locales/uk.js +200 -0
  65. package/dist/locales/vi.d.ts +201 -0
  66. package/dist/locales/vi.js +200 -0
  67. package/dist/locales/zh.d.ts +201 -0
  68. package/dist/locales/zh.js +200 -0
  69. package/dist/mail-sender.d.ts +9 -0
  70. package/dist/mail-sender.js +11 -0
  71. package/dist/main.js +26 -12
  72. package/dist/notify.d.ts +3 -3
  73. package/dist/notify.js +8 -5
  74. package/dist/password-reset.d.ts +2 -3
  75. package/dist/password-reset.js +4 -2
  76. package/dist/server.d.ts +2 -0
  77. package/dist/server.js +13 -1
  78. package/dist/shared-translation.d.ts +1 -0
  79. package/dist/shared-translation.js +32 -23
  80. package/dist/ui-aliases.d.ts +4 -0
  81. package/dist/ui-aliases.js +41 -0
  82. package/dist/ui-languages.d.ts +62 -0
  83. package/dist/ui-languages.js +32 -0
  84. package/package.json +1 -1
  85. package/src/admin.ts +5 -4
  86. package/src/event-writer.ts +126 -0
  87. package/src/i18n.ts +74 -0
  88. package/src/live-api.ts +26 -2
  89. package/src/live-interpreter.ts +87 -68
  90. package/src/live-recovery.ts +10 -0
  91. package/src/locales/ar.ts +201 -0
  92. package/src/locales/cs.ts +201 -0
  93. package/src/locales/da.ts +201 -0
  94. package/src/locales/de.ts +201 -0
  95. package/src/locales/en.ts +201 -0
  96. package/src/locales/es.ts +201 -0
  97. package/src/locales/fi.ts +201 -0
  98. package/src/locales/fr.ts +201 -0
  99. package/src/locales/hi.ts +201 -0
  100. package/src/locales/hu.ts +201 -0
  101. package/src/locales/id.ts +201 -0
  102. package/src/locales/it.ts +201 -0
  103. package/src/locales/nl.ts +201 -0
  104. package/src/locales/ru.ts +201 -0
  105. package/src/locales/sv.ts +201 -0
  106. package/src/locales/uk.ts +201 -0
  107. package/src/locales/vi.ts +201 -0
  108. package/src/locales/zh.ts +201 -0
  109. package/src/mail-sender.ts +12 -0
  110. package/src/main.ts +25 -12
  111. package/src/notify.ts +10 -7
  112. package/src/password-reset.ts +5 -5
  113. package/src/server.ts +14 -1
  114. package/src/shared-translation.ts +27 -19
  115. package/src/ui-aliases.ts +42 -0
  116. package/src/ui-languages.ts +31 -0
  117. package/web/dist/assets/ar-DK3LI2hP.js +1 -0
  118. package/web/dist/assets/background.worker-Hz7oaQ8X.js +547 -0
  119. package/web/dist/assets/cs-C9cckHFh.js +1 -0
  120. package/web/dist/assets/da-BpNIZAZo.js +1 -0
  121. package/web/dist/assets/de-C1DX_kDk.js +1 -0
  122. package/web/dist/assets/es-bymdOEnB.js +1 -0
  123. package/web/dist/assets/fi-p1DAJXf3.js +1 -0
  124. package/web/dist/assets/fr-w1qH9bbD.js +1 -0
  125. package/web/dist/assets/hi-DnZJakpY.js +1 -0
  126. package/web/dist/assets/{hls-3VKVEQE3-CAL4qBEq.js → hls-3VKVEQE3-DURNoQ5W.js} +1 -1
  127. package/web/dist/assets/hu-N7uSyLAk.js +1 -0
  128. package/web/dist/assets/id-CX3MVU3E.js +1 -0
  129. package/web/dist/assets/index-BXJlt2r6.js +3 -0
  130. package/web/dist/assets/index-Brm9OAsr.css +1 -0
  131. package/web/dist/assets/it-CQ0H28FI.js +1 -0
  132. package/web/dist/assets/{mpegts-H9oC2fAK.js → mpegts-BdyqUrVC.js} +1 -1
  133. package/web/dist/assets/{mpegts-LO6RVLD6-Bkd9Sqr6.js → mpegts-LO6RVLD6-yGogIzG9.js} +1 -1
  134. package/web/dist/assets/nl-DJqCLHMq.js +1 -0
  135. package/web/dist/assets/ru-Rk2yagke.js +1 -0
  136. package/web/dist/assets/sv-1r9eOl4-.js +1 -0
  137. package/web/dist/assets/uk-CrRsLNHV.js +1 -0
  138. package/web/dist/assets/vi-B5kxrjP7.js +1 -0
  139. package/web/dist/assets/zh-BB_S8QpG.js +1 -0
  140. package/web/dist/index.html +132 -131
  141. package/web/dist/sw.js +24 -7
  142. package/backtoschool/dist/assets/index-pQ_uH4bZ.js +0 -93
  143. package/web/dist/assets/background.worker-DH8ag5Q4.js +0 -547
  144. package/web/dist/assets/index-CLeEWlms.js +0 -3
  145. package/web/dist/assets/index-D34dTY18.css +0 -1
package/dist/live-api.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { EventWriterError } from "./event-writer.js";
1
2
  import { tokenFrom } from "./accounts.js";
2
3
  import { INVITATION_STATES, LIVE_EVENT_KINDS, LIVE_EVENT_STATUSES, LiveEventError, isTicketed, } from "./live-events.js";
3
4
  import { LAYOUT_SCOPES, LayoutError, PANEL_REGISTRY, layoutNameFor, presetLayout, } from "./layouts.js";
@@ -19,13 +20,13 @@ function json(response, code, body) {
19
20
  });
20
21
  response.end(value);
21
22
  }
22
- async function body(request) {
23
+ async function body(request, limit = 64 * 1024) {
23
24
  const chunks = [];
24
25
  let size = 0;
25
26
  for await (const chunk of request) {
26
27
  const buffer = chunk;
27
28
  size += buffer.length;
28
- if (size > 64 * 1024)
29
+ if (size > limit)
29
30
  throw new LiveEventError("body too large", 413);
30
31
  chunks.push(buffer);
31
32
  }
@@ -187,6 +188,32 @@ export async function handleLiveApi(request, response, url, options) {
187
188
  !path.startsWith("/api/v1/layout-presets"))
188
189
  return false;
189
190
  try {
191
+ if (path === "/api/v1/events/ai-draft") {
192
+ if (request.method !== "POST") {
193
+ json(response, 405, { error: "POST only" });
194
+ return true;
195
+ }
196
+ const account = await requiredAccount(request, response, options);
197
+ if (!account)
198
+ return true;
199
+ if (!options.eventWriter) {
200
+ json(response, 503, { error: "The AI writer is temporarily unavailable. You can keep writing here." });
201
+ return true;
202
+ }
203
+ const input = await body(request, 12 * 1024);
204
+ const controller = new AbortController();
205
+ const closed = () => controller.abort();
206
+ response.once("close", closed);
207
+ try {
208
+ const result = await options.eventWriter.draft(account.id, input, controller.signal);
209
+ if (!response.destroyed)
210
+ json(response, 200, result);
211
+ }
212
+ finally {
213
+ response.off("close", closed);
214
+ }
215
+ return true;
216
+ }
190
217
  if (path === "/api/v1/events") {
191
218
  if (request.method === "GET") {
192
219
  const account = await signedIn(request, options.accounts);
@@ -559,6 +586,14 @@ export async function handleLiveApi(request, response, url, options) {
559
586
  return true;
560
587
  }
561
588
  catch (error) {
589
+ if (error instanceof EventWriterError) {
590
+ if (!response.destroyed) {
591
+ if (error.retryAfter)
592
+ response.setHeader("retry-after", String(error.retryAfter));
593
+ json(response, error.status, { error: error.message });
594
+ }
595
+ return true;
596
+ }
562
597
  if (error instanceof LiveEventError || error instanceof LayoutError || error instanceof RoomError) {
563
598
  json(response, error.status, { error: error.message });
564
599
  return true;
@@ -49,6 +49,8 @@ export declare class Interpreter {
49
49
  private controller;
50
50
  private translating;
51
51
  private translationController;
52
+ private recognitionFailures;
53
+ private translationFailures;
52
54
  private translations;
53
55
  constructor(options: {
54
56
  language: () => string;
@@ -57,7 +59,7 @@ export declare class Interpreter {
57
59
  channel: () => string;
58
60
  lines: (lines: Caption[]) => void;
59
61
  status: (text: string) => void;
60
- failed: () => void;
62
+ failed: (message: string) => void;
61
63
  fetcher?: typeof fetch;
62
64
  });
63
65
  reset(): void;
@@ -1,3 +1,4 @@
1
+ import { audioError, transientAudioError } from "./live-recovery.js";
1
2
  import { encodeWav } from "./pcm-wav.js";
2
3
  /** Match provider labels across overlapping timestamps. A speaker absent from
3
4
  * the rolling context gets a new label; pitch alone never identifies someone. */
@@ -67,6 +68,8 @@ export class Interpreter {
67
68
  controller = null;
68
69
  translating = null;
69
70
  translationController = null;
71
+ recognitionFailures = 0;
72
+ translationFailures = 0;
70
73
  translations = [];
71
74
  constructor(options) {
72
75
  this.options = options;
@@ -78,6 +81,7 @@ export class Interpreter {
78
81
  this.pending = null;
79
82
  this.running = null;
80
83
  this.translating = null;
84
+ this.recognitionFailures = this.translationFailures = 0;
81
85
  this.translations = [];
82
86
  this.committedUntil = null;
83
87
  this.tracker.reset();
@@ -92,7 +96,7 @@ export class Interpreter {
92
96
  const response = await (this.options.fetcher ?? fetch)(path, { ...init, signal });
93
97
  const body = await response.json();
94
98
  if (!response.ok)
95
- throw new Error(body.error || "Audio translation is unavailable.");
99
+ throw audioError(body.error || "Audio translation is unavailable.", response.status);
96
100
  return body;
97
101
  }
98
102
  async run(generation) {
@@ -101,74 +105,85 @@ export class Interpreter {
101
105
  while (this.pending && generation === this.generation) {
102
106
  const window = this.pending;
103
107
  this.pending = null;
104
- if (Date.now() - window.until > 12_000)
105
- continue;
106
- const controller = new AbortController();
107
- this.controller = controller;
108
- const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]);
109
- const speakers = this.options.speakers(), target = this.options.language();
110
- const heard = await this.json(speakers ? "/api/v1/speech/speakers" : "/api/v1/speech/transcribe?live=1", {
111
- method: "POST", headers: { "content-type": "audio/wav" }, body: new Uint8Array(encodeWav(speakers ? window.samples : window.samples.slice(-80_000))),
112
- }, signal);
113
- if (generation !== this.generation)
114
- return;
115
- const language = heard.language;
116
- let lines = [];
117
- if (speakers) {
118
- const links = this.tracker.reconcile(heard.turns ?? [], window.at, this.options.voices());
119
- // The watermark follows emitted words, not the newest capture
120
- // interval: an overlapping window can recover audio that arrived
121
- // while recognition/translation was busy. Keep unfinished phrases
122
- // in that overlap so short capture intervals do not split every
123
- // sentence (especially damaging when German changes word order).
124
- const cutoff = this.committedUntil ?? window.freshAt;
125
- const turns = heard.turns ?? [];
126
- for (const [index, turn] of turns.entries()) {
127
- const speaker = links.get(turn.speaker);
128
- const words = turn.words.filter(word => window.at + (word.start + word.end) * 500 >= cutoff &&
129
- (window.at + word.end * 1000 <= window.until - 100 || /[.!?。!?]$/.test(word.text)));
130
- let phrase = [];
131
- const emit = () => {
132
- if (!phrase.length)
133
- return;
134
- lines.push({ channel: this.options.channel(), at: window.at + phrase[0].start * 1000,
135
- until: window.at + phrase.at(-1).end * 1000, text: phrase.map(word => word.text).join(" ").trim(),
136
- language, speaker: speaker.id, voiceProfile: turn.profile });
137
- phrase = [];
138
- };
139
- for (const word of words) {
140
- if (phrase.length && word.start - phrase.at(-1).end >= 0.45)
141
- emit();
142
- phrase.push(word);
143
- if (/[.!?。!?]$/.test(word.text) || phrase.map(word => word.text).join(" ").length >= 400)
108
+ try {
109
+ if (Date.now() - window.until > 12_000)
110
+ continue;
111
+ const controller = new AbortController();
112
+ this.controller = controller;
113
+ const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]);
114
+ const speakers = this.options.speakers(), target = this.options.language();
115
+ const heard = await this.json(speakers ? "/api/v1/speech/speakers" : "/api/v1/speech/transcribe?live=1", {
116
+ method: "POST", headers: { "content-type": "audio/wav" }, body: new Uint8Array(encodeWav(speakers ? window.samples : window.samples.slice(-80_000))),
117
+ }, signal);
118
+ if (generation !== this.generation)
119
+ return;
120
+ this.recognitionFailures = 0;
121
+ const language = heard.language;
122
+ let lines = [];
123
+ if (speakers) {
124
+ const links = this.tracker.reconcile(heard.turns ?? [], window.at, this.options.voices());
125
+ // The watermark follows emitted words, not the newest capture
126
+ // interval: an overlapping window can recover audio that arrived
127
+ // while recognition/translation was busy. Keep unfinished phrases
128
+ // in that overlap so short capture intervals do not split every
129
+ // sentence (especially damaging when German changes word order).
130
+ const cutoff = this.committedUntil ?? window.freshAt;
131
+ const turns = heard.turns ?? [];
132
+ for (const [index, turn] of turns.entries()) {
133
+ const speaker = links.get(turn.speaker);
134
+ const words = turn.words.filter(word => window.at + (word.start + word.end) * 500 >= cutoff &&
135
+ (window.at + word.end * 1000 <= window.until - 100 || /[.!?。!?]$/.test(word.text)));
136
+ let phrase = [];
137
+ const emit = () => {
138
+ if (!phrase.length)
139
+ return;
140
+ lines.push({ channel: this.options.channel(), at: window.at + phrase[0].start * 1000,
141
+ until: window.at + phrase.at(-1).end * 1000, text: phrase.map(word => word.text).join(" ").trim(),
142
+ language, speaker: speaker.id, voiceProfile: turn.profile });
143
+ phrase = [];
144
+ };
145
+ for (const word of words) {
146
+ if (phrase.length && word.start - phrase.at(-1).end >= 0.45)
147
+ emit();
148
+ phrase.push(word);
149
+ if (/[.!?。!?]$/.test(word.text) || phrase.map(word => word.text).join(" ").length >= 400)
150
+ emit();
151
+ }
152
+ if (phrase.length && (index < turns.length - 1 ||
153
+ window.until - (window.at + phrase.at(-1).end * 1000) >= 350 ||
154
+ window.until - (window.at + phrase[0].start * 1000) >= 3000))
144
155
  emit();
145
156
  }
146
- if (phrase.length && (index < turns.length - 1 ||
147
- window.until - (window.at + phrase.at(-1).end * 1000) >= 350 ||
148
- window.until - (window.at + phrase[0].start * 1000) >= 3000))
149
- emit();
150
157
  }
158
+ else if (heard.text) {
159
+ lines = [{ channel: this.options.channel(), at: window.freshAt, until: window.until, text: heard.text, language }];
160
+ }
161
+ if (!lines.length)
162
+ continue;
163
+ this.committedUntil = Math.max(this.committedUntil ?? window.freshAt, ...lines.map(line => line.until));
164
+ // Drop expired work instead of turning off an otherwise healthy stream.
165
+ this.translations = this.translations.filter(item => Date.now() - item.until <= 12_000).slice(-11);
166
+ this.translations.push({ lines, language, target, until: window.until });
167
+ // Recognition of the next audio window can proceed while the text
168
+ // model translates this phrase. Voice synthesis is a third stage.
169
+ if (this.translating === null)
170
+ void this.translate(this.generation);
151
171
  }
152
- else if (heard.text) {
153
- lines = [{ channel: this.options.channel(), at: window.freshAt, until: window.until, text: heard.text, language }];
172
+ catch (error) {
173
+ if (generation !== this.generation)
174
+ return;
175
+ if (!transientAudioError(error) || ++this.recognitionFailures >= 3)
176
+ throw error;
177
+ this.options.status("Speech recognition interrupted · listening for the next phrase…");
154
178
  }
155
- if (!lines.length)
156
- continue;
157
- this.committedUntil = Math.max(this.committedUntil ?? window.freshAt, ...lines.map(line => line.until));
158
- if (this.translations.length >= 12)
159
- throw new Error("Translation fell behind. Try again after the language model has warmed up.");
160
- this.translations.push({ lines, language, target, until: window.until });
161
- // Recognition of the next audio window can proceed while the text
162
- // model translates this phrase. Voice synthesis is a third stage.
163
- if (this.translating === null)
164
- void this.translate(this.generation);
165
179
  }
166
180
  }
167
181
  catch (error) {
168
182
  if (generation === this.generation) {
183
+ const message = error instanceof Error ? error.message : "Live translation stopped.";
169
184
  this.reset();
170
- this.options.failed();
171
- this.options.status(error instanceof Error ? error.message : "Live translation stopped.");
185
+ this.options.failed(message);
186
+ this.options.status(message);
172
187
  }
173
188
  }
174
189
  finally {
@@ -181,36 +196,47 @@ export class Interpreter {
181
196
  try {
182
197
  while (this.translations.length && generation === this.generation) {
183
198
  const item = this.translations.shift();
184
- let { lines } = item;
185
- const { language, target, until } = item;
186
- if (Date.now() - until > 12_000)
187
- throw new Error("Translation fell behind. Try again after the language model has warmed up.");
188
- const controller = new AbortController();
189
- this.translationController = controller;
190
- if (target && language !== target) {
191
- if (!language)
192
- throw new Error("The audio language could not be detected. Waiting for clearer speech.");
193
- const translated = await this.json("/api/v1/translate?live=1", {
194
- method: "POST", headers: { "content-type": "application/json" },
195
- body: JSON.stringify({ texts: lines.map(line => line.text), from: language, to: target }),
196
- }, AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]));
197
- if (translated.texts.length !== lines.length || translated.texts.some(text => !text.trim()))
198
- throw new Error("Translation omitted a phrase. Enable translated audio again to retry.");
199
- lines = lines.map((line, i) => ({ ...line, original: line.text, sourceLanguage: language, language: target, text: translated.texts[i] }));
199
+ try {
200
+ let { lines } = item;
201
+ const { language, target, until } = item;
202
+ if (Date.now() - until > 12_000)
203
+ continue;
204
+ const controller = new AbortController();
205
+ this.translationController = controller;
206
+ if (target && language !== target) {
207
+ if (!language)
208
+ throw audioError("The audio language could not be detected. Waiting for clearer speech.", 502);
209
+ const translated = await this.json("/api/v1/translate?live=1", {
210
+ method: "POST", headers: { "content-type": "application/json" },
211
+ body: JSON.stringify({ texts: lines.map(line => line.text), from: language, to: target }),
212
+ }, AbortSignal.any([controller.signal, AbortSignal.timeout(12_000)]));
213
+ if (translated.texts.length !== lines.length || translated.texts.some(text => !text.trim()))
214
+ throw audioError("Translation omitted a phrase. Waiting for the next phrase.", 502);
215
+ lines = lines.map((line, i) => ({ ...line, original: line.text, sourceLanguage: language, language: target, text: translated.texts[i] }));
216
+ }
217
+ if (generation !== this.generation)
218
+ return;
219
+ if (Date.now() - until > 12_000)
220
+ continue;
221
+ this.translationFailures = 0;
222
+ this.options.lines(lines.flatMap(line => splitCaption(line)));
223
+ this.options.status(target ? `${language} → ${target} · live translation` : `Original audio language: ${language || "detecting…"}`);
224
+ }
225
+ catch (error) {
226
+ if (generation !== this.generation)
227
+ return;
228
+ if (!transientAudioError(error) || ++this.translationFailures >= 3)
229
+ throw error;
230
+ this.options.status("Translation interrupted · catching up with live speech…");
200
231
  }
201
- if (generation !== this.generation)
202
- return;
203
- if (Date.now() - until > 12_000)
204
- throw new Error("Translation fell behind. Try again after the language model has warmed up.");
205
- this.options.lines(lines.flatMap(line => splitCaption(line)));
206
- this.options.status(target ? `${language} → ${target} · live translation` : `Original audio language: ${language || "detecting…"}`);
207
232
  }
208
233
  }
209
234
  catch (error) {
210
235
  if (generation === this.generation) {
236
+ const message = error instanceof Error ? error.message : "Live translation stopped.";
211
237
  this.reset();
212
- this.options.failed();
213
- this.options.status(error instanceof Error ? error.message : "Live translation stopped.");
238
+ this.options.failed(message);
239
+ this.options.status(message);
214
240
  }
215
241
  }
216
242
  finally {
@@ -0,0 +1,6 @@
1
+ /** Access, billing and throttling failures are terminal. Only transient service
2
+ * and transport failures may recover, with a bounded consecutive failure count. */
3
+ export declare function transientAudioError(error: unknown): boolean;
4
+ export declare function audioError(message: string, status: number): Error & {
5
+ status: number;
6
+ };
@@ -0,0 +1,11 @@
1
+ /** Access, billing and throttling failures are terminal. Only transient service
2
+ * and transport failures may recover, with a bounded consecutive failure count. */
3
+ export function transientAudioError(error) {
4
+ const status = error?.status;
5
+ if (typeof status === "number")
6
+ return [408, 502, 503, 504].includes(status);
7
+ return error instanceof TypeError || (error instanceof Error && ["AbortError", "TimeoutError"].includes(error.name));
8
+ }
9
+ export function audioError(message, status) {
10
+ return Object.assign(new Error(message), { status });
11
+ }
@@ -0,0 +1,201 @@
1
+ declare const _default: {
2
+ "Interface language": string;
3
+ Language: string;
4
+ "Translation language": string;
5
+ "Original (auto-detect)": string;
6
+ Play: string;
7
+ Pause: string;
8
+ Stop: string;
9
+ Next: string;
10
+ Previous: string;
11
+ Select: string;
12
+ "Next/Prev": string;
13
+ Quit: string;
14
+ "Now Playing": string;
15
+ "Spectrum Analyser": string;
16
+ Playlist: string;
17
+ Files: string;
18
+ "Open files": string;
19
+ "Open folder": string;
20
+ "Add to library": string;
21
+ "Replace playlist": string;
22
+ Filter: string;
23
+ Search: string;
24
+ "Nothing loaded.": string;
25
+ "Empty.": string;
26
+ "Loading\u2026": string;
27
+ "Connecting\u2026": string;
28
+ Connected: string;
29
+ "Not connected": string;
30
+ "No source": string;
31
+ Playing: string;
32
+ Stopped: string;
33
+ Volume: string;
34
+ Seek: string;
35
+ "Full screen": string;
36
+ Download: string;
37
+ Share: string;
38
+ Copy: string;
39
+ "Copy link": string;
40
+ Copied: string;
41
+ Save: string;
42
+ Cancel: string;
43
+ Close: string;
44
+ Remove: string;
45
+ Delete: string;
46
+ Edit: string;
47
+ Add: string;
48
+ Send: string;
49
+ Retry: string;
50
+ Back: string;
51
+ "Next page": string;
52
+ "Previous page": string;
53
+ "Show more": string;
54
+ Panels: string;
55
+ "Reset layout": string;
56
+ Show: string;
57
+ Hide: string;
58
+ Collapse: string;
59
+ Expand: string;
60
+ "Move up": string;
61
+ "Move down": string;
62
+ Install: string;
63
+ "TV mode": string;
64
+ On: string;
65
+ Off: string;
66
+ Account: string;
67
+ "Sign in": string;
68
+ "Sign out": string;
69
+ "Create account": string;
70
+ Email: string;
71
+ Password: string;
72
+ "Forgot password?": string;
73
+ "Welcome back": string;
74
+ Profile: string;
75
+ Handle: string;
76
+ "Your servers": string;
77
+ Server: string;
78
+ Connect: string;
79
+ Disconnect: string;
80
+ Directory: string;
81
+ Favourites: string;
82
+ Follow: string;
83
+ Unfollow: string;
84
+ Notifications: string;
85
+ "Phone number": string;
86
+ "Save number": string;
87
+ Transcript: string;
88
+ Captions: string;
89
+ "Translate audio": string;
90
+ "Audio language": string;
91
+ "Audio options": string;
92
+ "Keep background sound": string;
93
+ "Background level": string;
94
+ "Speaker voices": string;
95
+ "Any voice": string;
96
+ "Male voice": string;
97
+ "Female voice": string;
98
+ "Original audio.": string;
99
+ "Buy translated audio": string;
100
+ "Audio credit": string;
101
+ "Buy pass": string;
102
+ Pass: string;
103
+ "Pay with": string;
104
+ "Check payment": string;
105
+ "Open checkout": string;
106
+ "Free sessions": string;
107
+ "Usage rates and expiry": string;
108
+ Catalogs: string;
109
+ "Add catalog": string;
110
+ "Catalog name": string;
111
+ Link: string;
112
+ "Play link": string;
113
+ "Go live": string;
114
+ Publish: string;
115
+ Listen: string;
116
+ "Live now": string;
117
+ Upcoming: string;
118
+ Parties: string;
119
+ "Join party": string;
120
+ "Invite code": string;
121
+ Invite: string;
122
+ Chat: string;
123
+ "Chat message": string;
124
+ People: string;
125
+ Speaker: string;
126
+ Speakers: string;
127
+ Listener: string;
128
+ Moderator: string;
129
+ "Raise hand": string;
130
+ "Hand raises": string;
131
+ Questions: string;
132
+ Resources: string;
133
+ Replay: string;
134
+ Schedule: string;
135
+ Event: string;
136
+ Host: string;
137
+ About: string;
138
+ Stage: string;
139
+ Title: string;
140
+ Description: string;
141
+ Topic: string;
142
+ Public: string;
143
+ "Anyone with the link": string;
144
+ "Invited people": string;
145
+ "Who can join": string;
146
+ "Date and time": string;
147
+ "Expected length": string;
148
+ Repeat: string;
149
+ "Does not repeat": string;
150
+ "Every day": string;
151
+ "Every week": string;
152
+ Homepage: string;
153
+ "Host name": string;
154
+ "Profile photo URL": string;
155
+ "Broadcast link": string;
156
+ "Edit event": string;
157
+ "Save changes": string;
158
+ "Create live": string;
159
+ "Schedule class": string;
160
+ "Start class": string;
161
+ "End event": string;
162
+ "End session": string;
163
+ "Session ended": string;
164
+ "Event ended": string;
165
+ Cancelled: string;
166
+ "Replay available": string;
167
+ Join: string;
168
+ "Create invite": string;
169
+ "Invite as": string;
170
+ "Watch now": string;
171
+ "Start a room": string;
172
+ "Recent replays": string;
173
+ "Your account": string;
174
+ "Write with AI": string;
175
+ "Suggested title": string;
176
+ "Suggested description": string;
177
+ "Suggested topic": string;
178
+ "Use these details": string;
179
+ "Learn something live.": string;
180
+ "Screen share": string;
181
+ "Upload and broadcast": string;
182
+ Help: string;
183
+ "Reload Player": string;
184
+ "Copy Bundled CLI Path": string;
185
+ "Developer tools": string;
186
+ Undo: string;
187
+ Redo: string;
188
+ Cut: string;
189
+ Paste: string;
190
+ "Select all": string;
191
+ Status: string;
192
+ "Activity log": string;
193
+ Admin: string;
194
+ "Playback controls": string;
195
+ "Tracks and folders": string;
196
+ "Original-language captions.": string;
197
+ "Skip to playback controls": string;
198
+ "Skip to playlist": string;
199
+ "Skip to transcript and translated audio": string;
200
+ };
201
+ export default _default;