Skip to content

Olbrasoft/NotificationAudio

Repository files navigation

NotificationAudio

.NET library for playing notification audio on Linux with priority-based fallback between PipeWire, PulseAudio, and FFmpeg.

Features

  • Stream-First Design - Optimized for ephemeral TTS audio data
  • Priority-Based Fallback - Automatically selects best available audio backend:
    1. PipeWire (pw-play) - Modern, low-latency (Priority 1)
    2. PulseAudio (paplay) - Widely supported (Priority 2)
    3. FFmpeg (ffplay) - Universal fallback (Priority 3)
  • Audio Sink Selection - Route notifications to specific output device
  • Dependency Injection - Easy setup with AddNotificationAudio()
  • Async/Await - Non-blocking audio playback
  • GUID-Based Temp Files - Safe concurrent playback

Installation

Install via NuGet:

dotnet add package Olbrasoft.NotificationAudio.Providers.Linux

This package includes all dependencies:

  • Olbrasoft.NotificationAudio.Abstractions
  • Olbrasoft.NotificationAudio.Core

Quick Start

1. Register Services

using Olbrasoft.NotificationAudio.Providers.Linux;

var builder = WebApplication.CreateBuilder(args);

// Register all NotificationAudio services
builder.Services.AddNotificationAudio();

var app = builder.Build();

2. Play Audio from Stream (TTS)

using Olbrasoft.NotificationAudio.Abstractions;

public class NotificationService
{
    private readonly INotificationPlayer _player;

    public NotificationService(INotificationPlayer player)
    {
        _player = player;
    }

    public async Task SpeakAsync(Stream audioStream)
    {
        // Play MP3 audio from stream
        await _player.PlayAsync(audioStream, ".mp3");
    }
}

3. Play Audio from File

public async Task PlayNotificationAsync(string filePath)
{
    await _player.PlayAsync(filePath);
}

Configuration

Audio Sink Selection

Route notifications to specific output device via appsettings.json:

{
  "NotificationAudio": {
    "AudioSink": "49"
  }
}

Get available sinks:

# PipeWire
pw-cli list-objects | grep -A 5 "node.name"

# PulseAudio
pactl list sinks | grep -E "Sink #|Name:"

If not configured, uses system default output.

Architecture

Project Structure

NotificationAudio/
├── src/
│   ├── NotificationAudio.Abstractions/    # Interfaces
│   ├── NotificationAudio.Core/            # Core implementation
│   └── NotificationAudio.Providers.Linux/ # Linux audio providers + DI
└── tests/
    ├── NotificationAudio.Core.Tests/
    └── NotificationAudio.Providers.Linux.Tests/

Key Interfaces

INotificationPlayer

Main API for audio playback.

public interface INotificationPlayer
{
    Task PlayAsync(Stream audioStream, string fileExtension = ".mp3", CancellationToken cancellationToken = default);
    Task PlayAsync(string filePath, CancellationToken cancellationToken = default);
    void Stop();
}

IPlaybackProvider

Abstraction for audio backends (pw-play, paplay, ffplay).

public interface IPlaybackProvider
{
    string Name { get; }
    int Priority { get; }
    bool IsAvailable();
    Task PlayAsync(string filePath, string? audioSink, CancellationToken cancellationToken);
}

IAudioSinkSelector

Determines target output device from configuration.

public interface IAudioSinkSelector
{
    string? GetNotificationSink();
}

How It Works

  1. Provider Selection

    • NotificationPlayer queries all registered IPlaybackProvider instances
    • Sorted by Priority (1 = highest)
    • First available provider is selected via IsAvailable()
  2. Stream Handling

    • Stream → GUID-based temp file in /tmp/
    • Pass temp file path to provider
    • Delete temp file after playback (even on errors)
  3. Audio Sink Routing

    • LinuxAudioSinkSelector reads NotificationAudio:AudioSink from configuration
    • Each provider uses sink-specific command-line syntax:
      • PipeWire: pw-play --target <sink> <file>
      • PulseAudio: paplay --device=<sink> <file>
      • FFmpeg: PULSE_SINK=<sink> ffplay -nodisp -autoexit <file>

Use Cases

Text-to-Speech (TTS)

Perfect for playing ephemeral TTS audio:

public class TtsService
{
    private readonly INotificationPlayer _player;
    private readonly ITtsEngine _tts;

    public async Task SpeakAsync(string text)
    {
        using var audioStream = await _tts.SynthesizeAsync(text);
        await _player.PlayAsync(audioStream, ".mp3");
        // Stream automatically disposed, temp file cleaned up
    }
}

Desktop Notifications

Combine with system notifications:

public class NotificationManager
{
    private readonly INotificationPlayer _player;

    public async Task NotifyAsync(string message, string soundFile)
    {
        // Show visual notification
        ShowToast(message);

        // Play notification sound
        await _player.PlayAsync(soundFile);
    }
}

Requirements

Runtime Dependencies

At least one of the following must be installed:

  • PipeWire (Recommended): sudo apt install pipewire pipewire-pulse
  • PulseAudio: sudo apt install pulseaudio pulseaudio-utils
  • FFmpeg: sudo apt install ffmpeg

.NET Version

  • .NET 10.0 or higher

Testing

Run all tests:

dotnet test

Test coverage:

  • 43 tests total
  • 11 tests for Core (NotificationPlayer)
  • 32 tests for Providers.Linux (3 providers + LinuxAudioSinkSelector + DI)

Troubleshooting

Issue Cause Solution
InvalidOperationException: No audio playback provider found No audio backends installed Install pw-play, paplay, or ffplay
Audio plays on wrong device Incorrect sink ID Check pw-cli list-objects or pactl list sinks
FileNotFoundException File path doesn't exist Verify file path is correct

CI/CD

Versioning Strategy

Auto-increment versioning: 1.0.${{ github.run_number }}

Example versions: 1.0.11, 1.0.12, 1.0.13, ...

NuGet Packages

Three packages are published on every push to main (when tests pass):

  • Olbrasoft.NotificationAudio.Abstractions
  • Olbrasoft.NotificationAudio.Core
  • Olbrasoft.NotificationAudio.Providers.Linux

License

MIT License - see LICENSE file for details.

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Links

About

C# library for playing audio notifications on Linux/Windows with configurable output device routing

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages