一键导入
shiny-speech
Generate code using Shiny.Speech for cross-platform speech-to-text, text-to-speech, audio capture, and audio playback with pluggable cloud providers
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate code using Shiny.Speech for cross-platform speech-to-text, text-to-speech, audio capture, and audio playback with pluggable cloud providers
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Guide for implementing Firebase Cloud Messaging push notifications in .NET MAUI apps using Shiny.Push.FirebaseMessaging on iOS and Android.
Generate code using Shiny.DocumentDb.Firestore.Mobile, an on-device (native) Firebase Firestore provider for Shiny.DocumentDb on iOS and Android — offline-first persistence, real-time snapshot listeners, LINQ queries, and managed Firebase Auth identity.
Generate code using Shiny.Extensions.Push, a server-side push notification dispatch library for .NET with a provider-agnostic core and transports for APNs (.p8/ES256), FCM (HTTP v1), Web Push (VAPID + RFC 8291) and WNS (Windows App SDK / Entra auth), plus Shiny.DocumentDb persistence, structured targeting, topics, interceptors, dead-token pruning, multi-app keyed registration, runtime static/dynamic (multi-tenant) configuration via IPushConfigurationProvider, and System.Diagnostics.Metrics + tracing — fully AOT/trim-safe.
Shiny BluetoothLE client/central operations for scanning, connecting, and communicating with BLE peripherals
Generate code for Shiny.AiConversation - a centralized AI service library for .NET MAUI apps with chat client abstraction, wake word detection, speech-to-text/text-to-speech, acknowledgement modes (None/AudioBlip/LessWordy/Full), persistent message store, optional AI chat history lookup tool, and configurable sound effects
Generate code using Shiny Aspire integrations — Orleans ADO.NET hosting and Gluetun VPN container routing
| name | shiny-speech |
| description | Generate code using Shiny.Speech for cross-platform speech-to-text, text-to-speech, audio capture, and audio playback with pluggable cloud providers |
| auto_invoke | true |
| triggers | ["speech to text","text to speech","speech recognition","voice recognition","tts","stt","speak","dictation","transcribe","synthesize speech","audio capture","audio playback","microphone","ISpeechToTextService","ITextToSpeechService","IAudioSource","IAudioPlayer","IAudio","IAudioMonitor","IAudioDevices","AudioDevice","AudioMonitorOptions","microphone monitor","mic passthrough","live monitoring","PA","megaphone","talk over bluetooth speaker","audio route","output device","input device","device selection","AddAudioMonitor","AddAudioDevices","SetInputDevice","SetOutputDevice","InputLevelChanged","ISpeechToTextProvider","ITextToSpeechProvider","SpeechRecognitionResult","SpeechRecognitionOptions","SpeechRecognitionError","TextToSpeechOptions","VoiceInfo","AccessState","ResultReceived","KeywordHeard","StatementAfterKeyword","WaitListenForKeywords","ListenForKeywords","ListenUntilSilence","SpeakAsync","GetVoicesAsync","StartCaptureAsync","StopCaptureAsync","AudioProcessingOptions","noise suppression","background noise","echo cancellation","acoustic echo cancellation","AEC","automatic gain control","AGC","voice processing","barge-in","EchoCancellation","NoiseSuppression","AutomaticGainControl","AddSpeechServices","AddSpeechToText","AddTextToSpeech","AddAudioServices","AddAudioSource","AddAudioPlayer","AddCloudSpeechToText","AddCloudTextToSpeech","AddAzureSpeech","AddElevenLabsTextToSpeech","AddElevenLabsSpeechToText","AddElevenLabsSpeech","ElevenLabsSpeechToTextProvider","Scribe","scribe_v1","AzureSpeechConfig","ElevenLabsConfig","AddTypecastSpeech","AddTypecastTextToSpeech","Typecast","TypecastConfig","TypecastTextToSpeechProvider","CloudSpeechToText","CloudTextToSpeech","Shiny.Speech","Shiny.Audio","Shiny.Core","Shiny.Hosting.Maui","UseShiny","AndroidPlatform","Shiny.Speech.Cloud","Shiny.Speech.Azure","Shiny.Speech.ElevenLabs","Shiny.Speech.Typecast","PipeStream","IsListening","IsSpeaking","AudioLevelChanged","IsPlayerAnalysisSupported","VU meter","audio level","Volume","set volume","read volume","VolumeChanged","IsVolumeControlSupported","system volume","media volume","wake word","keyword detection","hey siri","voice activation","blazor speech","blazor wasm speech","browser speech","webassembly speech","web speech api","BrowserSpeechToTextService","BrowserTextToSpeechService","BrowserAudioPlayer","BrowserAudioSource","OperatingSystem.IsBrowser"] |
You are an expert in Shiny Speech, a library that provides cross-platform speech-to-text, text-to-speech, audio capture, and audio playback for .NET MAUI and Blazor WebAssembly with pluggable cloud providers.
Invoke this skill when the user wants to:
GitHub: https://github.com/shinyorg/speech NuGet Packages:
Shiny.Speech — Core library with platform-native STT, TTS, audio capture, and playbackShiny.Speech.Cloud — Cloud provider abstractionsShiny.Speech.Azure — Azure AI Speech providerShiny.Speech.ElevenLabs — ElevenLabs TTS providerShiny.Speech.Typecast — Typecast TTS provider (TTS only, via the typecast-csharp SDK)Namespace: Shiny.Speech
Shiny Speech provides:
ISpeechToTextService (iOS, Android, Windows, Browser/WASM)ResultReceived, KeywordHeard, Error events allow multiple subscribersStart() to begin listening, Stop() to end; Start() throws if already listeningKeywords in SpeechRecognitionOptions and subscribe to KeywordHeardITextToSpeechService (iOS, Android, Windows, Browser/WASM)IAudioSource (raw PCM 16kHz, 16-bit, mono — all platforms including browser)IAudioPlayer — play a Stream, or a remote URL / local file path via PlayAsync(string) (platform resolves the source natively; browser uses HTML5 Audio)IAudioMonitor — routes the mic to the current output in near-real-time (PA / "talk over a Bluetooth speaker"); Start/Stop, adjustable Gain, InputLevelChanged VU signal, AudioMonitorOptions (voice processing + preferred devices), SetInputDevice/SetOutputDevice. iOS/Mac Catalyst + Android onlyIAudioDevices — GetInputs/GetOutputs, CurrentInput/CurrentOutput, Changed event; normalized AudioDevice.Type. iOS/Mac Catalyst + Android onlyIAudio facade exposing Player / Source / Monitor / Devices — inject it to discover the whole audio surface (focused interfaces remain independently injectable)ISpeechToTextProvider and ITextToSpeechProviderListenUntilSilence, StatementAfterKeyword, WaitListenForKeywords, ListenForKeywordsAccessState and RequestAccess()AudioLevelChanged event on ITextToSpeechService and IAudioPlayer emits a normalized 0.0–1.0 RMS level during playback; IsPlayerAnalysisSupported reports per-platform availabilityFor platform-native speech only:
dotnet add package Shiny.Speech
For Azure AI Speech (cloud STT + TTS):
dotnet add package Shiny.Speech
dotnet add package Shiny.Speech.Azure
For ElevenLabs (cloud TTS):
dotnet add package Shiny.Speech
dotnet add package Shiny.Speech.ElevenLabs
Platform-native speech services:
builder
.UseMauiApp<App>()
.UseShiny(); // REQUIRED for native STT/audio — registers Shiny.Core's AndroidPlatform
builder.Services.AddSpeechServices(); // Registers STT, TTS, AudioSource, AudioPlayer
// On Browser/WASM: auto-detected via OperatingSystem.IsBrowser()
UseShiny()is mandatory for native speech/audio. Android runtime permission requests (RECORD_AUDIO) and current-activity tracking are delegated toShiny.Core'sAndroidPlatform. Reference theShiny.Hosting.Mauipackage and call.UseShiny()on theMauiAppBuilder(before building) soAndroidPlatformis registered and receivesOnRequestPermissionsResultcallbacks. Without it,RequestAccess()throws aTimeoutException("no current activity"). This replaced the library's old self-containedActivityProvider+PermissionRequestFragment.
Or register individually:
builder.Services.AddSpeechToText(); // ISpeechToTextService only
builder.Services.AddTextToSpeech(); // ITextToSpeechService only
builder.Services.AddAudioServices(); // IAudioSource + IAudioPlayer + IAudioMonitor + IAudioDevices + IAudio (from Shiny.Audio)
builder.Services.AddAudioSource(); // IAudioSource only
builder.Services.AddAudioPlayer(); // IAudioPlayer only
builder.Services.AddAudioMonitor(); // IAudioMonitor only (iOS/Mac Catalyst + Android)
builder.Services.AddAudioDevices(); // IAudioDevices only (iOS/Mac Catalyst + Android)
Namespace:
IAudioSource,IAudioPlayer, andPipeStreamlive in theShiny.Audionamespace (shipped in the standaloneShiny.Audiopackage, referenced byShiny.Speech). Addusing Shiny.Audio;when consuming them.AccessStatenow comes fromShiny.Coreand lives in theShinynamespace — becauseShinyis a parent namespace, code insideShiny.Audio/Shiny.Speechresolves it automatically; addusing Shiny;only where you reference it elsewhere. All the DI extension methods above are in theShinynamespace regardless of package.AddAudioServices()/AddAudioSource()/AddAudioPlayer()come fromShiny.Audioand can be used withoutShiny.Speechfor capture/playback-only scenarios (still require.UseShiny()on Android).
Azure AI Speech (replaces platform-native with cloud):
builder.Services.AddAzureSpeech("your-subscription-key", "eastus");
// Automatically registers IAudioSource and IAudioPlayer for platform audio I/O
Or with config object and selective services:
builder.Services.AddAzureSpeech(
new AzureSpeechConfig { SubscriptionKey = "key", Region = "eastus" },
speechToText: true,
textToSpeech: true
);
ElevenLabs (replaces platform-native STT/TTS with cloud — Scribe + TTS):
// Register both STT (Scribe) and TTS at once
builder.Services.AddElevenLabsSpeech("your-api-key");
// Or selectively
builder.Services.AddElevenLabsSpeechToText("your-api-key"); // Scribe STT only
builder.Services.AddElevenLabsTextToSpeech("your-api-key"); // TTS only
// Auto-registers IAudioSource and/or IAudioPlayer for platform audio I/O as needed
// With a config object — overrides default Scribe model / TTS model / voice
builder.Services.AddElevenLabsSpeech(new ElevenLabsConfig
{
ApiKey = "your-api-key",
SpeechToTextModel = "scribe_v1",
TextToSpeechModel = "eleven_multilingual_v2",
DefaultVoiceId = "21m00Tcm4TlvDq8ikWAM"
});
ElevenLabs Scribe is request/response, not streaming: results are yielded as a single final
SpeechRecognitionResultwhen the user callsStop()(the captured audio is buffered, wrapped in a WAV container, and posted to/v1/speech-to-text). For continuous partial results, use Azure instead.
Typecast (cloud TTS only — via the typecast-csharp SDK):
builder.Services.AddTypecastSpeech("your-typecast-api-key");
// AddTypecastTextToSpeech(...) is an identical alias. Registers ITextToSpeechService + IAudioPlayer.
// With a config object — model, default voice, language, emotion, audio format:
builder.Services.AddTypecastSpeech(new TypecastConfig
{
ApiKey = "your-typecast-api-key",
DefaultVoiceId = "<voice-id>", // required unless you pass TextToSpeechOptions.Voice per call
Model = Typecast.Models.TTSModel.SsfmV30,
AudioFormat = Typecast.Models.AudioFormat.Mp3
});
Typecast is TTS-only — there is no
AddTypecastSpeechToText; pair it with Azure/ElevenLabs/OpenAI or native STT if you need recognition. It has no fixed default voice: setTypecastConfig.DefaultVoiceIdor passTextToSpeechOptions.Voice, and callITextToSpeechService.GetVoicesAsync()to discover the voice ids available to your account.TypecastConfigalso exposes optionalLanguage,Emotion(+EmotionIntensity) hints.
Changing API keys / credentials at runtime:
All cloud provider config objects (AzureSpeechConfig, ElevenLabsConfig, OpenAiSpeechConfig, TypecastConfig) are mutable singletons. Register them normally, then change the key (or region/model/voice) at any time — the provider uses the new value on its next call, no re-registration needed. Keep a reference to the config you pass in, or resolve it from DI:
var config = new TypecastConfig { ApiKey = "initial" };
builder.Services.AddTypecastSpeech(config);
// ...later:
config.ApiKey = "rotated-key"; // via your retained reference
serviceProvider.GetRequiredService<AzureSpeechConfig>().SubscriptionKey = "new-key"; // or resolve from DI
Providers that cache an SDK/HTTP client (ElevenLabs, Typecast) rebuild it automatically when the key changes (via RefreshableClient<T> in Shiny.Speech.Cloud); Azure and OpenAI read the config on every call. Do not re-call AddXxxSpeech(...) to change a key — just mutate the config.
Android — Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
MODIFY_AUDIO_SETTINGS is required for the TTS audio-level Visualizer and for the native STT beep suppression.
iOS — Add to Info.plist:
<key>NSSpeechRecognitionUsageDescription</key>
<string>This app uses speech recognition</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone for speech recognition</string>
Browser (Blazor WebAssembly) — No manifest changes and no <script> tag needed. The browser prompts for microphone access automatically, and the JS interop module ships inside the Shiny.Audio package as a static web asset (_content/Shiny.Audio/shiny-audio.js), loaded on demand via JSHost.ImportAsync. Do not copy the JS into wwwroot or add a <script> reference — just reference the NuGet package.
Note:
IAudioSourcecaptures raw PCM audio in the browser using the Web Audio API (getUserMedia+ScriptProcessorNode), downsampled to 16kHz 16-bit mono — the same format as other platforms.
Always check permissions before using STT. The service uses a Start/Stop model with events.
public class MyViewModel(ISpeechToTextService stt)
{
async Task StartListening()
{
var access = await stt.RequestAccess();
if (access != AccessState.Available)
return;
// Subscribe to events (multiple subscribers allowed)
stt.ResultReceived += (s, result) =>
{
// result.Text — recognized text
// result.IsFinal — true when segment is finalized
// result.Confidence — optional confidence score (0-1)
};
stt.KeywordHeard += (s, keyword) =>
{
// keyword — the matched keyword string
};
stt.Error += (s, error) =>
{
// error.Message — error description
// error.Exception — optional exception
};
// Start listening (throws InvalidOperationException if already listening)
await stt.Start(new SpeechRecognitionOptions
{
Culture = CultureInfo.GetCultureInfo("en-US"),
SilenceTimeout = TimeSpan.FromSeconds(3),
PreferOnDevice = true,
Keywords = ["Yes", "No", "Maybe"] // optional keyword detection
});
}
async Task StopListening()
{
await stt.Stop(); // no-op if not listening
}
}
public class MyViewModel(ISpeechToTextService stt)
{
async Task SimpleDictation(CancellationToken ct)
{
// Listen until silence — starts, waits for first final result, stops
var text = await stt.ListenUntilSilence(
new SpeechRecognitionOptions
{
Culture = CultureInfo.GetCultureInfo("en-US"),
SilenceTimeout = TimeSpan.FromSeconds(3)
},
ct
);
}
async Task WakeWordActivation(CancellationToken ct)
{
// "Hey Computer, do something" → returns "do something"
// Waits for keyword, then captures next final statement
var command = await stt.StatementAfterKeyword(
["Hey Computer"],
cancellationToken: ct
);
}
async Task WaitForAnswer(CancellationToken ct)
{
// Wait for one specific keyword (with optional timeout)
var answer = await stt.WaitListenForKeywords(
["Yes", "No", "Maybe"],
timeout: TimeSpan.FromSeconds(30),
cancellationToken: ct
);
// Returns matched keyword or null on timeout
}
async Task ContinuousKeywords(CancellationToken ct)
{
// Stream keywords continuously as IAsyncEnumerable
await foreach (var keyword in stt.ListenForKeywords(
["Up", "Down", "Left", "Right"],
cancellationToken: ct))
{
Console.WriteLine($"Direction: {keyword}");
}
}
}
public class MyViewModel(ITextToSpeechService tts)
{
async Task Speak()
{
// Simple speech
await tts.SpeakAsync("Hello, world!");
// With options
await tts.SpeakAsync("Hello, world!", new TextToSpeechOptions
{
SpeechRate = 1.2f,
Pitch = 1.0f,
Volume = 0.8f,
Culture = CultureInfo.GetCultureInfo("en-US")
});
// List available voices
var voices = await tts.GetVoicesAsync();
var voice = voices.FirstOrDefault(v => v.Name.Contains("Neural"));
// Speak with specific voice
await tts.SpeakAsync("Hello!", new TextToSpeechOptions { Voice = voice });
// Stop speaking
if (tts.IsSpeaking)
await tts.StopAsync();
}
}
using Shiny.Audio; // IAudioSource, IAudioPlayer, PipeStream
using Shiny; // AccessState (from Shiny.Core)
public class MyViewModel(IAudioSource audioSource)
{
async Task CaptureAudio(CancellationToken ct)
{
// Returns raw PCM stream (16kHz, 16-bit, mono).
// NOTE: StartCaptureAsync(AudioProcessingOptions?, CancellationToken) — the first
// parameter is the processing options, so pass the token by name (or null first).
await using var stream = await audioSource.StartCaptureAsync(cancellationToken: ct);
// Read audio data from stream...
// Stream remains open until StopCaptureAsync is called
await audioSource.StopCaptureAsync();
}
}
Request platform voice-processing effects via AudioProcessingOptions to strip background noise and
cancel the device's own speaker/TTS output from the mic (barge-in) so it isn't re-captured:
using Shiny.Audio;
// All three effects (AEC + noise suppression + AGC)
var stream = await audioSource.StartCaptureAsync(AudioProcessingOptions.VoiceChat, ct);
// Or select individually
var stream2 = await audioSource.StartCaptureAsync(new AudioProcessingOptions
{
EchoCancellation = true, // subtracts speaker/TTS output — stops TTS bleeding into the mic
NoiseSuppression = true, // attenuates steady background noise
AutomaticGainControl = true // normalizes capture level
}, ct);
With cloud STT providers, set it on SpeechRecognitionOptions.AudioProcessing (they capture through
IAudioSource):
await stt.Start(new SpeechRecognitionOptions
{
Culture = CultureInfo.GetCultureInfo("en-US"),
AudioProcessing = AudioProcessingOptions.VoiceChat
});
Behavior notes when generating code:
AcousticEchoCanceler / NoiseSuppressor / AutomaticGainControl, gated on .IsAvailable; echo cancellation also routes capture through VoiceCommunication.Communications capture category (driver-provided AEC/NS when present).getUserMedia constraints (echoCancellation / noiseSuppression / autoGainControl); AEC uses WebRTC AEC3 and cancels page-rendered TTS.ISpeechToTextService manages its own mic and ignores AudioProcessing — the setting only affects IAudioSource capture (cloud providers, raw capture).IAudioPlayer.PlayAsync has two overloads: a Stream, or a string that is either a remote
http/https URL or a local file path. Pass a plain URL/path — each platform resolves it natively;
never construct a platform-specific file URI. Remote sources stream progressively on Android / Windows /
Browser and are buffered on Apple. In the browser a "local path" means an app-relative URL (no device
file system).
public class MyViewModel(IAudioPlayer audioPlayer)
{
Task PlayRemote(CancellationToken ct) => audioPlayer.PlayAsync("https://example.com/clip.mp3", ct);
Task PlayLocal(CancellationToken ct) => audioPlayer.PlayAsync(Path.Combine(FileSystem.AppDataDirectory, "chime.mp3"), ct);
async Task PlayStream(Stream mp3Stream, CancellationToken ct)
{
await audioPlayer.PlayAsync(mp3Stream, ct); // e.g. MP3
if (audioPlayer.IsPlaying)
await audioPlayer.StopAsync();
}
}
Subscribe to AudioLevelChanged on ITextToSpeechService (native + cloud TTS) or IAudioPlayer (generic audio playback). Each emitted value is a normalized RMS level in 0.0–1.0. Always gate UI on IsPlayerAnalysisSupported — it is false on Windows native TTS and Browser.
public partial class TtsViewModel(ITextToSpeechService tts) : ObservableObject
{
[ObservableProperty] double audioLevel; // bind to ProgressBar.Progress
public bool IsVuSupported => tts.IsPlayerAnalysisSupported;
public TtsViewModel(ITextToSpeechService tts) : this(tts)
=> tts.AudioLevelChanged += (_, level) =>
MainThread.BeginInvokeOnMainThread(() => AudioLevel = level);
}
Platform behaviour:
| Surface | iOS / macOS | Android | Windows | Browser |
|---|---|---|---|---|
Native TTS (ITextToSpeechService) | ✅ AVAudioEngine + player-node tap | ✅ OnAudioAvailable PCM RMS | ❌ | ❌ |
Cloud TTS (CloudTextToSpeech) | ✅ forwarded from IAudioPlayer | ✅ forwarded from IAudioPlayer | ❌ | ❌ |
Generic playback (IAudioPlayer) | ✅ AVAudioPlayer.MeteringEnabled | ✅ Visualizer on session | ❌ | ❌ |
Apple native TTS plays through AVAudioEngine + AVAudioPlayerNode so a tap on the player node can compute RMS. The engine is created lazily on first speak and kept warm — first utterance adds ~50–150 ms; subsequent utterances are indistinguishable. Reset AudioLevel to 0 on speak completion / StopAsync so the meter drains.
IAudio / IAudioMonitor / IAudioDevices)Inject the IAudio facade to reach the whole surface (Player / Source / Monitor / Devices),
or inject the focused interfaces directly. IAudioMonitor routes the mic to the current output live
(PA / "talk over a Bluetooth speaker"); IAudioDevices enumerates routes.
using Shiny.Audio;
using Shiny; // AccessState
public partial class MicViewModel(IAudio audio) : ObservableObject
{
[ObservableProperty] double level;
async Task Toggle()
{
if (audio.Monitor.IsMonitoring)
{
await audio.Monitor.Stop();
return;
}
if (await audio.Monitor.RequestAccess() != AccessState.Available)
return;
audio.Monitor.InputLevelChanged += (_, l) =>
MainThread.BeginInvokeOnMainThread(() => Level = l);
await audio.Monitor.Start(new AudioMonitorOptions
{
Gain = 1.0,
Processing = AudioProcessingOptions.VoiceChat, // AEC/NS/AGC — fights feedback
InputDevice = audio.Devices.CurrentInput
});
}
void ListOutputs()
{
foreach (var d in audio.Devices.GetOutputs()) // AudioDevice: Id, Name, Io, Type, IsCurrent
Console.WriteLine($"{d.Name} ({d.Type}){(d.IsCurrent ? " *" : "")}");
}
Task ChooseMic(AudioDevice mic) => audio.Monitor.SetInputDevice(mic); // live device switch
}
Behavior notes when generating code:
IAudioMonitor and IAudioDevices are implemented on iOS/Mac Catalyst and Android only. The IAudio facade throws PlatformNotSupportedException if Monitor/Devices are accessed elsewhere — guard by platform.AudioMonitorOptions.Processing null to route to a Bluetooth A2DP speaker (phone mic + BT output). Enabling processing (AEC) engages iOS's voice-processing unit, which forces Bluetooth onto the low-quality HFP profile — an A2DP-only speaker then drops back to the phone. Only enable processing for phone-speaker output where feedback is a problem.Gain, or enable AEC (accepting the Bluetooth trade-off). Always stop the monitor on page-disappear / app-background.IAudioMonitor.SetInputDevice/SetOutputDevice: Android enumerates and selects both input and output; iOS can select the input but treats output as observe-only (no app-level output enumeration/selection — AirPlay/Bluetooth output is owned by the system picker). Use IAudioDevices.CurrentInput/CurrentOutput (with .Type) as a display property everywhere.IAudioSource (pull-stream capture) and IAudioMonitor (live passthrough) are different tools — use the monitor for real-time mic→speaker, not a capture-stream-into-player loop.Implement ISpeechToTextProvider and/or ITextToSpeechProvider:
public class MyCloudSttProvider : ISpeechToTextProvider
{
// Required: surface non-fatal errors (e.g. a transient network blip between
// chunked requests in continuous mode) without aborting the IAsyncEnumerable.
// CloudSpeechToText subscribes to this and forwards to ISpeechToTextService.Error.
public event EventHandler<SpeechRecognitionError>? Error;
public async IAsyncEnumerable<SpeechRecognitionResult> RecognizeAsync(
Stream audioStream,
SpeechRecognitionOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Send audioStream to your cloud API
// Yield results as they arrive
try
{
yield return new SpeechRecognitionResult("Hello", IsFinal: true, Confidence: 0.95f);
}
catch (HttpRequestException ex)
{
// Non-fatal: signal the error and let the session keep running.
// Throwing instead would terminate the enumerator and end the session.
Error?.Invoke(this, new SpeechRecognitionError(ex.Message, ex));
}
}
}
// Register in DI (IAudioSource is auto-registered)
builder.Services.AddCloudSpeechToText<MyCloudSttProvider>();
RequestAccess() before STT operationsStart() to begin, Stop() to end; Start() throws if already listeningStart() to avoid missing resultsStop() to avoid leaksListenUntilSilence, StatementAfterKeyword, WaitListenForKeywords, ListenForKeywords handle Start/Stop/event wiring for youIAudioSource and IAudioPlayer implement IAsyncDisposableListenUntilSilence — For simple dictation scenariosStatementAfterKeyword — For "Hey Siri" style wake word activationWaitListenForKeywords — For yes/no/choice scenariosListenForKeywords — For continuous keyword detection as an async streamIAudioSource and IAudioPlayer as needed via TryAdd, so manual registration is no longer requiredAccessState — Check for NotSupported, Denied, and Restricted statesIsListening/IsSpeaking/IsPlaying — Check state before starting new listening/speech/playbackPreferOnDevice — Set to true for offline-capable STT when availableAddSpeechServices() uses OperatingSystem.IsBrowser() at runtime to register browser implementations; no conditional code needed in your appIAudioSource captures raw PCM via the Web Audio API (getUserMedia + ScriptProcessorNode), downsampled to 16kHz 16-bit monoshiny-audio.js ships as a static web asset inside the Shiny.Audio package (_content/Shiny.Audio/shiny-audio.js) and is imported automatically via JSHost.ImportAsync; never copy it into wwwroot or add a <script> tagPlayAndRecord with AllowBluetooth / AllowBluetoothA2dp / DefaultToSpeaker, so when CarPlay is active iOS automatically routes audio through the car's microphone and speakers — no CarPlay-specific code neededIsPlayerAnalysisSupported before showing meter UI; events do not fire on platforms where metering isn't available (Windows native TTS, Browser)AudioLevelChanged to the UI thread — the event fires from the audio render / synthesizer thread; use MainThread.BeginInvokeOnMainThread in MAUI or equivalent in Blazor before mutating bound propertiesAudioLevel back to 0 after SpeakAsync returns or StopAsync is called so the meter drains visuallyIAudioMonitor.Stop() on page-disappear and app-background; a live monitor left open is feedback and battery drainIAudioMonitor / IAudioDevices exist on iOS/Mac Catalyst + Android only; accessing them via the IAudio facade elsewhere throws PlatformNotSupportedExceptionIAudio facade for discovery — inject one IAudio to reach Player/Source/Monitor/Devices; inject the focused interface directly when a class only needs oneFor detailed API documentation, see:
reference/api-reference.md - Full API surface, interfaces, records, and configurationdotnet add package Shiny.Speech # Core platform-native speech services
dotnet add package Shiny.Speech.Cloud # Cloud provider abstractions (included by Azure/ElevenLabs)
dotnet add package Shiny.Speech.Azure # Azure AI Speech provider
dotnet add package Shiny.Speech.ElevenLabs # ElevenLabs TTS provider
dotnet add package Shiny.Speech.Typecast # Typecast TTS provider (TTS only)