refactor(audio-pipelines-transcribe): use @proj-airi/audio (#1990)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by-agent: Codex
This commit is contained in:
Neko
2026-06-18 15:10:34 +08:00
committed by GitHub
co-authored by autofix-ci[bot]
parent 70d1c317df
commit 391d21b996
8 changed files with 53 additions and 5 deletions
+2 -1
View File
@@ -34,7 +34,8 @@
"build:packages": "turbo run build -F=\"./packages/*\"",
"build:engines": "turbo run build -F=\"./engines/*\"",
"test": "vitest --coverage",
"test:run": "vitest run && pnpm run test-vishot:run && pnpm run test-ui:run",
"test:run": "vitest run && pnpm run test-audio-pipelines-transcribe:run && pnpm run test-vishot:run && pnpm run test-ui:run",
"test-audio-pipelines-transcribe:run": "vitest run --config packages/audio-pipelines-transcribe/vitest.config.ts",
"test-vishot:run": "vitest run --config packages/vishot-runtime/vitest.config.ts",
"test-ui:run": "vitest run --config packages/stage-ui/vitest.config.ts",
"lint": "moeru-lint .",
@@ -9,6 +9,7 @@
},
"dependencies": {
"@moeru/std": "catalog:",
"@proj-airi/audio": "workspace:^",
"uncrypto": "catalog:"
}
}
@@ -0,0 +1 @@
export * from './utils'
@@ -0,0 +1,31 @@
import { toWav } from '@proj-airi/audio/encoding'
import { describe, expect, it } from 'vitest'
import { mediaStreamFromAudioFile } from '.'
function createSineWaveFile() {
const sampleRate = 44_100
const durationSeconds = 0.05
const sampleCount = Math.floor(sampleRate * durationSeconds)
const samples = new Float32Array(sampleCount)
for (let index = 0; index < sampleCount; index += 1) {
const phase = (index / sampleRate) * 440 * Math.PI * 2
samples[index] = Math.sin(phase)
}
return new File([toWav(samples.buffer, sampleRate)], 'tone.wav', { type: 'audio/wav' })
}
describe('mediaStreamFromAudioFile', () => {
it('decodes a browser audio file into a media stream and releases resources', async () => {
const result = await mediaStreamFromAudioFile(createSineWaveFile())
expect(result.stream).toBeInstanceOf(MediaStream)
expect(result.stream.getAudioTracks()).toHaveLength(1)
await result.cleanup()
expect(result.stream.getAudioTracks()[0]?.readyState).toBe('ended')
})
})
@@ -1,5 +1,12 @@
import { tryCatch } from '@moeru/std'
/**
* Decodes an audio file into a playable {@link MediaStream}.
*
* Use this when browser transcription code needs to feed file-backed audio
* into APIs that consume live media streams. The returned cleanup function
* stops the one-shot buffer source and releases the owned {@link AudioContext}.
*/
export async function mediaStreamFromAudioFile(file: File): Promise<{
cleanup: () => Promise<void>
stream: MediaStream
@@ -26,10 +33,15 @@ export async function mediaStreamFromAudioFile(file: File): Promise<{
try {
source.stop()
}
catch { /* noop */ }
catch {
// `AudioBufferSourceNode.stop()` throws if playback already ended or
// was stopped by the caller; cleanup should remain idempotent.
}
source.disconnect()
destination.disconnect()
for (const track of destination.stream.getTracks()) {
track.stop()
}
await audioContext.close()
},
}
@@ -25,7 +25,7 @@ export default defineConfig(({ mode }) => ({
browser: {
provider: playwright(),
enabled: true,
// at least one instance is required
// Vitest browser mode requires an explicit browser instance list.
instances: [
{ browser: 'chromium' },
],
+3
View File
@@ -3300,6 +3300,9 @@ importers:
'@moeru/std':
specifier: 'catalog:'
version: 0.1.0-beta.17
'@proj-airi/audio':
specifier: workspace:^
version: link:../audio
uncrypto:
specifier: 'catalog:'
version: 0.1.3
-1
View File
@@ -7,7 +7,6 @@ export default defineConfig({
'apps/ui-server-auth',
'apps/ui-admin',
'apps/stage-tamagotchi',
'packages/audio-pipelines-transcribe',
'packages/cap-vite',
'packages/core-agent',
'packages/vishot-runner-browser',