Sam Afshari's Notes
  • GitHub
  • Twitter/X
  • All posts
  • Ed
  • NuGets
  • POIWorld
  • RedCorners

Playing Sound Effects in Avalonia UI - Mon, Apr 10, 2023

If you’re looking for a way to develop cross-platform desktop-oriented applications, Avalonia UI might just be the framework for you. However, the documentation and third-party libraries can still be a bit immature. In this post, I’ll be sharing my experience developing an application that required playing audio files on different platforms, and how I managed to get it to work.

Goal

Our goal is to play audio files from a local file or a URL. To achieve this, the cross-platform code calls a function pointer that is defined separately on each platform as Func<string, Task>, where the input is the path/URL to the audio file. You can set this up with interfaces and dependency injection, but I chose to keep it old school.

Windows

Avalonia’s default template comes with one single Desktop project. However, to make platform-specific calls, we’ll need to fork that into two separate projects for Windows and macOS. To get started with Windows, change the TargetFramework of your Windows project to net7.0-windows10.0.17763.0. Note that you can’t target Windows 7, and at a minimum, you’ll need to target Windows 10 to get access to the APIs we’ll be using. Your csproj should look like this:

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net7.0-windows10.0.17763.0</TargetFramework>
    <Nullable>enable</Nullable>
    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
  </PropertyGroup>

Once you’ve made this change and reloaded your project, you’ll gain access to the Windows.Media namespaces, which allow you to easily play media files using the Windows.Media.Playback.MediaPlayer class:

var player = new MediaPlayer();
player.Source = MediaSource.CreateFromUri(new Uri("file://c:/media.m4a"));
player.Play();

MediaSource supports loading local files using file:// Uris or remote files.

macOS

On macOS, we can use the afplay command-line utility to play audio files. To do this, we can simply write a function that runs a new process for afplay [path] to play the sound effect we want:

new Process()
{
    StartInfo = new ProcessStartInfo
    {
        FileName = "/bin/bash",
        Arguments = $"-c \"afplay 'media.mp4'\"",
        RedirectStandardOutput = true,
        RedirectStandardInput = true,
        UseShellExecute = false,
        CreateNoWindow = true,
    }
}.Start();

This is exactly what the NetCoreAudio project does for its macOS implementation and in general on Unix.

If you want to download a remote file, you can use System.Net.Http.HttpClient to do that:

using var client = new HttpClient();
var bytes = await client.GetByteArrayAsync(url);
File.WriteAllBytes(localPath, bytes);

WebAssembly

Playing audio on the web can be done using an Audio object in JavaScript and calling the play() method on it. To call JavaScript functions from your C# code, you can use the JSImport attribute.

First, define a function in main.js that plays an audio file, for example:

globalThis.playSound = function (url) {
    var audio = new Audio(url);
    audio.play();
}

Then you can JSImport this as a C# method anywhere you want like this:

using System.Runtime.InteropServices.JavaScript;

...

[JSImport("globalThis.playSound")]
public static partial void PlaySound(string url);

Finally, call the PlaySound method in C# to run the globalThis.playSound JavaScript function:

PlaySound("http://sound.com/audio.mp4");

However, when dealing with a website, you might run into CORS (Cross-Origin Resource Sharing) issues. Make sure the remote host allows you to download the file you want. It’s always a good idea to look at your browser’s DevTools console for hints about any issues related to cross-site scripting errors.

Back to Home


© Sam Afshari 2024