Playing Embedded Audio Files in Xamarin.Forms

“How hard would it be to make the app play this ‘dinging’ sound whenever the user selects the control?” the product owner asked.

“Should be pretty straight-forward,” I responded like a newb. “Not hard at all.”

Well, as it turns out, playing audio from within a Xamarin.Forms project isn’t as straight-forward as I had assumed. However, I was correct that it isn’t very hard to do.

It all boils down to a shared Interface, platform specific code implemented in each project file, and leveraging the DependencyService.

The Interface

IAudioService.cs

using System.Threading.Tasks;
namespace WSAudioApp.Interfaces
{
    public interface IAudioService
    {
        Task PlayAudioFile(string fileName);
    }
}

The Platform Code

The first key to making this work is that each implementation of the interface is registered with the DependencyService by applying this Dependency metadata attribute to the class:

[assembly: Dependency(typeof(AudioServiceImplementation))]

Everything else is just platform specific code and procedures. I emphasize the procedures here because the each platform presents a specific ‘gotcha’ or two before this will all work.

Android

AudioServiceImplementation.cs

using WSAudioApp.Interfaces;
using WSAudioApp.Droid.Implementations;
using Android.Media;
using System.Threading.Tasks;
using Xamarin.Forms;
[assembly: Dependency(typeof(AudioServiceImplementation))]
namespace WSAudioApp.Droid.Implementations
{
    public class AudioServiceImplementation : IAudioService
    {
        private MediaPlayer _mediaPlayer;
        public async Task PlayAudioFile(string fileName)
        {
            _mediaPlayer = new MediaPlayer();
            var fd = global::Android.App.Application.Context.Assets.OpenFd(fileName);
            _mediaPlayer.Prepared += (s, e) =>
            {
                _mediaPlayer.Start();
            };
            await _mediaPlayer.SetDataSourceAsync(fd.FileDescriptor, fd.StartOffset, fd.Length);
            _mediaPlayer.Prepare();
        }
    }
}

The Android implementation leverages the MediaPlayer class, and it pulls the files from the app’s Assets.

Gotchas

  • The fileName property must contain the name and extension, it must exactly match the file you want to play, and it is case-sensitive.
  • These files must be located in the Android project’s Assets folder, and they must have the Build Action property set to AndroidAsset or your app will crash at runtime.

Android BuildAction

iOS

AudioServiceImplementation.cs

using Xamarin.Forms;
using WSAudioApp.iOS.Implementations;
using WSAudioApp.Interfaces;
using System.Threading.Tasks;
using AVFoundation;
using Foundation;
using System.IO;
[assembly: Dependency(typeof(AudioServiceImplementation))]
namespace WSAudioApp.iOS.Implementations
{
    public class AudioServiceImplementation : IAudioService
    {
        private AVAudioPlayer _mediaPlayer;
        public async Task PlayAudioFile(string fileName)
        {
            string sFilePath = NSBundle.MainBundle.PathForResource(Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName));
            var url = NSUrl.FromString(sFilePath);
            var _mediaPlayer = AVAudioPlayer.FromUrl(url);
            _mediaPlayer.FinishedPlaying += (object sender, AVStatusEventArgs e) => {
                _mediaPlayer = null;
            };
            _mediaPlayer.Play();
        }
    }
}

The iOS implementation leverages the AVAudioPlayer class and pulls the files from the app’s MainBundle. Note that this method will generate a warning in its current state due to the lack of any awaitkeywords, but it will build.

Gotchas

  • The fileName property must contain the name and extension, it must exactly match the file you want to play, and it is case-sensitive.
  • These files must be located in the iOS project’s Resources folder, and they must have the Build Action property set to BundleResource or your app will crash at runtime.

iOS BuildAction

UWP

AudioServiceImplementation.cs

using WSAudioApp.Interfaces;
using WSAudioApp;
using System;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.UI.Xaml.Controls;
using Windows.ApplicationModel;
using System.Diagnostics;
using Xamarin.Forms;
using WSAudioApp.UWP.Implementations;
[assembly: Dependency(typeof(AudioServiceImplementation))]
namespace WSAudioApp.UWP.Implementations
{
    public class AudioServiceImplementation : IAudioService
    {
        public async Task PlayAudioFile(string fileName)
        {
            StorageFolder Folder = await Package.Current.InstalledLocation.GetFolderAsync(@"Sounds");
            StorageFile sf = await Folder.GetFileAsync(fileName);
            var PlayMusic = new MediaElement();
            PlayMusic.AudioCategory = Windows.UI.Xaml.Media.AudioCategory.Media;
            PlayMusic.SetSource(await sf.OpenAsync(FileAccessMode.Read), sf.ContentType);
            PlayMusic.Play();
        }
    }
}

The UWP implementation leverages the MediaElement class. It is also somewhat more flexible than its Android and iOS counterparts in that it pulls the files from a custom defined folder name, in this case the Sounds folder.

Gotchas

  • Again, the fileName property must contain the name and extension, it must exactly match the file you want to play, and it is case-sensitive.
  • These files must be located in the UWP project’s Sounds folder, or whichever folder you decide to use, and they must have the Build Action property set to Embedded Resource.

iOS BuildAction

The Calling Code

When we need to call one of these methods we have to first grab the implementation from the DependencyService:

IAudioService audioService = DependencyService.Get<IAudioService>();
audioService.PlayAudioFile("Tone1.mp3");

So that’s it. Not too challenging, but you can spend a lot of time spinning your wheels if you aren’t aware of each platform’s file system location and property requirements. If you want to learn more about the DependencyService and what it can do, read up on the Xamarin docs here.

Scroll to Top