In this tutorial, we are going to cover how we can add sound effects to our Ionic applications. This particular example is going to cover adding a “click” sound effect that is triggered when switching between tabs, but we will be creating a service that can be used generally to play many different sound effects in whatever situation you would like.
I wrote a tutorial on this subject a while ago, but I wanted to publish a new tutorial for a couple of reasons:
- It was a long time ago and I think there is room for improvement
- The Cordova plugin that Ionic Native uses for native audio does not appear to work with Capacitor, so I wanted to adapt the service to be able to rely entirely on web audio if desired.
We will be creating an audio service that can handle switching between native and web audio depending on the platform that the application is running on. However, we will also be adding an option to force web audio even in a native environment which can be used in the case of Capacitor (at least until I can find a simple solution for native audio in Capacitor).
In any case, I don’t think using web audio for sound effects really has any obvious downsides in a native environment anyway.
Before We Get Started
Last updated for Ionic 4, beta.16
This tutorial assumes that you already have a decent working knowledge of the Ionic framework. If you need more of an introduction to Ionic I would recommend checking out my book or the Ionic tutorials on my website.
1. Install the Native Audio Plugin
In order to enable support for native audio, we will need to install the Cordova plugin for native audio as well as the Ionic Native package for that plugin.
Keep in mind that if you are using Capacitor, plugins should be installed using npm install
not ionic cordova plugin add
.
2. Creating an Audio Service
First, we are going to add an Audio service to our application. We can do that by running the following command:
ionic g service services/Audio
The role of this service will be to preload our audio assets and to play them on demand. It will be smart enough to handle preloading the audio in the correct manner, as well as using the correct playing mechanism based on the platform.
Modify src/app/services/audio.service.ts to reflect the following:
import { Injectable } from '@angular/core';
import { Platform } from '@ionic/angular';
import { NativeAudio } from '@ionic-native/native-audio/ngx';
interface Sound {
key: string;
asset: string;
isNative: boolean
}
@Injectable({
providedIn: 'root'
})
export class AudioService {
private sounds: Sound[] = [];
private audioPlayer: HTMLAudioElement = new Audio();
private forceWebAudio: boolean = true;
constructor(private platform: Platform, private nativeAudio: NativeAudio){
}
preload(key: string, asset: string): void {
if(this.platform.is('cordova') && !this.forceWebAudio){
this.nativeAudio.preloadSimple(key, asset);
this.sounds.push({
key: key,
asset: asset,
isNative: true
});
} else {
let audio = new Audio();
audio.src = asset;
this.sounds.push({
key: key,
asset: asset,
isNative: false
});
}
}
play(key: string): void {
let soundToPlay = this.sounds.find((sound) => {
return sound.key === key;
});
if(soundToPlay.isNative){
this.nativeAudio.play(soundToPlay.asset).then((res) => {
console.log(res);
}, (err) => {
console.log(err);
});
} else {
this.audioPlayer.src = soundToPlay.asset;
this.audioPlayer.play();
}
}
}
In this service, we are keeping track of an array of sounds
which will contain all of the audio assets that we want to play.
We create an audioPlayer
object for playing web audio (which is the same as having an <audio src="whatever.mp3">
element). We will use this single audio object and switch out the src
to play various sounds. The forceWebAudio
flag will always use web audio to play sounds when enabled.
The purpose of the preload
method is to load the sound assets and make them available for use. We supply it with a key
that we will use to reference a particular sound effect and an asset
which will link to the actual audio asset.
In the case of native audio, the sound is preloaded with the native plugin. For web audio, we create a new audio object and set its src
which will load the audio asset into the cache - this will allow us to immediately play the sound later rather than having to load it on demand.
The play
method allows us to pass it the key
of the sound we want to play, and then it will find it in the sounds
array. We when either play that sound using the native audio plugin, or we set the src
property on the audioPlayer
to that asset and then call the play
method.
3. Preloading Sound Assets
Before we can trigger playing our sounds, we need to pass the sound assets to the preload
method we just created. Since we want to play a sound when switching tabs for this example, we could preload the sound in the TabsPage component.
Modify src/app/tabs/tabs.page.ts to reflect the following:
import { Component, AfterViewInit } from '@angular/core';
import { AudioService } from '../services/audio.service';
@Component({
selector: 'app-tabs',
templateUrl: 'tabs.page.html',
styleUrls: ['tabs.page.scss']
})
export class TabsPage implements AfterViewInit {
constructor(private audio: AudioService){
}
ngAfterViewInit(){
this.audio.preload('tabSwitch', 'assets/audio/clickSound.mp3');
}
}
This will cause the sound to be loaded (whether through web or native audio) and be available for use. You don’t have to preload the sound here - you could preload the sound from anywhere (perhaps you would prefer to load all of your sounds in the root component).
NOTE: You will need to add a sound asset to your assets
folder. For this example, I used a “click” sound from freesound.org. Make sure to check that the license for the sounds you want to use match your intended usage.
4. Triggering Sounds
Finally, we just need to trigger the sound in some way. All we need to do to achieve this is to call the play
method on the audio service like this:
this.audio.play('tabSwitch');
Let’s complete our example of triggering the sound on tab changes.
Modify src/app/tabs/tabs.page.ts to reflect the following:
import { Component, AfterViewInit, ViewChild } from '@angular/core';
import { Tabs } from '@ionic/angular';
import { AudioService } from '../services/audio.service';
import { skip } from 'rxjs/operators';
@Component({
selector: 'app-tabs',
templateUrl: 'tabs.page.html',
styleUrls: ['tabs.page.scss']
})
export class TabsPage implements AfterViewInit {
@ViewChild(Tabs) tabs: Tabs;
constructor(private audio: AudioService){
}
ngAfterViewInit(){
this.audio.preload('tabSwitch', 'assets/audio/clickSound.mp3');
this.tabs.ionChange.pipe(skip(1)).subscribe((ev) => {
this.audio.play('tabSwitch');
});
}
}
We are grabbing a reference to the tabs component here, and then we subscribe to the ionChange
observable which triggers every time the tab changes. The ionChange
will also fire once upon initially loading the app, so to avoid playing the sound the first time it is triggered we pipe
the skip
operator on the observable to ignore the first time it is triggered.
If you are interested in learning more about the mechanism I am using here to detect tab switching, you might be interested in one of my recent videos: Using Tab Badges in Ionic.
Summary
Loading and playing audio programmatically can be a bit cumbersome, but with the service we have created we can break it down into just two simple method calls - one to load the asset, and one to play it.