Skip to content

Add Flutter examples for VAD - #3856

Merged
csukuangfj merged 9 commits into
k2-fsa:masterfrom
csukuangfj:vad-flutter
Aug 10, 2026
Merged

csukuangfj merged 9 commits into
k2-fsa:masterfrom
csukuangfj:vad-flutter

Conversation

@csukuangfj

@csukuangfj csukuangfj commented Aug 10, 2026 •

Copy link
Copy Markdown
Collaborator

Please find pre-built Flutter APPs for

Screenshot 2026-08-10 at 20 51 07

Summary by CodeRabbit

  • New Features
    • Added Flutter voice activity detection demos for microphone input and audio files.
    • Supports VAD model selection, configurable thresholds, progress reporting, cancellation, playback, segment detection, and WAV export.
    • Added cross-platform support for Android, iOS, macOS, Linux, Windows, and Web.
    • Added browser-based VAD processing and improved web audio playback and file-saving options.
  • Documentation
    • Added setup, model, platform support, and build instructions for both examples.
  • Chores
    • Automated multi-platform builds, packaging, signing, and release artifact uploads.

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 10, 2026
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds file-based and microphone-based Flutter VAD demos for multiple platforms. It adds native and web processing paths, audio conversion and playback utilities, platform runners, CI packaging, browser save support, and JavaScript-backed web VAD bindings.

Changes

Flutter CI and release packaging

Layer / File(s) Summary
Cross-platform build and release workflows
.github/workflows/test-flutter*.yaml, .gitignore
The workflows build Flutter VAD applications across macOS, Linux, Windows, Android, and Web. They package artifacts, sign macOS apps, use local plugins, and conditionally upload release assets.

File-based VAD demo

Layer / File(s) Summary
Audio, model, and VAD runtime
flutter-examples/vad-from-file/lib/*
The demo adds model preparation, FFmpeg and Web Audio decoding, WAV encoding, resampling, platform-specific playback and saving, VAD workers, and VAD result streams.
File-based VAD interface
flutter-examples/vad-from-file/lib/main.dart, flutter-examples/vad-from-file/lib/vad_screen.dart
The application adds model information, file selection, playback, configurable VAD execution, progress reporting, segment playback, WAV export, reset behavior, and cleanup.
File-based platform projects
flutter-examples/vad-from-file/android/*, flutter-examples/vad-from-file/ios/*, flutter-examples/vad-from-file/linux/*, flutter-examples/vad-from-file/macos/*, flutter-examples/vad-from-file/windows/*, flutter-examples/vad-from-file/web/*
The demo adds Flutter project metadata, native runners, build configurations, manifests, app resources, and web worker assets for supported platforms.

Microphone-based VAD demo

Layer / File(s) Summary
Streaming VAD runtime
flutter-examples/vad-from-microphone/lib/*
The demo adds model selection, microphone streaming, native-isolate and Web Worker processing, speech-state streams, segment handling, WAV encoding, playback, saving, and browser audio helpers.
Microphone VAD interface
flutter-examples/vad-from-microphone/lib/main.dart, flutter-examples/vad-from-microphone/lib/vad_screen.dart
The application adds microphone selection, permission handling, PCM buffering, recording controls, speech indicators, segment playback, and WAV export.
Microphone platform projects
flutter-examples/vad-from-microphone/android/*, flutter-examples/vad-from-microphone/ios/*, flutter-examples/vad-from-microphone/linux/*, flutter-examples/vad-from-microphone/macos/*, flutter-examples/vad-from-microphone/windows/*, flutter-examples/vad-from-microphone/web/*
The demo adds platform build files, native runners, microphone permissions, application resources, and a real-time Web Worker implementation.

Web VAD bindings

Layer / File(s) Summary
JavaScript-backed detector bindings
flutter/sherpa_onnx/lib/src/web/vad.dart
CircularBuffer and VoiceActivityDetector now create and operate JavaScript-backed objects through Dart JS interop. The detector forwards audio and queue operations, converts segments, and performs idempotent cleanup.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant VadScreen
  participant VadManager
  participant VadWorker
  participant VADDetector
  participant AudioOutput

  VadScreen->>VadManager: initialize and submit audio
  VadManager->>VadWorker: forward web VAD request
  VadWorker->>VADDetector: load model and process audio windows
  VADDetector-->>VadWorker: speech segments and progress
  VadWorker-->>VadManager: result callbacks
  VadManager-->>VadScreen: state, progress, and segments
  VadScreen->>AudioOutput: play or save WAV segment
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: adding Flutter examples for voice activity detection.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@csukuangfj
csukuangfj requested a lite review from Copilot August 10, 2026 12:58
@csukuangfj
csukuangfj merged commit 17ab05b into k2-fsa:master Aug 10, 2026
1 check was pending
@csukuangfj
csukuangfj deleted the vad-flutter branch August 10, 2026 13:00

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (21)
flutter-examples/tts/lib/web_audio.dart-37-46 (1)

37-46: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Await the JavaScript promise, and avoid shared globals for the payload.

eval.callAsFunction returns the promise of the async IIFE. The Dart function does not await it. Two consequences follow:

  • The returned Future completes before the file is written. The caller at flutter-examples/vad-from-file/lib/vad_screen.dart:332 awaits this function and can continue before the save finishes.
  • Errors thrown inside the IIFE become an unhandled JS rejection. Dart never observes them.

The payload also travels through the shared globals _sherpaSaveBytes and _sherpaSaveFilename. If the user triggers two saves before the first completes, the second call overwrites both globals, and the first IIFE can write the wrong bytes under the wrong name. Pass the bytes and the name as arguments to the evaluated function instead.

🔧 Proposed fix: pass arguments and await the promise
 Future<void> saveWavBytesWithDialog(Uint8List wavBytes, String filename) async {
-  globalContext['_sherpaSaveBytes'] = wavBytes.toJS;
-  globalContext['_sherpaSaveFilename'] = filename.toJS;
-
   final eval = globalContext.getProperty('eval'.toJS) as JSFunction;
-  eval.callAsFunction(null, '''
-    (async function() {
-      var bytes = window._sherpaSaveBytes;
-      var name = window._sherpaSaveFilename || 'audio.wav';
+  final fn = eval.callAsFunction(null, '''
+    (async function(bytes, name) {
+      name = name || 'audio.wav';
       var blob = new Blob([bytes], {type: 'audio/wav'});

Then drop the window._sherpaSave* resets inside the IIFE, and finish with:

  final promise = (fn as JSFunction)
      .callAsFunction(null, wavBytes.toJS, filename.toJS) as JSPromise;
  await promise.toDart;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/tts/lib/web_audio.dart` around lines 37 - 46, Update
saveWavBytesWithDialog to eliminate the shared global payload variables and
define the evaluated async function to receive the WAV bytes and filename as
arguments. Invoke the resulting JSFunction with wavBytes.toJS and filename.toJS,
cast the returned promise to JSPromise, and await promise.toDart so completion
and errors propagate through the Dart Future.
flutter-examples/vad-from-file/lib/audio_decoder_web.dart-24-76 (1)

24-76: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Close the temporary AudioContext after decoding.

This decoder creates a new Web Audio context for every file and does not call close(). A new context per decode can consume browser audio resources; after decodeAudioData and channel extraction complete, close the temporary context. If a reusable AudioContext is preferred instead, keep it alive for repeated decodes rather than constructing one each time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/audio_decoder_web.dart` around lines 24 -
76, Update decodeAudioBytes to close the temporary AudioContext after decoding
and channel extraction complete, including when later processing fails; invoke
its close() method before returning or from guaranteed cleanup. If replacing
per-call construction with a reusable context, preserve that context across
decodes and do not close it after each file.
flutter/sherpa_onnx/lib/src/web/vad.dart-39-47 (1)

39-47: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Typed-data views lose their offset and length when passed to JavaScript. All three sites call .buffer.toJS and drop offsetInBytes and lengthInBytes. If the Dart list is a view onto a larger backing buffer, the JavaScript side receives the entire buffer instead of the intended range. flutter-examples/vad-from-file/lib/vad_manager.dart line 363 creates window chunks with Float32List.sublistView, so this pattern reaches the same API. Pass the offset and length to the Float32Array constructor, or copy the view first.

  • flutter/sherpa_onnx/lib/src/web/vad.dart#L39-L47: construct the Float32Array with data.buffer.toJS, data.offsetInBytes.toJS, and data.length.toJS in CircularBuffer.push.
  • flutter/sherpa_onnx/lib/src/web/vad.dart#L149-L157: apply the same three-argument construction in VoiceActivityDetector.acceptWaveform.
  • flutter-examples/vad-from-file/lib/web_blob_url.dart#L7-L16: build the blob part from bytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes).toJS instead of the raw buffer.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter/sherpa_onnx/lib/src/web/vad.dart` around lines 39 - 47, The
JavaScript conversions must preserve typed-data view boundaries instead of
passing the entire backing buffer. In flutter/sherpa_onnx/lib/src/web/vad.dart
lines 39-47, update CircularBuffer.push, and lines 149-157, update
VoiceActivityDetector.acceptWaveform, to construct Float32Array with the buffer,
offsetInBytes, and length. In
flutter-examples/vad-from-file/lib/web_blob_url.dart lines 7-16, create the blob
part from a Uint8List limited to bytes.offsetInBytes and bytes.lengthInBytes
before converting it to JavaScript.
flutter-examples/vad-from-file/lib/vad_manager.dart-296-360 (1)

296-360: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Cancel does not work on native.

The isolate handles one message at a time. The loop at lines 359-383 runs to completion inside the _RunVadRequest handler and blocks the isolate event loop. The _CancelRequest message sent by cancel() is only delivered after the run ends, so cancelled stays false for the whole run. The UI resets to the idle state while the isolate keeps processing, then a stale _VadDone arrives.

Use a control channel the isolate can read without its event loop, or split the loop into chunks and yield between chunks so pending messages are processed.

♻️ Chunked-loop sketch
-          for (int i = 0; i < numIter; i++) {
-            if (cancelled) break;
+          // Process in chunks and yield so pending _CancelRequest messages
+          // reach this isolate between chunks.
+          for (int i = 0; i < numIter; i++) {
+            if (cancelled) break;

Move the body into an async function and await Future.delayed(Duration.zero) every N windows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/vad_manager.dart` around lines 296 - 360,
Update the _RunVadRequest processing in receivePort.listen so the VAD loop
yields to the isolate event loop periodically, allowing _CancelRequest messages
to set cancelled during processing. Move the run body into an async helper or
otherwise yield between window chunks, check cancelled before each chunk/window,
and prevent sending a stale _VadDone after cancellation.
flutter-examples/vad-from-file/lib/vad_screen.dart-104-158 (1)

104-158: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset _hasSegmentListener when the player controller is replaced.

_pickFile disposes the old controller and creates a new one, but _hasSegmentListener stays true from the previous file. _playSegment at lines 307-310 then skips addListener(_onPlayerPositionUpdate). After the second file is loaded, segment playback never stops at the segment end and "Play all" no longer advances.

Reset the flag whenever the controller is replaced or cleared.

🐛 Proposed fix
     // Dispose previous player.
     await _playerController?.dispose();
     _playerController = null;
+    _hasSegmentListener = false;
+    _segmentEndMs = 0;

Also set _hasSegmentListener = false; inside _clearAll.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/vad_screen.dart` around lines 104 - 158,
Reset _hasSegmentListener to false whenever _playerController is disposed,
replaced, or cleared. Update _pickFile around the old-controller disposal and
new-controller assignment, and update _clearAll so _playSegment can register
_onPlayerPositionUpdate for each newly loaded player.
flutter-examples/vad-from-file/pubspec.yaml-17-27 (1)

17-27: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Declare audioplayers with a compatible version constraint.

lib/play_bytes.dart and lib/play_bytes_stub.dart import package:audioplayers/audioplayers.dart, but this manifest does not declare it. Add it to the manifest and match the examples elsewhere (^5.0.0) or use an older version compatible with the declared Flutter SDK baseline. Do not rely on an incidental transitive dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/pubspec.yaml` around lines 17 - 27, Declare
the audioplayers dependency in pubspec.yaml with an explicit compatible version
constraint, preferably ^5.0.0 or an older version matching the Flutter SDK
baseline, so the imports in play_bytes.dart and play_bytes_stub.dart are backed
by a direct dependency.
flutter-examples/vad-from-file/lib/wav_encoder.dart-35-47 (1)

35-47: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate RIFF chunk bounds before reading chunk contents.

decodeWav promises to return null for invalid files. A short fmt chunk can make ByteData throw. A data chunk with a declared size beyond the input can make Uint8List.view throw. RIFF also requires one padding byte after an odd-sized chunk.

Validate the complete padded chunk length before reading it. Reject fmt chunks shorter than 16 bytes. Reject zero-channel input.

Proposed fix
   while (offset + 8 <= bytes.length) {
     final chunkId = String.fromCharCodes(bytes.sublist(offset, offset + 4));
     final chunkSize = bd.getUint32(offset + 4, Endian.little);
+    final dataOffset = offset + 8;
+    final paddedChunkSize = chunkSize + (chunkSize & 1);
+    if (paddedChunkSize > bytes.length - dataOffset) return null;
+
     if (chunkId == 'fmt ') {
+      if (chunkSize < 16) return null;
       audioFormat = bd.getUint16(offset + 8, Endian.little);
       numChannels = bd.getUint16(offset + 10, Endian.little);
       sampleRate = bd.getUint32(offset + 12, Endian.little);
       bitsPerSample = bd.getUint16(offset + 22, Endian.little);
       foundFmt = true;
-      offset += 8 + chunkSize;
+      offset = dataOffset + paddedChunkSize;
       break;
     }
-    offset += 8 + chunkSize;
+    offset = dataOffset + paddedChunkSize;
   }
-  if (!foundFmt) return null;
+  if (!foundFmt || numChannels == 0) return null;
 
   // Find "data" chunk.
   offset = 12;
   Uint8List? dataBytes;
   while (offset + 8 <= bytes.length) {
     final chunkId = String.fromCharCodes(bytes.sublist(offset, offset + 4));
     final chunkSize = bd.getUint32(offset + 4, Endian.little);
+    final dataOffset = offset + 8;
+    final paddedChunkSize = chunkSize + (chunkSize & 1);
+    if (paddedChunkSize > bytes.length - dataOffset) return null;
+
     if (chunkId == 'data') {
-      dataBytes = Uint8List.view(bytes.buffer, bytes.offsetInBytes + offset + 8, chunkSize);
+      dataBytes = Uint8List.view(
+        bytes.buffer,
+        bytes.offsetInBytes + dataOffset,
+        chunkSize,
+      );
       break;
     }
-    offset += 8 + chunkSize;
+    offset = dataOffset + paddedChunkSize;
   }

Also applies to: 54-62

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/wav_encoder.dart` around lines 35 - 47,
Update decodeWav’s RIFF chunk loop to validate each chunk’s complete padded
length, including the required padding byte for odd chunk sizes, before reading
chunk contents or advancing offset; return null when the chunk exceeds the
input. Require fmt chunks to be at least 16 bytes before ByteData reads, and
reject decoded WAVs with zero channels while preserving null as the invalid-file
result.
flutter-examples/vad-from-file/web/vad-worker.js-155-155 (1)

155-155: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

A runVad message is dropped silently when savedConfig is null.

The guard && savedConfig makes the handler skip the message when initialization has not completed or has failed. The worker posts no result and no error. The main thread then waits for a response that never arrives.

Handle the two conditions separately and report the error.

🐛 Proposed fix to report the missing configuration
-  else if (msg.type === 'runVad' && savedConfig) {
+  else if (msg.type === 'runVad') {
     try {
+      if (!savedConfig) {
+        throw new Error('VAD is not initialized. Send an init message first.');
+      }
       _cancelled = false;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/web/vad-worker.js` at line 155, Update the
runVad message handling around the msg.type === 'runVad' branch to process every
runVad request, then validate savedConfig separately before running VAD. When
savedConfig is null or initialization failed, post an error response instead of
silently ignoring the request; otherwise preserve the existing VAD execution and
result handling.
flutter-examples/vad-from-file/web/vad-worker.js-208-251 (1)

208-251: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Throttle the progress messages.

The loop posts one progress message per window. With a 512-sample window at 16 kHz, that is about 31 messages per second of audio. A 10-minute file produces more than 18,000 messages. Each message wakes the main thread and typically triggers a widget rebuild, which slows the UI during processing.

Post progress only when the reported value changes by a visible amount.

♻️ Proposed fix to throttle progress reporting
       const segments = [];
+      let lastProgress = -1;
 
       for (let i = 0; i < numIter; i++) {
@@
         // Report progress.
         const progress = (i + 1) / numIter;
-        self.postMessage({ type: 'progress', progress: progress });
+        // Report at most 100 updates per run.
+        const bucket = Math.floor(progress * 100);
+        if (bucket !== lastProgress) {
+          lastProgress = bucket;
+          self.postMessage({ type: 'progress', progress: progress });
+        }
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/web/vad-worker.js` around lines 208 - 251,
Throttle progress reporting in the processing loop around the existing
self.postMessage call so messages are emitted only when progress changes by a
visible threshold, while still reporting completion. Track the last reported
progress outside the loop and update it only when posting; preserve the existing
progress calculation and message shape.
flutter-examples/vad-from-file/lib/web_audio.dart-37-82 (1)

37-82: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return the picker promise and skip downloads on user cancel.

eval.callAsFunction returns the async IIFE’s Promise immediately, so callers continue before the file write completes. Store that promise and await it via .toDart. In the picker block, catch AbortError and return instead of executing the direct download fallback; only use the fallback when the picker is unavailable or for unrelated JS/API failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/web_audio.dart` around lines 37 - 82,
Update saveWavBytesWithDialog so it stores the Promise returned by
eval.callAsFunction for the async IIFE and awaits it via toDart before
returning. In the showSaveFilePicker catch block, return immediately for
AbortError after clearing temporary globals, while allowing unavailable-picker
or unrelated failures to continue to the direct-download fallback.
flutter-examples/vad-from-file/android/app/build.gradle.kts-33-38 (1)

33-38: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use a protected release signing key for published APKs.

Line 37 signs every release APK with the debug signing configuration. The CI workflow publishes APKs as release assets. Configure signingConfigs.release from protected CI secrets. Reserve the debug key for debug builds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/android/app/build.gradle.kts` around lines 33
- 38, Update the release signing configuration in buildTypes.release to use
signingConfigs.release populated from protected CI secrets, and define the
required release keystore properties/configuration. Remove the debug
signingConfig assignment so signingConfigs.debug is used only for debug builds
while CI-published release APKs use the protected key.
flutter-examples/vad-from-file/windows/runner/utils.cpp-48-57 (1)

48-57: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle WideCharToMultiByte failure before subtracting one.

WideCharToMultiByte returns 0 on failure. The current code subtracts one from that value and stores it in unsigned int, which underflows to a very large size. std::string::resize can then throw instead of returning the documented empty string. Store the conversion result in a signed variable, check failure before removing the null terminator, and only subtract one afterward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/windows/runner/utils.cpp` around lines 48 -
57, Update the UTF-16 conversion flow around WideCharToMultiByte in the
conversion function: store its result in a signed variable, return the empty
utf8_string immediately when the result is zero, and subtract one only after
that failure check. Preserve the existing max_size validation and resizing
behavior for successful conversions.
flutter-examples/vad-from-microphone/lib/worker_web.dart-71-79 (1)

71-79: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply the control values to the active Ten VAD configuration.

configToJs(config) serializes both sileroVad and tenVad, and prepareModelConfig() fills Ten VAD from defaultVadConfig. This code only updates sileroVad; when selectedModelIndex == 1, Ten VAD keeps its stored defaults and ignores the init-time user controls. Update the active config key or both VAD branches so Ten VAD receives threshold, minSilenceDuration, minSpeechDuration, and maxSpeechDuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/worker_web.dart` around lines 71 -
79, Update the configuration override in the worker initialization flow after
configToJs(config) so the user control values are applied to the active VAD
configuration, including the tenVad branch when selectedModelIndex == 1. Set
threshold, minSilenceDuration, minSpeechDuration, and maxSpeechDuration on the
appropriate VAD object while preserving the existing sileroVad behavior.
flutter-examples/vad-from-microphone/lib/model_web.dart-3-5 (1)

3-5: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Raise the Flutter minimum to match the web implementation.

flutter-examples/vad-from-microphone/pubspec.yaml allows flutter: ">=2.8.1", but the web code depends on CI toolchains at 3.44.8 or latest and modern Dart Web APIs. A Dart SDK 3.x / Flutter 2.8.1 build cannot compile dart:js_interop_unsafe, package:web, or the web AssetManifest.loadFromAssetBundle path. Set a compatible Flutter SDK minimum or replace the web implementation with APIs supported by >=2.8.1.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/model_web.dart` around lines 3 - 5,
Raise the Flutter SDK minimum in
flutter-examples/vad-from-microphone/pubspec.yaml to a version compatible with
the web implementation, including model_web.dart’s dart:js_interop_unsafe and
package:web usage, model.dart’s AssetManifest.loadFromAssetBundle path, and
worker_web.dart’s modern web APIs; do not replace these APIs with legacy
equivalents.
flutter-examples/vad-from-microphone/lib/vad_manager.dart-176-194 (1)

176-194: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Native VAD detector disposal is not deterministic. VadMicManager.dispose is synchronous and kills the isolate with Isolate.immediate right after it posts _DisposeRequest, so the isolate can terminate before vad.free() releases the native FFI handle. Making disposal deterministic requires a change at the manager and at every call site.

  • flutter-examples/vad-from-microphone/lib/vad_manager.dart#L176-L194: make dispose() return Future<void>, wait for the isolate to process _DisposeRequest (or for an explicit acknowledgement) before kill, use Isolate.beforeNextEvent, and close the main-side ReceivePort created at Line 287.
  • flutter-examples/vad-from-microphone/lib/vad_screen.dart#L216-L217: await _manager?.dispose() before constructing the replacement manager, and await it in State.dispose() at Line 561 through an unawaited-but-sequenced helper.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/vad_manager.dart` around lines 176 -
194, Make VadMicManager.dispose() return Future<void>, await acknowledgement
that _DisposeRequest completed before terminating the isolate with
Isolate.beforeNextEvent, and close the main-side ReceivePort created during
initialization. Update vad_screen.dart call sites at lines 216-217 and 561 to
await or sequence disposal before creating a replacement manager or completing
State.dispose().
flutter-examples/vad-from-microphone/lib/wav_encoder.dart-35-63 (1)

35-63: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

decodeWav throws on malformed input instead of returning null. The doc comment at Line 14 promises a null return for an invalid file, but the parser trusts the declared chunk sizes and the fmt field values without validation. Malformed or truncated input therefore raises RangeError or IntegerDivisionByZeroException.

  • flutter-examples/vad-from-microphone/lib/wav_encoder.dart#L35-L63: honour RIFF even-byte chunk padding when advancing offset, verify that the fmt chunk holds at least 16 bytes before reading bitsPerSample, and clamp the data chunk view to the remaining byte count.
  • flutter-examples/vad-from-microphone/lib/wav_encoder.dart#L66-L93: validate numChannels > 0 and sampleRate > 0 before the divisions in both decode branches, and return null otherwise.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/wav_encoder.dart` around lines 35 -
63, The decodeWav parser must return null rather than throw on malformed WAV
input. In flutter-examples/vad-from-microphone/lib/wav_encoder.dart:35-63,
update both chunk-scanning loops to honor even-byte RIFF padding, require the
fmt chunk to contain at least 16 bytes before reading its fields, and clamp the
data chunk view to the remaining bytes. In
flutter-examples/vad-from-microphone/lib/wav_encoder.dart:66-93, validate
numChannels and sampleRate are greater than zero before either decode branch
performs division, returning null when invalid; preserve valid-input decoding
behavior.
flutter-examples/vad-from-microphone/lib/vad_manager.dart-357-369 (1)

357-369: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Send _SegmentCountChanged only when the count changes.

The if (segmentCount > 0) check sits outside the while loop but inside the audio-chunk handler. After the first segment is detected, every later audio chunk sends a _SegmentCountChanged message with an unchanged value. vad_screen.dart Line 226 calls setState for each of these messages. At 16 kHz with a 512-sample window this produces about 31 rebuilds per second with no state change.

🔧 Proposed fix
           // Collect completed segments (available after speech ends).
+          bool countChanged = false;
           while (!vad!.isEmpty()) {
             final seg = vad!.front();
-            final sampleRate = 16000; // Always 16kHz for VAD.
+            const sampleRate = 16000; // Always 16kHz for VAD.
             final startSec = seg.start / sampleRate;
             final endSec = startSec + seg.samples.length / sampleRate;
             mainSendPort.send(_SegmentDetected(startSec, endSec, seg.samples));
             vad!.pop();
             segmentCount++;
+            countChanged = true;
           }
-          if (segmentCount > 0) {
+          if (countChanged) {
             mainSendPort.send(_SegmentCountChanged(segmentCount));
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/vad_manager.dart` around lines 357 -
369, Update the audio-chunk handling around the segment collection loop so
_SegmentCountChanged is sent only when segmentCount increases during that
handler invocation. Preserve sending _SegmentDetected for each completed
segment, but avoid emitting the count message when no new segments were
collected.
flutter-examples/vad-from-microphone/lib/vad_manager.dart-204-241 (1)

204-241: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Propagate the web initialization failure.

_initWeb catches the error, logs it, and returns normally. _initNative rethrows at Line 324. Because of this difference, init() on web completes successfully after a worker failure. _startListening in vad_screen.dart Lines 232-242 then treats the initialization as successful and starts the recorder. acceptWaveform drops every chunk, because _state stays uninitialized. The user sees "Recording..." but no segment is ever produced.

Rethrow so both platforms behave the same way.

🔧 Proposed fix
     } catch (e) {
       _state = VadState.uninitialized;
       _logController.add('Error: $e');
+      rethrow;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/vad_manager.dart` around lines 204 -
241, Update the catch block in _initWeb to rethrow the initialization error
after setting the uninitialized state and logging it, matching _initNative’s
failure behavior so init() rejects and _startListening does not start recording
after a worker failure.
flutter-examples/vad-from-microphone/lib/vad_screen.dart-96-109 (1)

96-109: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix the web playback stop path.

On web, _togglePlayback sets _isPlayingRecording to true and calls web_audio.playWavBytes. Nothing resets the flag, because _player!.onPlayerComplete (Line 67) never fires for Web Audio playback. The button then stays on "Stop Playback" permanently. The stop branch calls _player?.stop(), which does not stop the browser Audio element created by web_audio.dart.

web_audio.dart already exports stopPlayback(). Use it on the web path, and reset the flag after the estimated duration, as _playSegment does at Lines 125-133.

🔧 Proposed fix
   void _togglePlayback() {
     if (_recordedWavBytes == null) return;
     if (_isPlayingRecording) {
-      _player?.stop();
+      if (kIsWeb) {
+        web_audio.stopPlayback();
+      } else {
+        _player?.stop();
+      }
       setState(() => _isPlayingRecording = false);
     } else {
       setState(() => _isPlayingRecording = true);
       if (kIsWeb) {
         web_audio.playWavBytes(_recordedWavBytes!);
+        final duration = _capturedSamples / 16000.0;
+        Future.delayed(
+            Duration(milliseconds: (duration * 1000).ceil() + 200), () {
+          if (mounted && _isPlayingRecording) {
+            setState(() => _isPlayingRecording = false);
+          }
+        });
       } else {
         play_bytes.playWavBytes(_player!, _recordedWavBytes!);
       }
     }
   }

The same gap exists in _playSegment Lines 115-119: the stop branch calls _player?.stop() on web, which does not stop browser playback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/vad_screen.dart` around lines 96 -
109, Update _togglePlayback and _playSegment so their web stop branches call
web_audio.stopPlayback() instead of only stopping _player. For web playback,
schedule _isPlayingRecording to reset after the estimated WAV duration, matching
the existing _playSegment completion behavior; preserve the current
native-player handling for non-web platforms.
flutter-examples/vad-from-microphone/macos/Runner/Info.plist-4-30 (1)

4-30: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add NSMicrophoneUsageDescription.

This macOS target enables com.apple.security.device.audio-input, but flutter-examples/vad-from-microphone/macos/Runner/Info.plist does not define the required microphone usage description. Add a user-facing string for Privacy - Microphone Usage Description so the app can request microphone access.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/macos/Runner/Info.plist` around lines 4
- 30, Add the NSMicrophoneUsageDescription key to the macOS Runner Info.plist
dictionary with a clear user-facing explanation of why the app needs microphone
access, while preserving the existing plist entries.
flutter-examples/vad-from-microphone/pubspec.yaml-9-11 (1)

9-11: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Raise the minimum Dart SDK constraint.

flutter-examples/vad-from-microphone/pubspec.yaml declares sdk: ">=3.1.0 <4.0.0" while record: ^6.2.0 requires Dart ^3.5.0. Users on Dart 3.1 through 3.4 satisfy the app constraint but cannot resolve dependencies. Set the minimum Dart SDK to 3.5.0 or use a compatible record version.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/pubspec.yaml` around lines 9 - 11,
Update the environment SDK constraint in pubspec.yaml to require Dart >=3.5.0,
keeping the existing upper bound and Flutter constraint unchanged so it is
compatible with record ^6.2.0.
🟡 Minor comments (19)
flutter-examples/tts/lib/web_audio.dart-48-67 (1)

48-67: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not download the file after the user cancels the save dialog.

showSaveFilePicker rejects with an AbortError when the user cancels. The catch block treats cancellation the same as an unsupported API, so the code falls through and downloads the file anyway. The user cancels the save, and the file still lands in the downloads folder.

Distinguish AbortError from other failures.

🔧 Proposed fix: return on cancellation
         } catch (e) {
-          // User cancelled or API not available, fall through to download.
+          if (e && e.name === 'AbortError') {
+            // User cancelled the save dialog. Do not download.
+            window._sherpaSaveBytes = null;
+            window._sherpaSaveFilename = null;
+            return;
+          }
+          // Write failed. Fall through to the download fallback.
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/tts/lib/web_audio.dart` around lines 48 - 67, Update the
catch block around window.showSaveFilePicker to detect an AbortError and return
immediately when the user cancels the save dialog. Preserve the existing
fallback download behavior for unsupported APIs and other non-cancellation
failures.
flutter-examples/vad-from-file/lib/web_blob_url.dart-18-22 (1)

18-22: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a way to revoke the object URL.

URL.createObjectURL keeps the whole blob alive until the URL is revoked or the tab closes. vad_screen.dart creates a new URL for every picked file and never revokes the previous one. Large media files then accumulate in browser memory.

Export a revokeBlobUrl(String url) helper and call it when the player controller is replaced or cleared.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/web_blob_url.dart` around lines 18 - 22,
Export a revokeBlobUrl(String url) helper alongside the existing object-URL
creation logic, using URL.revokeObjectURL to release the blob. Update
vad_screen.dart to invoke this helper whenever the player controller is replaced
or cleared, revoking the previously stored URL before discarding it while
preserving the current URL creation flow.
flutter/sherpa_onnx/lib/src/web/vad.dart-141-157 (1)

141-157: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the other methods against use after free().

free() sets _freed, but acceptWaveform, isEmpty, isDetected, pop, clear, front, reset, and flush do not check it. A call after free() reaches a released JavaScript handle. The same gap exists in CircularBuffer.

Add an early return or a StateError when _freed is true, and set ptr = null in free().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter/sherpa_onnx/lib/src/web/vad.dart` around lines 141 - 157, Guard every
public method on the VAD and CircularBuffer wrappers—acceptWaveform, isEmpty,
isDetected, pop, clear, front, reset, and flush—against calls after _freed,
using an early return or StateError consistent with each method’s return type.
Update free() to release the handle and then set ptr to null while preserving
idempotency.
flutter-examples/vad-from-file/lib/vad_manager.dart-186-201 (1)

186-201: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Close the main-isolate ReceivePort in dispose().

_initNative creates a ReceivePort at line 250 and keeps a subscription on it. dispose() kills the isolate but never closes that port. The port and its listener stay alive for the process lifetime.

♻️ Proposed fix
+  ReceivePort? _receivePort;
+
   void dispose() {
     _state = VadState.uninitialized;
     if (kIsWeb) {
       _worker?.dispose();
       _worker = null;
     } else {
       _sendPort?.send(_DisposeRequest());
       _isolate?.kill(priority: Isolate.immediate);
       _isolate = null;
       _sendPort = null;
+      _receivePort?.close();
+      _receivePort = null;
     }

Assign _receivePort = receivePort; in _initNative after line 250.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/vad_manager.dart` around lines 186 - 201,
Update dispose() to close the main-isolate ReceivePort and clear its reference,
and ensure _initNative assigns the created receivePort to the manager’s
_receivePort field so dispose() can access it. Keep the existing isolate
shutdown and controller cleanup behavior unchanged.
flutter-examples/vad-from-file/lib/audio_decoder_native.dart-62-72 (1)

62-72: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Delete the output file on the FFmpeg failure paths.

If FFmpeg fails or writes a partial file, both early returns leave outputPath on disk. Temp files then accumulate across runs. Use a finally block or delete before each return.

♻️ Proposed cleanup
     if (!ReturnCode.isSuccess(returnCode)) {
       final logs = await session.getOutput();
       print('FFmpeg error: $logs');
+      try {
+        final f = File(outputPath);
+        if (await f.exists()) await f.delete();
+      } catch (_) {}
       return null;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/audio_decoder_native.dart` around lines 62
- 72, Update the FFmpeg handling in the audio decoder around the returnCode
check and outputPath existence check to delete the output file before either
early return, covering both command failure and missing/partial output cases.
Reuse the existing File(outputPath) cleanup target and preserve the current
logging and null-return behavior.
flutter-examples/vad-from-file/lib/vad_controls.dart-34-35 (1)

34-35: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the Min Speech Duration hint with the default value.

vad_screen.dart initializes _minSpeechController with '0.25', but this hint shows 0.5. Users see two different recommended values.

🐛 Proposed fix
                 _buildField(
-                    'Min Speech Duration (s)', minSpeechController, '0.5'),
+                    'Min Speech Duration (s)', minSpeechController, '0.25'),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/vad_controls.dart` around lines 34 - 35,
Update the Min Speech Duration hint in _buildField within vad_controls.dart to
match the '0.25' default initialized by _minSpeechController in vad_screen.dart,
keeping the label and controller unchanged.
flutter-examples/vad-from-file/lib/vad_screen.dart-490-497 (1)

490-497: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard the RTF division against a zero duration.

Line 79 guards _audioDuration > 0, but this widget does not. If _audioDuration is 0, the text shows NaN or Infinity.

🐛 Proposed fix
-                  'RTF: ${(_elapsed / _audioDuration).toStringAsFixed(3)} '
+                  'RTF: ${(_audioDuration > 0 ? _elapsed / _audioDuration : 0.0).toStringAsFixed(3)} '
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/vad_screen.dart` around lines 490 - 497,
Guard the RTF calculation in the Text widget around _audioDuration so division
occurs only when the duration is greater than zero. Display a safe fallback
value instead of NaN or Infinity when _audioDuration is zero, while preserving
the existing formatted RTF output for valid durations.
flutter-examples/vad-from-file/lib/worker_web.dart-96-104 (1)

96-104: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Send only the requested sample view.

Line 98 sends the complete backing buffer. If samples is a Float32List view, the worker receives samples outside the requested view. Copy the view into an exact buffer before posting the message.

Proposed fix
   }) {
+    final sampleData = Float32List.fromList(samples);
     final msg = JSObject();
     msg['type'] = 'runVad'.toJS;
-    msg['samples'] = samples.buffer.toJS;
+    msg['samples'] = sampleData.buffer.toJS;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/worker_web.dart` around lines 96 - 104,
Update the message construction in the worker request flow to copy `samples`
into an exact-length `Float32List` view before assigning its buffer to
`msg['samples']`. Preserve the existing sample metadata and
`_worker?.postMessage(msg)` behavior while ensuring no bytes outside the
requested samples view are sent.
flutter-examples/vad-from-file/pubspec.yaml-9-11 (1)

9-11: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Raise the Flutter SDK lower bound.

The package requires Dart 3.1, but flutter: ">=2.8.1" also allows Flutter 2.8.x, which does not bundle Dart 3.1. Set the Flutter lower bound to Flutter 3.13.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/pubspec.yaml` around lines 9 - 11, Update the
Flutter SDK constraint in the environment section of pubspec.yaml from 2.8.1 to
a Flutter 3.13 lower bound, while preserving the existing Dart SDK constraint.
flutter-examples/vad-from-file/web/vad-worker.js-209-209 (1)

209-209: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

A cancelled run reports a normal result.

Line 209 breaks the loop when _cancelled is set. The code then flushes and posts a result message with the partial segments. The main thread cannot tell a cancelled run from a completed run, so it displays partial output as the final output.

Add a cancellation flag to the result message, or post a distinct message type.

🐛 Proposed fix to mark the result as cancelled
       self.postMessage({
         type: 'result',
         segments: segments,
         elapsed: elapsed,
         audioDuration: audioDuration,
+        cancelled: _cancelled,
       });

Update the worker message documentation at lines 37-43 and the main-thread handler to read the new field.

Also applies to: 281-286

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/web/vad-worker.js` at line 209, Update the
cancellation path in the worker loop around _cancelled so the emitted result
message indicates that processing was cancelled, rather than appearing as a
normal completion. Document the new cancellation field in the worker message
documentation and update the main-thread result handler to read it and avoid
treating partial segments as final output.
flutter-examples/vad-from-file/web/vad-worker.js-166-171 (1)

166-171: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

|| replaces a valid 0 parameter with the default.

msg.threshold || 0.5 evaluates the default whenever the value is 0. The same applies to minSilenceDuration, minSpeechDuration, and maxSpeechDuration. A user who sets minSpeechDuration to 0 to disable the minimum gets 0.25 instead. Use ?? so only null and undefined select the default.

🐛 Proposed fix for the default values
       const userParams = {
-        threshold: msg.threshold || 0.5,
-        minSilenceDuration: msg.minSilenceDuration || 0.5,
-        minSpeechDuration: msg.minSpeechDuration || 0.25,
-        maxSpeechDuration: msg.maxSpeechDuration || 5.0,
+        threshold: msg.threshold ?? 0.5,
+        minSilenceDuration: msg.minSilenceDuration ?? 0.5,
+        minSpeechDuration: msg.minSpeechDuration ?? 0.25,
+        maxSpeechDuration: msg.maxSpeechDuration ?? 5.0,
       };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/web/vad-worker.js` around lines 166 - 171,
Update the userParams defaults in the worker message handling to use nullish
coalescing (??) instead of logical OR (||) for threshold, minSilenceDuration,
minSpeechDuration, and maxSpeechDuration, preserving explicit 0 values while
still defaulting null or undefined inputs.
flutter-examples/vad-from-file/lib/web_audio.dart-139-159 (1)

139-159: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

playWavBytes never revokes the object URL.

Each call creates a blob URL and assigns it to a new Audio. The URL is never revoked, so every playback retains the WAV bytes for the lifetime of the page. Revoke the previous URL when you replace the audio element.

♻️ Proposed fix to revoke the blob URL
       if (window._sherpaCurrentAudio) {
         window._sherpaCurrentAudio.pause();
         window._sherpaCurrentAudio.currentTime = 0;
+        if (window._sherpaCurrentAudioUrl) {
+          URL.revokeObjectURL(window._sherpaCurrentAudioUrl);
+          window._sherpaCurrentAudioUrl = null;
+        }
       }
       var bytes = window._sherpaWavBytes;
       var blob = new Blob([bytes], {type: 'audio/wav'});
       var url = URL.createObjectURL(blob);
       var audio = new Audio(url);
       window._sherpaCurrentAudio = audio;
+      window._sherpaCurrentAudioUrl = url;
       audio.play();

Apply the same revoke in stopPlayback at lines 167-171.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-file/lib/web_audio.dart` around lines 139 - 159,
Update playWavBytes and stopPlayback to track the current blob URL and call
URL.revokeObjectURL on the previous URL before replacing the audio element or
stopping playback. Preserve the existing audio pause/reset behavior, and clear
the tracked URL after revocation.
flutter-examples/vad-from-microphone/README.md-32-33 (1)

32-33: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Create the assets directory before downloading a model.

Git does not retain empty directories. A fresh checkout does not contain flutter-examples/vad-from-microphone/assets, so these cd commands fail.

Proposed fix
+mkdir -p flutter-examples/vad-from-microphone/assets
 cd flutter-examples/vad-from-microphone/assets
 wget https://github.com/k2-fsa/sherpa-onnx/releases/download/vad-models/silero_vad.onnx

Also applies to: 40-41

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/README.md` around lines 32 - 33, Update
the README download instructions to create the vad-from-microphone assets
directory before changing into it, applying the same fix to both referenced
download command blocks. Preserve the existing model download commands after the
directory creation step.
flutter-examples/vad-from-microphone/lib/vad_screen.dart-78-94 (1)

78-94: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Check mounted before setState.

_loadDevices awaits hasPermission() and listInputDevices(). If the user leaves the screen while a permission prompt is open, the widget is disposed before the awaits resolve, and both setState calls throw. The refresh button at Line 399 also calls this method. Add a mounted check in the success path and the catch block.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/vad_screen.dart` around lines 78 -
94, Update _loadDevices to check mounted after the awaited device-loading
operations before the success-path setState, and again in the catch block before
updating _logController, so no state updates occur after the widget is disposed.
flutter-examples/vad-from-microphone/lib/wav_encoder.dart-66-93 (1)

66-93: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Reject a zero channel count.

Both decode branches divide by numChannels. A malformed fmt chunk can report 0, and dataBytes.length ~/ (2 * numChannels) then throws IntegerDivisionByZeroException. The function contract states that it returns null for invalid input. Validate numChannels > 0 and sampleRate > 0 before Line 66.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/wav_encoder.dart` around lines 66 -
93, Validate numChannels > 0 and sampleRate > 0 before the PCM decoding branches
in the WAV decode function, returning null for invalid values. Ensure this check
runs before calculating numSamples or dividing by numChannels in either branch.
flutter-examples/vad-from-microphone/lib/vad_screen.dart-316-325 (1)

316-325: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the loop against an odd byte count.

The loop bound is bytes.length, but values has length bytes.length ~/ 2. If a stream chunk contains an odd number of bytes, the final iteration calls data.getInt16(i) past the end of the view and writes values[bytes.length ~/ 2], which is out of range. Both operations throw inside the stream callback. Iterate over values.length instead.

🔧 Proposed fix
   static Float32List _convertBytesToFloat32(Uint8List bytes) {
     final values = Float32List(bytes.length ~/ 2);
     final data = ByteData.view(bytes.buffer, bytes.offsetInBytes, bytes.lengthInBytes);
-    for (var i = 0; i < bytes.length; i += 2) {
-      int short = data.getInt16(i, Endian.little);
-      values[i ~/ 2] = short / 32768.0;
+    for (var i = 0; i < values.length; i++) {
+      values[i] = data.getInt16(i * 2, Endian.little) / 32768.0;
     }
     return values;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/vad_screen.dart` around lines 316 -
325, Update _convertBytesToFloat32 so the conversion loop iterates only over
complete PCM16 samples using values.length, preventing out-of-range reads and
writes when bytes contains an odd number of bytes.
flutter-examples/vad-from-microphone/lib/web_audio.dart-139-159 (1)

139-159: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Revoke the object URL after playback.

playWavBytes calls URL.createObjectURL(blob) and never calls URL.revokeObjectURL. The browser retains the blob until the page reloads. vad_screen.dart Line 126 calls this method for every segment playback, so a session with many segments accumulates the full WAV data of each played segment in memory.

Revoke the previous URL when replacing the audio element, and revoke the current one on the ended event.

🔧 Proposed fix
     (function() {
       // Stop previous audio.
       if (window._sherpaCurrentAudio) {
         window._sherpaCurrentAudio.pause();
         window._sherpaCurrentAudio.currentTime = 0;
+        if (window._sherpaCurrentUrl) {
+          URL.revokeObjectURL(window._sherpaCurrentUrl);
+          window._sherpaCurrentUrl = null;
+        }
       }
       var bytes = window._sherpaWavBytes;
       var blob = new Blob([bytes], {type: 'audio/wav'});
       var url = URL.createObjectURL(blob);
       var audio = new Audio(url);
       window._sherpaCurrentAudio = audio;
-      audio.play();
+      window._sherpaCurrentUrl = url;
+      audio.addEventListener('ended', function() {
+        URL.revokeObjectURL(url);
+        if (window._sherpaCurrentUrl === url) window._sherpaCurrentUrl = null;
+      });
+      audio.play().catch(function(e) { console.warn('play failed', e); });
       window._sherpaWavBytes = null;
     })()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/web_audio.dart` around lines 139 -
159, Update playWavBytes to track the object URL associated with
window._sherpaCurrentAudio, revoke that previous URL before replacing the audio
element, and register an ended handler on the new audio that calls
URL.revokeObjectURL for its URL. Preserve stopping and resetting previous
playback while ensuring each created URL is revoked exactly when replaced or
playback ends.
flutter-examples/vad-from-microphone/lib/web_audio_stub.dart-1-4 (1)

1-4: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Make the encodeWav stub delegate to the real encoder.

web_audio.dart Line 8 re-exports the real encodeWav from wav_encoder.dart. This native stub returns Uint8List(0) instead. The two sides of the conditional import therefore have different behaviour for the same name. Any native call through the web_audio alias produces an empty WAV file with no error.

vad_screen.dart currently resolves encodeWav through its direct wav_encoder.dart import, so the defect is not reachable today. Re-export the real implementation so the two paths stay equivalent.

🔧 Proposed fix
 // Native stub for web_audio.dart.
 import 'dart:typed_data';
 
-Uint8List encodeWav(Float32List samples, int sampleRate) => Uint8List(0);
+export './wav_encoder.dart' show encodeWav;
+
 void playWavBytes(Uint8List wavBytes) {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/lib/web_audio_stub.dart` around lines 1
- 4, Update the native stub’s encodeWav definition in web_audio_stub.dart to
re-export or delegate to the real encodeWav implementation from
wav_encoder.dart, removing the empty Uint8List behavior and preserving
equivalent behavior with web_audio.dart.
flutter-examples/vad-from-microphone/web/index.html-21-21 (1)

21-21: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace the default web application description.

Both files retain "A new Flutter project.". Use a description that identifies the microphone VAD demo.

  • flutter-examples/vad-from-microphone/web/index.html#L21-L21: update the meta description.
  • flutter-examples/vad-from-microphone/web/manifest.json#L8-L8: use the same application description.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@flutter-examples/vad-from-microphone/web/index.html` at line 21, Replace the
default application description with text identifying the microphone VAD demo in
flutter-examples/vad-from-microphone/web/index.html lines 21-21 and use the same
description in flutter-examples/vad-from-microphone/web/manifest.json lines 8-8.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds two cross‑platform Flutter VAD demo apps (microphone + file) and updates CI/web helpers to support packaging and web “Save As” behavior.

Changes:

  • Introduces vad-from-microphone and vad-from-file Flutter apps with platform runners (Android/iOS/macOS/Linux/Windows/Web) and supporting Dart helpers.
  • Adds model configuration/loading helpers and native/web stubs for platform-specific functionality (workers, audio, save/play utilities).
  • Updates Flutter CI workflow to ad-hoc sign macOS app artifacts and expands web audio helpers with a “Save As” dialog path.

Reviewed changes

Copilot reviewed 212 out of 293 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
flutter-examples/vad-from-microphone/windows/runner/win32_window.h Adds Windows runner window abstraction for the microphone demo
flutter-examples/vad-from-microphone/windows/runner/utils.h Declares Windows runner utility helpers
flutter-examples/vad-from-microphone/windows/runner/utils.cpp Implements Windows console attachment and UTF conversions
flutter-examples/vad-from-microphone/windows/runner/runner.exe.manifest Enables PerMonitorV2 DPI awareness for Windows runner
flutter-examples/vad-from-microphone/windows/runner/resource.h Adds Windows resource IDs for runner
flutter-examples/vad-from-microphone/windows/runner/main.cpp Windows entrypoint for microphone demo
flutter-examples/vad-from-microphone/windows/runner/flutter_window.h Declares Flutter host window for Windows runner
flutter-examples/vad-from-microphone/windows/runner/flutter_window.cpp Implements Flutter host window behavior for Windows runner
flutter-examples/vad-from-microphone/windows/runner/Runner.rc Adds icon/version resources for Windows runner
flutter-examples/vad-from-microphone/windows/runner/CMakeLists.txt Builds Windows runner for microphone demo
flutter-examples/vad-from-microphone/windows/flutter/CMakeLists.txt Flutter-provided Windows build glue
flutter-examples/vad-from-microphone/windows/CMakeLists.txt Project-level Windows build configuration
flutter-examples/vad-from-microphone/windows/.gitignore Ignores Windows ephemeral/build artifacts
flutter-examples/vad-from-microphone/web/manifest.json Web app manifest for microphone demo
flutter-examples/vad-from-microphone/web/index.html Web bootstrap HTML for microphone demo
flutter-examples/vad-from-microphone/pubspec.yaml Flutter dependencies and assets for microphone demo
flutter-examples/vad-from-microphone/macos/RunnerTests/RunnerTests.swift macOS test scaffold
flutter-examples/vad-from-microphone/macos/Runner/Release.entitlements macOS release entitlements (audio input + file access)
flutter-examples/vad-from-microphone/macos/Runner/MainFlutterWindow.swift macOS Flutter window host
flutter-examples/vad-from-microphone/macos/Runner/Info.plist macOS app metadata
flutter-examples/vad-from-microphone/macos/Runner/DebugProfile.entitlements macOS debug/profile entitlements
flutter-examples/vad-from-microphone/macos/Runner/Configs/Warnings.xcconfig macOS warning configuration
flutter-examples/vad-from-microphone/macos/Runner/Configs/Release.xcconfig macOS release config include chain
flutter-examples/vad-from-microphone/macos/Runner/Configs/Debug.xcconfig macOS debug config include chain
flutter-examples/vad-from-microphone/macos/Runner/Configs/AppInfo.xcconfig macOS app identity configuration
flutter-examples/vad-from-microphone/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json macOS icon asset catalog metadata
flutter-examples/vad-from-microphone/macos/Runner/AppDelegate.swift macOS app delegate
flutter-examples/vad-from-microphone/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist macOS workspace checks metadata
flutter-examples/vad-from-microphone/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme macOS scheme config
flutter-examples/vad-from-microphone/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist macOS project workspace checks metadata
flutter-examples/vad-from-microphone/macos/Flutter/Flutter-Release.xcconfig macOS Flutter release config
flutter-examples/vad-from-microphone/macos/Flutter/Flutter-Debug.xcconfig macOS Flutter debug config
flutter-examples/vad-from-microphone/macos/.gitignore Ignores macOS build artifacts
flutter-examples/vad-from-microphone/linux/runner/my_application.h Linux GTK runner declaration
flutter-examples/vad-from-microphone/linux/runner/my_application.cc Linux GTK runner implementation
flutter-examples/vad-from-microphone/linux/runner/main.cc Linux runner entrypoint
flutter-examples/vad-from-microphone/linux/runner/CMakeLists.txt Linux runner build
flutter-examples/vad-from-microphone/linux/flutter/CMakeLists.txt Flutter-provided Linux build glue
flutter-examples/vad-from-microphone/linux/CMakeLists.txt Project-level Linux build configuration
flutter-examples/vad-from-microphone/linux/.gitignore Ignores Linux ephemeral artifacts
flutter-examples/vad-from-microphone/lib/worker_stub.dart Native stub for web worker API surface
flutter-examples/vad-from-microphone/lib/web_audio_stub.dart Native stub for web audio helpers
flutter-examples/vad-from-microphone/lib/vad_controls.dart UI controls for VAD parameters
flutter-examples/vad-from-microphone/lib/save_file_stub.dart Web stub for native “save file” helpers
flutter-examples/vad-from-microphone/lib/save_file.dart Native save dialog + file writing utility
flutter-examples/vad-from-microphone/lib/play_bytes_stub.dart Web stub for playing WAV bytes
flutter-examples/vad-from-microphone/lib/play_bytes.dart Native helper to play WAV bytes via temp file
flutter-examples/vad-from-microphone/lib/model_web.dart Web model loading helpers (asset bytes + JS config)
flutter-examples/vad-from-microphone/lib/model_config.dart Centralized VAD model selection/config
flutter-examples/vad-from-microphone/lib/model.dart Native model asset copying + config path resolution
flutter-examples/vad-from-microphone/ios/RunnerTests/RunnerTests.swift iOS test scaffold
flutter-examples/vad-from-microphone/ios/Runner/SceneDelegate.swift iOS scene delegate
flutter-examples/vad-from-microphone/ios/Runner/Runner-Bridging-Header.h iOS bridging header
flutter-examples/vad-from-microphone/ios/Runner/Info.plist iOS app metadata incl. microphone permission
flutter-examples/vad-from-microphone/ios/Runner/Base.lproj/Main.storyboard iOS main storyboard
flutter-examples/vad-from-microphone/ios/Runner/Base.lproj/LaunchScreen.storyboard iOS launch screen
flutter-examples/vad-from-microphone/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md iOS launch image notes
flutter-examples/vad-from-microphone/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json iOS launch image asset metadata
flutter-examples/vad-from-microphone/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json iOS app icon asset metadata
flutter-examples/vad-from-microphone/ios/Runner/AppDelegate.swift iOS app delegate + plugin registration
flutter-examples/vad-from-microphone/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings iOS workspace settings
flutter-examples/vad-from-microphone/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist iOS workspace checks metadata
flutter-examples/vad-from-microphone/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme iOS scheme config
flutter-examples/vad-from-microphone/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings iOS project workspace settings
flutter-examples/vad-from-microphone/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist iOS project workspace checks metadata
flutter-examples/vad-from-microphone/ios/Flutter/Release.xcconfig iOS Flutter release config
flutter-examples/vad-from-microphone/ios/Flutter/Debug.xcconfig iOS Flutter debug config
flutter-examples/vad-from-microphone/ios/Flutter/AppFrameworkInfo.plist iOS Flutter app framework metadata
flutter-examples/vad-from-microphone/ios/.gitignore Ignores iOS build artifacts
flutter-examples/vad-from-microphone/generate-asset-list.py Links asset list generation to shared script
flutter-examples/vad-from-microphone/android/settings.gradle.kts Android plugin management and versions
flutter-examples/vad-from-microphone/android/gradle/wrapper/gradle-wrapper.properties Gradle distribution pin
flutter-examples/vad-from-microphone/android/gradle.properties Android build JVM/AndroidX config
flutter-examples/vad-from-microphone/android/build.gradle.kts Android build directory wiring + repositories
flutter-examples/vad-from-microphone/android/app/src/profile/AndroidManifest.xml Android profile manifest (INTERNET for dev)
flutter-examples/vad-from-microphone/android/app/src/main/res/values/styles.xml Android launch/normal themes
flutter-examples/vad-from-microphone/android/app/src/main/res/values-night/styles.xml Android night mode themes
flutter-examples/vad-from-microphone/android/app/src/main/res/drawable/launch_background.xml Android splash background
flutter-examples/vad-from-microphone/android/app/src/main/res/drawable-v21/launch_background.xml Android v21 splash background
flutter-examples/vad-from-microphone/android/app/src/main/kotlin/com/example/vad_from_microphone/MainActivity.kt Android main activity
flutter-examples/vad-from-microphone/android/app/src/main/AndroidManifest.xml Android main manifest incl. RECORD_AUDIO
flutter-examples/vad-from-microphone/android/app/src/debug/AndroidManifest.xml Android debug manifest (INTERNET for dev)
flutter-examples/vad-from-microphone/android/app/build.gradle.kts Android app Gradle config
flutter-examples/vad-from-microphone/android/.gitignore Ignores Android build artifacts/keystores
flutter-examples/vad-from-microphone/analysis_options.yaml Analyzer/lints configuration
flutter-examples/vad-from-microphone/README.md Build instructions and feature list
flutter-examples/vad-from-microphone/.metadata Flutter tool metadata
flutter-examples/vad-from-microphone/.gitignore Ignores Flutter build/tool artifacts
flutter-examples/vad-from-file/windows/runner/win32_window.h Adds Windows runner window abstraction for the file demo
flutter-examples/vad-from-file/windows/runner/utils.h Declares Windows runner utility helpers
flutter-examples/vad-from-file/windows/runner/utils.cpp Implements Windows console attachment and UTF conversions
flutter-examples/vad-from-file/windows/runner/runner.exe.manifest Enables PerMonitorV2 DPI awareness for Windows runner
flutter-examples/vad-from-file/windows/runner/resource.h Adds Windows resource IDs for runner
flutter-examples/vad-from-file/windows/runner/main.cpp Windows entrypoint for file demo
flutter-examples/vad-from-file/windows/runner/flutter_window.h Declares Flutter host window for Windows runner
flutter-examples/vad-from-file/windows/runner/flutter_window.cpp Implements Flutter host window behavior for Windows runner
flutter-examples/vad-from-file/windows/runner/Runner.rc Adds icon/version resources for Windows runner
flutter-examples/vad-from-file/windows/runner/CMakeLists.txt Builds Windows runner for file demo
flutter-examples/vad-from-file/windows/flutter/CMakeLists.txt Flutter-provided Windows build glue
flutter-examples/vad-from-file/windows/CMakeLists.txt Project-level Windows build configuration
flutter-examples/vad-from-file/windows/.gitignore Ignores Windows ephemeral/build artifacts
flutter-examples/vad-from-file/web/manifest.json Web app manifest for file demo
flutter-examples/vad-from-file/web/index.html Web bootstrap HTML for file demo
flutter-examples/vad-from-file/pubspec.yaml Flutter dependencies and assets for file demo
flutter-examples/vad-from-file/macos/RunnerTests/RunnerTests.swift macOS test scaffold
flutter-examples/vad-from-file/macos/Runner/Release.entitlements macOS release entitlements (file access)
flutter-examples/vad-from-file/macos/Runner/MainFlutterWindow.swift macOS Flutter window host
flutter-examples/vad-from-file/macos/Runner/Info.plist macOS app metadata
flutter-examples/vad-from-file/macos/Runner/DebugProfile.entitlements macOS debug/profile entitlements
flutter-examples/vad-from-file/macos/Runner/Configs/Warnings.xcconfig macOS warning configuration
flutter-examples/vad-from-file/macos/Runner/Configs/Release.xcconfig macOS release config include chain
flutter-examples/vad-from-file/macos/Runner/Configs/Debug.xcconfig macOS debug config include chain
flutter-examples/vad-from-file/macos/Runner/Configs/AppInfo.xcconfig macOS app identity configuration
flutter-examples/vad-from-file/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json macOS icon asset catalog metadata
flutter-examples/vad-from-file/macos/Runner/AppDelegate.swift macOS app delegate
flutter-examples/vad-from-file/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist macOS workspace checks metadata
flutter-examples/vad-from-file/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme macOS scheme config
flutter-examples/vad-from-file/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist macOS project workspace checks metadata
flutter-examples/vad-from-file/macos/Flutter/Flutter-Release.xcconfig macOS Flutter release config
flutter-examples/vad-from-file/macos/Flutter/Flutter-Debug.xcconfig macOS Flutter debug config
flutter-examples/vad-from-file/macos/.gitignore Ignores macOS build artifacts
flutter-examples/vad-from-file/linux/runner/my_application.h Linux GTK runner declaration
flutter-examples/vad-from-file/linux/runner/my_application.cc Linux GTK runner implementation
flutter-examples/vad-from-file/linux/runner/main.cc Linux runner entrypoint
flutter-examples/vad-from-file/linux/runner/CMakeLists.txt Linux runner build
flutter-examples/vad-from-file/linux/flutter/CMakeLists.txt Flutter-provided Linux build glue
flutter-examples/vad-from-file/linux/CMakeLists.txt Project-level Linux build configuration
flutter-examples/vad-from-file/linux/.gitignore Ignores Linux ephemeral artifacts
flutter-examples/vad-from-file/lib/worker_stub.dart Native stub for web worker API surface
flutter-examples/vad-from-file/lib/web_blob_url_stub.dart Native stub for blob URL helper
flutter-examples/vad-from-file/lib/web_blob_url.dart Web helper to create blob URLs for playback
flutter-examples/vad-from-file/lib/web_audio_stub.dart Native stub for web audio helpers
flutter-examples/vad-from-file/lib/vad_controls.dart UI controls for VAD parameters
flutter-examples/vad-from-file/lib/save_file_stub.dart Web stub for native “save file” helpers
flutter-examples/vad-from-file/lib/save_file.dart Native save dialog + file writing utility
flutter-examples/vad-from-file/lib/resample.dart Linear resampler utility for web decode path
flutter-examples/vad-from-file/lib/play_bytes_stub.dart Web stub for playing WAV bytes
flutter-examples/vad-from-file/lib/play_bytes.dart Native helper to play WAV bytes via temp file
flutter-examples/vad-from-file/lib/model_web.dart Web model loading helpers (asset bytes + JS config)
flutter-examples/vad-from-file/lib/model_config.dart Centralized VAD model selection/config
flutter-examples/vad-from-file/lib/model.dart Native model asset copying + config path resolution
flutter-examples/vad-from-file/lib/cleanup_stub.dart Web stub for temp cleanup
flutter-examples/vad-from-file/lib/cleanup_native.dart Native temp WAV cleanup helper
flutter-examples/vad-from-file/lib/audio_decoder_web.dart Web Audio API decode pipeline to PCM
flutter-examples/vad-from-file/lib/audio_decoder_native.dart FFmpeg-based native decode pipeline to PCM
flutter-examples/vad-from-file/lib/audio_decoder.dart Stub API surface for conditional decoder implementations
flutter-examples/vad-from-file/ios/RunnerTests/RunnerTests.swift iOS test scaffold
flutter-examples/vad-from-file/ios/Runner/SceneDelegate.swift iOS scene delegate
flutter-examples/vad-from-file/ios/Runner/Runner-Bridging-Header.h iOS bridging header
flutter-examples/vad-from-file/ios/Runner/Info.plist iOS app metadata
flutter-examples/vad-from-file/ios/Runner/Base.lproj/Main.storyboard iOS main storyboard
flutter-examples/vad-from-file/ios/Runner/Base.lproj/LaunchScreen.storyboard iOS launch screen
flutter-examples/vad-from-file/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md iOS launch image notes
flutter-examples/vad-from-file/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json iOS launch image asset metadata
flutter-examples/vad-from-file/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json iOS app icon asset metadata
flutter-examples/vad-from-file/ios/Runner/AppDelegate.swift iOS app delegate + plugin registration
flutter-examples/vad-from-file/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings iOS workspace settings
flutter-examples/vad-from-file/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist iOS workspace checks metadata
flutter-examples/vad-from-file/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme iOS scheme config
flutter-examples/vad-from-file/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings iOS project workspace settings
flutter-examples/vad-from-file/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist iOS project workspace checks metadata
flutter-examples/vad-from-file/ios/Flutter/Release.xcconfig iOS Flutter release config
flutter-examples/vad-from-file/ios/Flutter/Debug.xcconfig iOS Flutter debug config
flutter-examples/vad-from-file/ios/Flutter/AppFrameworkInfo.plist iOS Flutter app framework metadata
flutter-examples/vad-from-file/ios/.gitignore Ignores iOS build artifacts
flutter-examples/vad-from-file/generate-asset-list.py Links asset list generation to shared script
flutter-examples/vad-from-file/android/settings.gradle.kts Android plugin management and versions
flutter-examples/vad-from-file/android/gradle/wrapper/gradle-wrapper.properties Gradle distribution pin
flutter-examples/vad-from-file/android/gradle.properties Android build JVM/AndroidX config
flutter-examples/vad-from-file/android/build.gradle.kts Android build directory wiring + repositories
flutter-examples/vad-from-file/android/app/src/profile/AndroidManifest.xml Android profile manifest (INTERNET for dev)
flutter-examples/vad-from-file/android/app/src/main/res/values/styles.xml Android launch/normal themes
flutter-examples/vad-from-file/android/app/src/main/res/values-night/styles.xml Android night mode themes
flutter-examples/vad-from-file/android/app/src/main/res/drawable/launch_background.xml Android splash background
flutter-examples/vad-from-file/android/app/src/main/res/drawable-v21/launch_background.xml Android v21 splash background
flutter-examples/vad-from-file/android/app/src/main/kotlin/com/example/vad_from_file/MainActivity.kt Android main activity
flutter-examples/vad-from-file/android/app/src/main/AndroidManifest.xml Android main manifest for file demo
flutter-examples/vad-from-file/android/app/src/debug/AndroidManifest.xml Android debug manifest (INTERNET for dev)
flutter-examples/vad-from-file/android/app/build.gradle.kts Android app Gradle config
flutter-examples/vad-from-file/android/.gitignore Ignores Android build artifacts/keystores
flutter-examples/vad-from-file/analysis_options.yaml Analyzer/lints configuration
flutter-examples/vad-from-file/README.md Build instructions and feature list
flutter-examples/vad-from-file/.metadata Flutter tool metadata
flutter-examples/vad-from-file/.gitignore Ignores Flutter build/tool artifacts
flutter-examples/tts/lib/web_audio_stub.dart Extends web audio stub API surface
flutter-examples/tts/lib/web_audio.dart Adds web “Save As” dialog helper for WAV bytes
.gitignore Adds ignore entry for swiftpm
.github/workflows/test-flutter.yaml Ad-hoc signs macOS app in CI before zipping artifacts
Files not reviewed (6)
  • flutter-examples/vad-from-file/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: Generated file
  • flutter-examples/vad-from-file/ios/Runner.xcworkspace/contents.xcworkspacedata: Generated file
  • flutter-examples/vad-from-file/macos/Runner.xcworkspace/contents.xcworkspacedata: Generated file
  • flutter-examples/vad-from-microphone/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: Generated file
  • flutter-examples/vad-from-microphone/ios/Runner.xcworkspace/contents.xcworkspacedata: Generated file
  • flutter-examples/vad-from-microphone/macos/Runner.xcworkspace/contents.xcworkspacedata: Generated file
Suppressed comments (6)

flutter-examples/vad-from-microphone/windows/runner/utils.cpp:1

  • freopen_s returns 0 on success, but the current condition treats non-zero (failure) as success; additionally stderr is duplicated from stdout rather than stderr. This causes console redirection to silently fail and can mis-route stderr output. Update the checks to test for success (== 0) and duplicate file descriptors using the actual stderr handle/fileno.
    flutter-examples/vad-from-microphone/windows/runner/utils.cpp:1
  • If WideCharToMultiByte fails it returns 0; subtracting 1 into an unsigned target_length underflows to a huge value, bypassing the target_length == 0 check and potentially causing large allocations. Compute the required size in a signed integer, check for 0 first, then subtract 1 after validating (and keep bounds checks consistent).
    flutter-examples/vad-from-microphone/windows/runner/flutter_window.cpp:1
  • flutter_controller_ can be null here (the switch executes regardless of the earlier if (flutter_controller_) block). On WM_FONTCHANGE this will dereference a null pointer. Guard this case with if (flutter_controller_ && flutter_controller_->engine()) or move the switch inside the if (flutter_controller_) block.
    flutter-examples/vad-from-microphone/lib/save_file.dart:1
  • readAsBytesSync() performs blocking IO on the UI isolate inside an async function, which can cause frame drops for larger files. Prefer the async variant (await File(sourcePath).readAsBytes()) to keep the UI responsive.
    flutter-examples/vad-from-microphone/windows/runner/win32_window.h:1
  • Correct typo: duplicated 'as' in the comment.
    flutter-examples/vad-from-microphone/lib/vad_controls.dart:1
  • package:flutter/services.dart is imported but not used in this file, which will trigger analyzer warnings and adds noise for readers. Remove the unused import (or, if the intent is to restrict input, apply an input formatter so the import is justified).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +37 to +42
Future<void> saveWavBytesWithDialog(Uint8List wavBytes, String filename) async {
globalContext['_sherpaSaveBytes'] = wavBytes.toJS;
globalContext['_sherpaSaveFilename'] = filename.toJS;

final eval = globalContext.getProperty('eval'.toJS) as JSFunction;
eval.callAsFunction(null, '''
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants