Background
Xamarin.Forms does not have built-in way of playing sound, but it is easy to implement this using platform specific code on Android and iOS. This blog post describes how to build a project that implements a sound playing service with Xamarin.Forms.
We'll begin this walkthrough with a new project in Xamarin Studio using the Cross Platform App - Blank Xamarin.Forms App template as a starting point.
Code
First of all we'll have to define the interface that should be implemented in our platform projects. Add a new interface to the Forms project in your solution called ISoundProvider:
public interface ISoundProvider{
public Task PlaySoundAsync(string filename);
}
Remember that you can press ALT+ENTER
on classes that the Xamarin Studio editor doesn't know to bring up the refactoring dropdown that automatically inserts the correct using statements in your source file.*
Next lets create the abstraction class that will be used in the Xamarin.Forms app to play sound in our Forms project:
public class SoundService{
private readonly ISoundProvider _soundProvider;
public SoundService(){
}
public async Task PlaySoundAsync(string filename){
return _soundProvider.PlaySoundAsync(filename);
}
}
We'll get back to how you look up the platform implementation to get an instance that you can assign to the _soundProvider
member.
Now we should be ready to create the platform specific providers!
public interface ISoundProvider{
public Task PlaySoundAsync(string filename);
}
Remember that you can press
ALT+ENTER
on classes that the Xamarin Studio editor doesn't know to bring up the refactoring dropdown that automatically inserts the correct using statements in your source file.*public class SoundService{
private readonly ISoundProvider _soundProvider;
public SoundService(){
}
public async Task PlaySoundAsync(string filename){
return _soundProvider.PlaySoundAsync(filename);
}
}
_soundProvider
member.Android
Lets start with Android first - add the following code to your Droid project in a file called AndroidSoundProvider.cs
:
public class AndroidSoundProvider: ISoundProvider
{
public Task PlaySoundAsync (string filename)
{
// Create media player
var player = new MediaPlayer();
// Create task completion source to support async/await
var tcs = new TaskCompletionSource<bool> ();
// Open the resource
var fd = Xamarin.Forms.Forms.Context.Assets.OpenFd (filename);
// Hook up some events
player.Prepared += (s, e) => {
player.Start();
};
player.Completion += (sender, e) => {
tcs.SetResult(true);
};
// Start playing
player.SetDataSource (fd.FileDescriptor); player.Prepare ();
return tcs.Task;
}
}
As you can see we're using the async/await pattern in our little app to make it possible to play a sound and wait for it to return. This is done by creating a TaskCompletionSource
that can be used to signal back to the caller that we are done playing our sound.
Since we'll be adding the sound file as an asset to our Android project, we're using the asset system in Android to load the file. After we've hooked up some events to be able mark the task as completed we'll set the data source and ask the player to prepare before we return the awaitable task object.
AndroidSoundProvider.cs
:public class AndroidSoundProvider: ISoundProvider
{
public Task PlaySoundAsync (string filename)
{
// Create media player
var player = new MediaPlayer();
// Create task completion source to support async/await
var tcs = new TaskCompletionSource<bool> ();
// Open the resource
var fd = Xamarin.Forms.Forms.Context.Assets.OpenFd (filename);
// Hook up some events
player.Prepared += (s, e) => {
player.Start();
};
player.Completion += (sender, e) => {
tcs.SetResult(true);
};
// Start playing
player.SetDataSource (fd.FileDescriptor); player.Prepare ();
return tcs.Task;
}
}
TaskCompletionSource
that can be used to signal back to the caller that we are done playing our sound. iOS
Next we'll create the iOS provider:
public class TouchSoundProvider: NSObject, ISoundProvider
{
private AVAudioPlayer _player;
public Task PlaySoundAsync (string filename)
{
var tcs = new TaskCompletionSource<bool> ();
string path = NSBundle.MainBundle.PathForResource(Path.GetFileNameWithoutExtension(filename),
Path.GetExtension(filename));
var url = NSUrl.FromString (path);
_player = AVAudioPlayer.FromUrl(url);
_player.FinishedPlaying += (object sender, AVStatusEventArgs e) => {
_player = null;
tcs.SetResult(true);
};
_player.Play();
return tcs.Task;
}
}
This code is as you can see almost identical to the code on Android, except that we're using the iOS Frameworks to play the sound andthe iOS resource system to load the sound file.
public class TouchSoundProvider: NSObject, ISoundProvider
{
private AVAudioPlayer _player;
public Task PlaySoundAsync (string filename)
{
var tcs = new TaskCompletionSource<bool> ();
string path = NSBundle.MainBundle.PathForResource(Path.GetFileNameWithoutExtension(filename),
Path.GetExtension(filename));
var url = NSUrl.FromString (path);
_player = AVAudioPlayer.FromUrl(url);
_player.FinishedPlaying += (object sender, AVStatusEventArgs e) => {
_player = null;
tcs.SetResult(true);
};
_player.Play();
return tcs.Task;
}
}
Connecting the Dots
Now we need to tie these classes together. The abstract SoundService
class does not yet know about our platform specific implementations. In Xamarin.Forms we have access to a simple dependency service that helps us make platform specific classes defined by interfaces available through a lookup mechanism.
To tell the dependency service that we have an implementations of the ISoundProvider
interface we add an assembly attribute to the classes in our platform projects:
Add the following assembly attributes to Android implementations (above the namespace line):
[assembly: Dependency (typeof (AndroidSoundProvider))]
and to the iOS implementation:
[assembly: Dependency (typeof (TouchSoundProvider))]
Now we should be ready to use the classes through the dependecy service in Xamarin.Forms. Update the abstraction class constructor to:
public SoundService(){
_soundProvider = DependencyService.Get();
}
You can read more about the Xamarin Forms Dependency Service here: https://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/
SoundService
class does not yet know about our platform specific implementations. In Xamarin.Forms we have access to a simple dependency service that helps us make platform specific classes defined by interfaces available through a lookup mechanism. ISoundProvider
interface we add an assembly attribute to the classes in our platform projects:[assembly: Dependency (typeof (AndroidSoundProvider))]
[assembly: Dependency (typeof (TouchSoundProvider))]
public SoundService(){
_soundProvider = DependencyService.Get();
}
You can read more about the Xamarin Forms Dependency Service here: https://developer.xamarin.com/guides/cross-platform/xamarin-forms/dependency-service/
Adding the Sound Resources
There are different ways of adding the sound resources - in this sample I've decided to add them as native resources to the platform projects.
On iOS you can add your sound file to the Resources folder - this will make the file a Bundle Resource (right-click on the sound file and ensure its build action is set to Bundle Resource).
In your Android project you'll add the sound file to the Asset folder and right-click and set the build action to AndroidAsset.
Add a Button to Play the Sound
In your Forms project you can now add a button that lets you play the sound you added (in this example it is called sound.mp3
). Update your App
class' constructor to:
public App ()
{
// The root page of your application
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = {
new Label {
XAlign = TextAlignment.Center,
Text = "Click to play a sound!"
},
new Button{
Text = "Play",
Command = new Command(async () => {
var soundService = new SoundService();
await soundService.PlaySoundAsync("sound.mp3");
})
}
}
}
};
}
Notice how the code uses a Command
to execute when the user taps the button, and how the command object supports the async/await pattern that we've already implemented in our code.
Now run and test your apps - they should play your sound when you tap the play button!
Full code can be downloaded from Github.
sound.mp3
). Update your App
class' constructor to:public App ()
{
// The root page of your application
MainPage = new ContentPage {
Content = new StackLayout {
VerticalOptions = LayoutOptions.Center,
Children = {
new Label {
XAlign = TextAlignment.Center,
Text = "Click to play a sound!"
},
new Button{
Text = "Play",
Command = new Command(async () => {
var soundService = new SoundService();
await soundService.PlaySoundAsync("sound.mp3");
})
}
}
}
};
}
Command
to execute when the user taps the button, and how the command object supports the async/await pattern that we've already implemented in our code.
2 comments:
Just wanted to thank you for the sample code and tutorial. It saved me a lot of time. Is there a way to do vibration within Xamarin Forms?
Thank for your feedback! To play a vibration I think you need to install a plugin: https://www.nuget.org/packages/Xam.Plugins.Forms.Vibrate/
Post a Comment