Repository navigation
Add Flutter examples for VAD - #3856
Conversation
📝 WalkthroughWalkthroughThe 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. ChangesFlutter CI and release packaging
File-based VAD demo
Microphone-based VAD demo
Web VAD bindings
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winAwait the JavaScript promise, and avoid shared globals for the payload.
eval.callAsFunctionreturns the promise of the async IIFE. The Dart function does not await it. Two consequences follow:
- The returned
Futurecompletes before the file is written. The caller atflutter-examples/vad-from-file/lib/vad_screen.dart:332awaits 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
_sherpaSaveBytesand_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 winClose the temporary
AudioContextafter 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; afterdecodeAudioDataand channel extraction complete, close the temporary context. If a reusableAudioContextis 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 winTyped-data views lose their offset and length when passed to JavaScript. All three sites call
.buffer.toJSand dropoffsetInBytesandlengthInBytes. 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.dartline 363 creates window chunks withFloat32List.sublistView, so this pattern reaches the same API. Pass the offset and length to theFloat32Arrayconstructor, or copy the view first.
flutter/sherpa_onnx/lib/src/web/vad.dart#L39-L47: construct theFloat32Arraywithdata.buffer.toJS,data.offsetInBytes.toJS, anddata.length.toJSinCircularBuffer.push.flutter/sherpa_onnx/lib/src/web/vad.dart#L149-L157: apply the same three-argument construction inVoiceActivityDetector.acceptWaveform.flutter-examples/vad-from-file/lib/web_blob_url.dart#L7-L16: build the blob part frombytes.buffer.asUint8List(bytes.offsetInBytes, bytes.lengthInBytes).toJSinstead 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 liftCancel does not work on native.
The isolate handles one message at a time. The loop at lines 359-383 runs to completion inside the
_RunVadRequesthandler and blocks the isolate event loop. The_CancelRequestmessage sent bycancel()is only delivered after the run ends, socancelledstays false for the whole run. The UI resets to the idle state while the isolate keeps processing, then a stale_VadDonearrives.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
asyncfunction andawait 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 winReset
_hasSegmentListenerwhen the player controller is replaced.
_pickFiledisposes the old controller and creates a new one, but_hasSegmentListenerstaystruefrom the previous file._playSegmentat lines 307-310 then skipsaddListener(_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 winDeclare
audioplayerswith a compatible version constraint.
lib/play_bytes.dartandlib/play_bytes_stub.dartimportpackage: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 winValidate RIFF chunk bounds before reading chunk contents.
decodeWavpromises to returnnullfor invalid files. A shortfmtchunk can makeByteDatathrow. Adatachunk with a declared size beyond the input can makeUint8List.viewthrow. RIFF also requires one padding byte after an odd-sized chunk.Validate the complete padded chunk length before reading it. Reject
fmtchunks 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 winA
runVadmessage is dropped silently whensavedConfigis null.The guard
&& savedConfigmakes the handler skip the message when initialization has not completed or has failed. The worker posts noresultand noerror. 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 winThrottle the progress messages.
The loop posts one
progressmessage 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 winReturn the picker promise and skip downloads on user cancel.
eval.callAsFunctionreturns the async IIFE’sPromiseimmediately, so callers continue before the file write completes. Store that promise andawaitit via.toDart. In the picker block, catchAbortErrorand 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 winUse 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.releasefrom 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 winHandle
WideCharToMultiBytefailure before subtracting one.
WideCharToMultiBytereturns0on failure. The current code subtracts one from that value and stores it inunsigned int, which underflows to a very large size.std::string::resizecan 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 winApply the control values to the active Ten VAD configuration.
configToJs(config)serializes bothsileroVadandtenVad, andprepareModelConfig()fills Ten VAD fromdefaultVadConfig. This code only updatessileroVad; whenselectedModelIndex == 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 receivesthreshold,minSilenceDuration,minSpeechDuration, andmaxSpeechDuration.🤖 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 winRaise the Flutter minimum to match the web implementation.
flutter-examples/vad-from-microphone/pubspec.yamlallowsflutter: ">=2.8.1", but the web code depends on CI toolchains at3.44.8orlatestand modern Dart Web APIs. A Dart SDK 3.x / Flutter 2.8.1 build cannot compiledart:js_interop_unsafe,package:web, or the webAssetManifest.loadFromAssetBundlepath. 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 liftNative VAD detector disposal is not deterministic.
VadMicManager.disposeis synchronous and kills the isolate withIsolate.immediateright after it posts_DisposeRequest, so the isolate can terminate beforevad.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: makedispose()returnFuture<void>, wait for the isolate to process_DisposeRequest(or for an explicit acknowledgement) beforekill, useIsolate.beforeNextEvent, and close the main-sideReceivePortcreated 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 inState.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
decodeWavthrows on malformed input instead of returningnull. The doc comment at Line 14 promises anullreturn for an invalid file, but the parser trusts the declared chunk sizes and thefmtfield values without validation. Malformed or truncated input therefore raisesRangeErrororIntegerDivisionByZeroException.
flutter-examples/vad-from-microphone/lib/wav_encoder.dart#L35-L63: honour RIFF even-byte chunk padding when advancingoffset, verify that thefmtchunk holds at least 16 bytes before readingbitsPerSample, and clamp thedatachunk view to the remaining byte count.flutter-examples/vad-from-microphone/lib/wav_encoder.dart#L66-L93: validatenumChannels > 0andsampleRate > 0before the divisions in both decode branches, and returnnullotherwise.🤖 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 winSend
_SegmentCountChangedonly when the count changes.The
if (segmentCount > 0)check sits outside thewhileloop but inside the audio-chunk handler. After the first segment is detected, every later audio chunk sends a_SegmentCountChangedmessage with an unchanged value.vad_screen.dartLine 226 callssetStatefor 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 winPropagate the web initialization failure.
_initWebcatches the error, logs it, and returns normally._initNativerethrows at Line 324. Because of this difference,init()on web completes successfully after a worker failure._startListeninginvad_screen.dartLines 232-242 then treats the initialization as successful and starts the recorder.acceptWaveformdrops every chunk, because_statestaysuninitialized. 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 winFix the web playback stop path.
On web,
_togglePlaybacksets_isPlayingRecordingtotrueand callsweb_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 browserAudioelement created byweb_audio.dart.
web_audio.dartalready exportsstopPlayback(). Use it on the web path, and reset the flag after the estimated duration, as_playSegmentdoes 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
_playSegmentLines 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 winAdd
NSMicrophoneUsageDescription.This macOS target enables
com.apple.security.device.audio-input, butflutter-examples/vad-from-microphone/macos/Runner/Info.plistdoes not define the required microphone usage description. Add a user-facing string forPrivacy - Microphone Usage Descriptionso 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 winRaise the minimum Dart SDK constraint.
flutter-examples/vad-from-microphone/pubspec.yamldeclaressdk: ">=3.1.0 <4.0.0"whilerecord: ^6.2.0requires 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 to3.5.0or use a compatiblerecordversion.🤖 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 winDo not download the file after the user cancels the save dialog.
showSaveFilePickerrejects with anAbortErrorwhen the user cancels. Thecatchblock 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
AbortErrorfrom 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 winAdd a way to revoke the object URL.
URL.createObjectURLkeeps the whole blob alive until the URL is revoked or the tab closes.vad_screen.dartcreates 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 winGuard the other methods against use after
free().
free()sets_freed, butacceptWaveform,isEmpty,isDetected,pop,clear,front,reset, andflushdo not check it. A call afterfree()reaches a released JavaScript handle. The same gap exists inCircularBuffer.Add an early return or a
StateErrorwhen_freedis true, and setptr = nullinfree().🤖 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 winClose the main-isolate
ReceivePortindispose().
_initNativecreates aReceivePortat 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_initNativeafter 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 winDelete the output file on the FFmpeg failure paths.
If FFmpeg fails or writes a partial file, both early returns leave
outputPathon disk. Temp files then accumulate across runs. Use afinallyblock 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 winAlign the Min Speech Duration hint with the default value.
vad_screen.dartinitializes_minSpeechControllerwith'0.25', but this hint shows0.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 winGuard the RTF division against a zero duration.
Line 79 guards
_audioDuration > 0, but this widget does not. If_audioDurationis 0, the text showsNaNorInfinity.🐛 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 winSend only the requested sample view.
Line 98 sends the complete backing buffer. If
samplesis aFloat32Listview, 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 winRaise 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 winA cancelled run reports a normal
result.Line 209 breaks the loop when
_cancelledis set. The code then flushes and posts aresultmessage 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 valid0parameter with the default.
msg.threshold || 0.5evaluates the default whenever the value is0. The same applies tominSilenceDuration,minSpeechDuration, andmaxSpeechDuration. A user who setsminSpeechDurationto0to disable the minimum gets0.25instead. Use??so onlynullandundefinedselect 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
playWavBytesnever 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
stopPlaybackat 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 winCreate 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 thesecdcommands 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.onnxAlso 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 winCheck
mountedbeforesetState.
_loadDevicesawaitshasPermission()andlistInputDevices(). If the user leaves the screen while a permission prompt is open, the widget is disposed before the awaits resolve, and bothsetStatecalls throw. The refresh button at Line 399 also calls this method. Add amountedcheck 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 winReject a zero channel count.
Both decode branches divide by
numChannels. A malformedfmtchunk can report0, anddataBytes.length ~/ (2 * numChannels)then throwsIntegerDivisionByZeroException. The function contract states that it returnsnullfor invalid input. ValidatenumChannels > 0andsampleRate > 0before 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 winGuard the loop against an odd byte count.
The loop bound is
bytes.length, butvalueshas lengthbytes.length ~/ 2. If a stream chunk contains an odd number of bytes, the final iteration callsdata.getInt16(i)past the end of the view and writesvalues[bytes.length ~/ 2], which is out of range. Both operations throw inside the stream callback. Iterate overvalues.lengthinstead.🔧 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 winRevoke the object URL after playback.
playWavBytescallsURL.createObjectURL(blob)and never callsURL.revokeObjectURL. The browser retains the blob until the page reloads.vad_screen.dartLine 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
endedevent.🔧 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 winMake the
encodeWavstub delegate to the real encoder.
web_audio.dartLine 8 re-exports the realencodeWavfromwav_encoder.dart. This native stub returnsUint8List(0)instead. The two sides of the conditional import therefore have different behaviour for the same name. Any native call through theweb_audioalias produces an empty WAV file with no error.
vad_screen.dartcurrently resolvesencodeWavthrough its directwav_encoder.dartimport, 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 winReplace 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 themetadescription.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.
There was a problem hiding this comment.
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-microphoneandvad-from-fileFlutter 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_sreturns 0 on success, but the current condition treats non-zero (failure) as success; additionally stderr is duplicated fromstdoutrather thanstderr. 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 actualstderrhandle/fileno.
flutter-examples/vad-from-microphone/windows/runner/utils.cpp:1- If
WideCharToMultiBytefails it returns 0; subtracting 1 into an unsignedtarget_lengthunderflows to a huge value, bypassing thetarget_length == 0check 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 (theswitchexecutes regardless of the earlierif (flutter_controller_)block). OnWM_FONTCHANGEthis will dereference a null pointer. Guard this case withif (flutter_controller_ && flutter_controller_->engine())or move theswitchinside theif (flutter_controller_)block.
flutter-examples/vad-from-microphone/lib/save_file.dart:1readAsBytesSync()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.dartis 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.
| 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, ''' |
Please find pre-built Flutter APPs for
at https://github.com/k2-fsa/sherpa-onnx/releases/tag/flutter
Summary by CodeRabbit