Skip to content
Shiny.NET
Commercial SupportGuaranteed response times on Shiny bug fixes, straight from the maintainers. Plans from US$1,500 a year.See the plans

Audio

NuGet package Shiny.AppDeviceBridge.Audio

The audio bridge gives the page the device’s speakers and microphone: play clips, read and set the system volume, record the microphone to a WAV file, list the audio routes, and route the microphone live to the output (a PA). Level meters for all three arrive as events.

Terminal window
dotnet add package Shiny.AppDeviceBridge.Audio
builder.UseAppDeviceBridge(
bridge => bridge.AddAudioBridge(o => o.MaxRecordingDuration = TimeSpan.FromMinutes(10)),
webApp => { /* … */ }
);
Client C# TypeScript
IAudioBridge AddAudioBridgeClient() AudioBridge

Built on Shiny.Audio 3.0, with Shiny.Audio.Linux on Linux.

Topic Details
Playing POST playback takes exactly one of an absolute http/https url or a file in a file root, and answers once the clip plays, with its id. Clips play together, at most 32 at once (409 too_many_clips). DELETE playback/{id} stops one and DELETE playback stops all. audio.playback.ended says whether it played out, was stopped, or failed.
Volume GET status reads the system media volume and PUT volume sets it. iOS and Mac Catalyst can only read it, so setting it answers 501 (volumeControlSupported is false). audio.volume fires for the hardware buttons too.
Recording POST recording records 16 kHz mono WAV, optionally through an effect preset (Robot, Chipmunk, DeepVoice, Cathedral, Telephone, Megaphone, Ensemble) and the platform’s voice processing. mode: "Both" keeps the clean take too. DELETE recording files it into the data root under audio-recordings/ as REC_yyyyMMdd-HHmmss.wav, read through the files bridge. POST recording/cancel keeps nothing.
Time limit A recording runs for at most MaxRecordingDuration (one hour by default; null for none), and less if the page asks with maxDurationSeconds. When it runs out, the bridge stops it, files it and raises audio.recording.ended with MaxDurationReached.
Routes GET devices lists inputs and outputs and which are in use. Pass an input’s id as inputDeviceId. POST devices/picker shows the system output picker (AirPlay and Bluetooth on iOS). audio.devices fires when a route comes or goes.
Monitor POST monitor routes the microphone straight to the output, with live gain (PUT monitor). It needs a listener on audio.monitor.level to start (409 not_listening) and stops once the last one is gone, so a page that went away can’t leave the microphone on the speaker. Use headphones or echo cancellation: a speaker feeds back.
One microphone A recording and the monitor can’t run at once; the second gets 409 microphone_busy, as it does while the native app or the speech bridge holds the microphone.
Platforms Playback, recording and routes: iOS, Mac Catalyst, macOS, Android, Windows and Linux. Monitor: iOS, Mac Catalyst, macOS, Android and Linux; Windows answers 501. Anything a platform lacks answers 501, and GET status says which parts are there. To bring your own implementations, pass o => o.RegisterAudioServices = false and register IAudioPlayer, IAudioRecorder, IAudioDevices or IAudioMonitor.
Platform setup Android needs RECORD_AUDIO. Apple platforms need NSMicrophoneUsageDescription, plus the com.apple.security.device.audio-input entitlement for sandboxed apps. Linux needs PulseAudio, PipeWire or ALSA.
import { AudioBridge } from "@shinyorg/appdevicebridge";
const audio = new AudioBridge();
const clip = await audio.play({ url: "https://example.com/chime.mp3" });
await audio.onPlaybackEnded(e => e.id === clip.id && console.log("done"));
await audio.requestAccess();
await audio.onRecordingLevel(e => meter.value = e.level);
await audio.startRecording({ effect: "Robot", maxDurationSeconds: 30 });
const recording = await audio.stopRecording();
await audio.play({ file: recording.file });
// The monitor runs while something listens to its level.
const stop = await audio.onMonitorLevel(e => meter.value = e.level);
await audio.startMonitor({ gain: 0.8, processing: { echoCancellation: true } });
GET/_bridge/audio/status#

What is supported, and what is running.

C#IAudioBridge.GetStatusAsyncTSAudioBridge.getStatus
Response
200 AudioStatus
AudioStatus · 12 fields
playbackSupportedbooleanClips can be played.
recordingSupportedbooleanThe microphone can be recorded.
devicesSupportedbooleanRoutes can be listed.
monitorSupportedbooleanThe microphone can be routed live to the output.
volumeControlSupportedbooleanThe volume can be set. False on iOS and Mac Catalyst, which only read it.
playbackLevelsSupportedbooleanaudio.playback.level is raised.
volumenumber | nullThe system media volume, 0–1, where playback is supported.
playingnumberHow many clips are playing.
recordingbooleanWhether a recording is running.
recordingElapsedSecondsnumber | nullHow much has been recorded, while recording.
monitoringbooleanWhether the monitor is running.
maxRecordingSecondsnumber | nullThe longest a recording may run; a longer request is shortened to it.
POST/_bridge/audio/access#

Requests microphone permission.

C#IAudioBridge.RequestAccessAsyncTSAudioBridge.requestAccess
Response
200 AudioAccessResult
AudioAccessResult · 1 field
accessAccessState
GET/_bridge/audio/playback#

The clips playing, oldest first.

C#IAudioBridge.GetPlaybackAsyncTSAudioBridge.getPlayback
Response
200 AudioPlayback[]
AudioPlayback · 3 fields
idstring
urlstring | null
fileBridgeFile | null
POST/_bridge/audio/playback#

Starts a clip and answers once it is playing. Clips play together: starting one does not stop another. onPlaybackEnded says when it ends.

C#IAudioBridge.PlayAsyncTSAudioBridge.play
Body
application/json · AudioPlayRequest
AudioPlayRequest · 2 fields
urlstring | nullFetched by the device, not the page.
fileBridgeFile | nullA file the page wrote, or a recording.
Response
200 AudioPlayback
AudioPlayback · 3 fields
idstring
urlstring | null
fileBridgeFile | null
DELETE/_bridge/audio/playback/{id}#

Stops one clip. Fails with 404 when it is not playing.

C#IAudioBridge.StopPlaybackAsyncTSAudioBridge.stopPlayback
ParameterInType
idpathstringrequired
Response
204 No content
Errors
404Not found
DELETE/_bridge/audio/playback#

Stops every clip.

C#IAudioBridge.StopAllPlaybackAsyncTSAudioBridge.stopAllPlayback
Response
204 No content
PUT/_bridge/audio/volume#

Sets the system media volume. Fails with 501 where it can only be read.

C#IAudioBridge.SetVolumeAsyncTSAudioBridge.setVolume
Body
application/json · AudioVolumeRequest
AudioVolumeRequest · 1 field
volumenumber0–1.
Response
200 AudioVolume
AudioVolume · 1 field
volumenumberThe system media volume, 0–1. Android's is stepped, so it may differ slightly from what was set.
Errors
501Not on this platform
POST/_bridge/audio/recording#

Starts recording the microphone. Fails with 409 microphone_busy when it is in use.

C#IAudioBridge.StartRecordingAsyncTSAudioBridge.startRecording
Body
application/json · AudioRecordingRequest
AudioRecordingRequest · 5 fields
modeAudioRecordModeWhich signal to keep.
effectAudioEffectPreset | nullApplied as it is recorded. Ignored for Dry.
inputDeviceIdstring | nullAn input's id; the system default when null.
processingAudioProcessing | nullThe platform's voice processing; raw input when null.
maxDurationSecondsnumber | nullStop and file it after this long. Never longer than maxRecordingSeconds.
Response
200 AudioStatus
AudioStatus · 12 fields
playbackSupportedbooleanClips can be played.
recordingSupportedbooleanThe microphone can be recorded.
devicesSupportedbooleanRoutes can be listed.
monitorSupportedbooleanThe microphone can be routed live to the output.
volumeControlSupportedbooleanThe volume can be set. False on iOS and Mac Catalyst, which only read it.
playbackLevelsSupportedbooleanaudio.playback.level is raised.
volumenumber | nullThe system media volume, 0–1, where playback is supported.
playingnumberHow many clips are playing.
recordingbooleanWhether a recording is running.
recordingElapsedSecondsnumber | nullHow much has been recorded, while recording.
monitoringbooleanWhether the monitor is running.
maxRecordingSecondsnumber | nullThe longest a recording may run; a longer request is shortened to it.
Errors
409Conflict
DELETE/_bridge/audio/recording#

Stops recording and files it. Fails with 409 not_recording when nothing is recording.

C#IAudioBridge.StopRecordingAsyncTSAudioBridge.stopRecording
Response
200 AudioRecording
AudioRecording · 7 fields
fileBridgeFileThe recording — the processed take unless it was recorded Dry.
dryFileBridgeFile | nullThe unprocessed take, for Both.
durationSecondsnumber
sampleRatenumber
channelsnumber
sizenumberBytes in file.
mimeTypestring
Errors
409Conflict
POST/_bridge/audio/recording/cancel#

Ends the recording and keeps nothing.

C#IAudioBridge.CancelRecordingAsyncTSAudioBridge.cancelRecording
Response
204 No content
POST/_bridge/audio/devices/picker#

Shows the system's output picker (AirPlay and Bluetooth on iOS). Does nothing where there is none.

C#IAudioBridge.ShowOutputPickerAsyncTSAudioBridge.showOutputPicker
Response
204 No content
POST/_bridge/audio/monitor#

Routes the microphone live to the output — a PA. Listen to onMonitorLevel first: without a listener it fails with 409 not_listening, and it stops once the last listener is gone, so a page that went away cannot leave the microphone on the speaker.

C#IAudioBridge.StartMonitorAsyncTSAudioBridge.startMonitor
Body
application/json · AudioMonitorRequest
AudioMonitorRequest · 5 fields
gainnumber | null0–1; 1 when null.
duckOtherAudiobooleanLower other audio while monitoring rather than interrupting it.
inputDeviceIdstring | nullAn input's id; the system default when null.
outputDeviceIdstring | nullAn output's id; the system default when null. Best effort: on iOS only the speaker and the receiver can be chosen, and Bluetooth or AirPlay is picked through showOutputPicker.
processingAudioProcessing | nullEcho cancellation helps against feedback.
Response
200 AudioMonitor
AudioMonitor · 1 field
gainnumber
Errors
409Conflict
PUT/_bridge/audio/monitor#

Changes the monitor's gain while it runs. Fails with 409 not_monitoring when it does not.

C#IAudioBridge.SetMonitorGainAsyncTSAudioBridge.setMonitorGain
Body
application/json · AudioMonitorGainRequest
AudioMonitorGainRequest · 1 field
gainnumber0–1.
Response
200 AudioMonitor
AudioMonitor · 1 field
gainnumber
Errors
409Conflict
DELETE/_bridge/audio/monitor#

Stops the monitor.

C#IAudioBridge.StopMonitorAsyncTSAudioBridge.stopMonitor
Response
204 No content
EVENTaudio.playback.ended#

A clip ended — played out, stopped or failed.

C#IAudioBridge.OnPlaybackEndedAsyncTSAudioBridge.onPlaybackEnded
Payload
AudioPlaybackEnded
idstring
stoppedbooleanTrue when it was stopped through the bridge rather than played out.
errorstring | nullWhat went wrong, when playback failed.
EVENTaudio.playback.level#

The output level about 20 times a second while something plays, where playbackLevelsSupported.

C#IAudioBridge.OnPlaybackLevelAsyncTSAudioBridge.onPlaybackLevel
Payload
AudioLevelChanged
levelnumber0 (silence) – 1, on the same perceptual scale for every meter.
EVENTaudio.volume#

The volume changed — the hardware buttons, the system's own control, or setVolume.

C#IAudioBridge.OnVolumeAsyncTSAudioBridge.onVolume
Payload
AudioVolume
volumenumberThe system media volume, 0–1. Android's is stepped, so it may differ slightly from what was set.
EVENTaudio.recording.level#

The level of what is being written, effect included, about 20 times a second.

C#IAudioBridge.OnRecordingLevelAsyncTSAudioBridge.onRecordingLevel
Payload
AudioLevelChanged
levelnumber0 (silence) – 1, on the same perceptual scale for every meter.
EVENTaudio.monitor.level#

The microphone's level while monitoring.

C#IAudioBridge.OnMonitorLevelAsyncTSAudioBridge.onMonitorLevel
Payload
AudioLevelChanged
levelnumber0 (silence) – 1, on the same perceptual scale for every meter.

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

AudioStatusobject#

What this device can do, and what is running.

playbackSupportedbooleanClips can be played.
recordingSupportedbooleanThe microphone can be recorded.
devicesSupportedbooleanRoutes can be listed.
monitorSupportedbooleanThe microphone can be routed live to the output.
volumeControlSupportedbooleanThe volume can be set. False on iOS and Mac Catalyst, which only read it.
playbackLevelsSupportedbooleanaudio.playback.level is raised.
volumenumber | nullThe system media volume, 0–1, where playback is supported.
playingnumberHow many clips are playing.
recordingbooleanWhether a recording is running.
recordingElapsedSecondsnumber | nullHow much has been recorded, while recording.
monitoringbooleanWhether the monitor is running.
maxRecordingSecondsnumber | nullThe longest a recording may run; a longer request is shortened to it.
AccessStateenum#

Where a permission stands. Reported by every bridge that needs one; the names match Shiny's own.

"Unknown""NotSupported""NotSetup""Disabled""Restricted""Denied""Available"
AudioPlaybackobject#

A clip that is playing.

idstring
urlstring | null
fileBridgeFile | null
BridgeFileobject#

A file the page can reach through the files bridge: a named root and a path inside it. Bridges that take or return a file use this, so nothing reaches the page that /_bridge/files would not also serve.

rootstring
pathstring
AudioPlayRequestobject#

What to play: exactly one of an absolute http/https URL or a file in a file root.

url?string | nullFetched by the device, not the page.
file?BridgeFile | nullA file the page wrote, or a recording.
AudioVolumeRequestobject#
volumenumber0–1.
AudioVolumeobject#
volumenumberThe system media volume, 0–1. Android's is stepped, so it may differ slightly from what was set.
AudioRecordingRequestobject#

A recording to start. Every field is optional.

mode?AudioRecordModeWhich signal to keep.
effect?AudioEffectPreset | nullApplied as it is recorded. Ignored for Dry.
inputDeviceId?string | nullAn input's id; the system default when null.
processing?AudioProcessing | nullThe platform's voice processing; raw input when null.
maxDurationSeconds?number | nullStop and file it after this long. Never longer than maxRecordingSeconds.
AudioRecordModeenum#

Which version of the signal a recording keeps.

"Wet""Dry""Both"
AudioEffectPresetenum#

A ready-made effect applied to a recording as it is made.

"Robot""Chipmunk""DeepVoice""Cathedral""Telephone""Megaphone""Ensemble"
AudioProcessingobject#

The platform's voice processing, best effort and device dependent. Apple's voice-processing unit cannot switch them one at a time, so any of them turns on all three there.

echoCancellation?boolean
noiseSuppression?boolean
automaticGainControl?boolean
AudioRecordingobject#

A recording, filed into a file root as 16 kHz mono 16-bit WAV and read through the files bridge.

fileBridgeFileThe recording — the processed take unless it was recorded Dry.
dryFileBridgeFile | nullThe unprocessed take, for Both.
durationSecondsnumber
sampleRatenumber
channelsnumber
sizenumberBytes in file.
mimeTypestring
AudioDeviceobject#

An input or output route.

idstringPass it as inputDeviceId or inputDeviceId.
namestring
ioAudioDeviceIo
typeAudioDeviceType
isCurrentbooleanThe route in use.
AudioDeviceIoenum#
"Input""Output"
AudioDeviceTypeenum#

What kind of route a device is, normalized across platforms.

"Unknown""BuiltInMic""BuiltInSpeaker""BuiltInReceiver""WiredHeadset""WiredHeadphones""Bluetooth""BluetoothA2dp""Usb""CarAudio""Hdmi""AirPlay"
AudioMonitorRequestobject#

The monitor to start. Every field is optional.

gain?number | null0–1; 1 when null.
duckOtherAudio?booleanLower other audio while monitoring rather than interrupting it.
inputDeviceId?string | nullAn input's id; the system default when null.
outputDeviceId?string | nullAn output's id; the system default when null. Best effort: on iOS only the speaker and the receiver can be chosen, and Bluetooth or AirPlay is picked through showOutputPicker.
processing?AudioProcessing | nullEcho cancellation helps against feedback.
AudioMonitorobject#

The running monitor.

gainnumber
AudioMonitorGainRequestobject#
gainnumber0–1.
AudioPlaybackEndedobject#

A clip ended.

idstring
stoppedbooleanTrue when it was stopped through the bridge rather than played out.
errorstring | nullWhat went wrong, when playback failed.
AudioLevelChangedobject#
levelnumber0 (silence) – 1, on the same perceptual scale for every meter.
AudioRecordingEndReasonenum#

Why a recording ended.

"Stopped""Cancelled""MaxDurationReached""Failed"
AudioMonitorEndReasonenum#

Why the monitor stopped.

"Stopped""PageClosed""Error"

These routes sit under AppDeviceBridgePolicies.Bridges like every other bridge: callers on this device by default. A url is fetched by the device rather than the page, so CORS doesn’t apply to it, but only the sound comes back, never the content. A file must be in one of the page’s own file roots. See Security.