Tutorial hero
Lesson icon

Adding Sound Effects to an Ionic Application

Originally published November 22, 2018 Time 7 mins

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:

  1. It was a long time ago and I think there is room for improvement
  2. 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.

Learn to build modern Angular apps with my course