floatinghotpot / cordova-plugin-nativeaudio

The low latency audio plugin is designed to enable low latency and polyphonic audio from Cordova/PhoneGap applications, using a very simple and basic API.
MIT License
235 stars 287 forks source link

"A reference does not exist for the specified audio id" #121

Open wkyleg opened 7 years ago

wkyleg commented 7 years ago

I am attempting to use this plugin in an Angular 1/Ionic 1 project but I am receiving the error message that "A reference does not exist for the specified audio id." I checked inside the APK cordova build and all of my audio files were there though.

When I used the "cordovaNativeAudio" module I didn't end up receiving any error at all. I had to to use "window.plugins.NativeAudio" instead to receive that.

Anyone have an idea what could be causing this problem?

Tomenbois commented 7 years ago

I have the same problem, do you have a solution ? Thx

wkyleg commented 7 years ago

I did not. I ended up using a Module called ngAudio which is an Angular wrapper for the HTML5 Audio API instead

Tomenbois commented 7 years ago

Hi, i tried ngAudio but it is not working for me. Do you know if it's work on Ionic2/Angular2 project ?

adoniscoder commented 7 years ago

for anyone experiencing this issue heres the temporary solution until the plugin fix : firstly I noticed that the 'id' that we have to pass to the plugin should be in json because plugin needs JSON format while it not handles this by itself. also you have to change the NativeAudio.java file to this : add the following lines in executePlayOrLoop method just in the try section after audioID

if ( !assetMap.containsKey(audioID) ) { this.executePreload( this.dData ); } and the problem solved

Tomenbois commented 7 years ago

Hi, i tried your solution but i have the same error Unhandled Promise rejection: A reference does not exist for the specified audio id. ; Zone: <root> ; Task: null ; Value: A reference does not exist for the specified audio id.

It worked once, then nothing !

adoniscoder commented 7 years ago

would you paste your code so I can see that ?

wkyleg commented 7 years ago

@Tomenbois It's an Angular 1 module so unfortunately not

Tomenbois commented 7 years ago

@ahm-hossein my app.component.ts

import {enableProdMode} from '@angular/core';
import { Component, ViewChild } from '@angular/core';
import { Nav, Events } from 'ionic-angular';
import { Platform } from 'ionic-angular';
import { StatusBar } from 'ionic-native';
import {WelcomePage} from '../pages/welcome/welcome';
import {DbService} from '../providers/db-service';
import {StoreService} from '../providers/store-service';
import {ExamListPage} from '../pages/exam-list/exam-list';
import {DeveloperPage} from '../pages/developer/developer';
import {TrackerListPage} from '../pages/tracker-list/tracker-list';
import {TableauPage} from '../pages/tableau/tableau';
import {TableauDetailsPage} from '../pages/tableau-details/tableau-details';
import {ContactPage} from '../pages/contact/contact';
import {TranslateService} from 'ng2-translate';
import {NativeAudio} from '@ionic-native/native-audio';

declare var window: any;

enableProdMode();

@Component({
  selector: 'app',
  templateUrl: 'app.html',
  providers: [DbService, StoreService],
})
export class MyApp {
  @ViewChild(Nav) nav: Nav;
  rootPage: any = WelcomePage;
  pages: Array<{title: string, component: any, icon: string, paid: number}>;
  page_accueil: Array<{title: string, component: any, icon: string, paid: number}>;
  page_evaluation: Array<{title: string, component: any, icon: string, paid: number}>;
  page_suivi: Array<{title: string, component: any, icon: string, paid: number}>;
  page_tableau: Array<{title: string, component: any, icon: string, paid: number}>;
  page_temps: Array<{title: string, component: any, icon: string, paid: number}>;
  page_contact: Array<{title: string, component: any, icon: string, paid: number}>;
  private readySource: any;
  dbService: any;
  storeService: any;
  private ad: any;
  public _Paid: any;
  private onSuccess:any;
  private onError:any;
  tracks: any[];
  singleTracks: any;
  allTracks: any[];
  selectedTrack: number;

 constructor(
   public platform: Platform,
   public events: Events,
   dbService: DbService,
   storeService: StoreService,
   public translateService: TranslateService,
   private nativeAudio: NativeAudio,

 ) {
    this.initializeApp();
    this.dbService = dbService;
    this.storeService = storeService;
    this.page_accueil = [{title: 'Accueil', component: WelcomePage, icon: 'bookmark', paid: 0}];
    this.page_evaluation = [{title: 'Evaluation', component: ExamListPage, icon: 'cube', paid: 0}];
    this.page_suivi = [{title: 'Suivi', component: TrackerListPage, icon: "arrow-graph-up-right", paid: 0}];
    this.page_tableau = [{title: 'Tableau', component: TableauPage, icon: "arrow-graph-up-right", paid: 0}];
    this.page_temps = [{title: 'Temps', component: TableauDetailsPage, icon: "arrow-graph-up-right", paid: 0}];
    this.page_contact = [{title: 'Contact', component: ContactPage, icon: "arrow-graph-up-right", paid: 0}];

    ];
    translateService.setDefaultLang('fr');
    platform.ready().then(() => {
    StatusBar.styleDefault();

  });
 }

 initializeApp() {
   this.platform.ready().then((readySource) => {
     // Okay, so the platform is ready and our plugins are available.
     // Here you can do any higher level native things you might need.
     console.log('Platform ready from', readySource);
     this.readySource = readySource;

     if (readySource == 'cordova') {
          StatusBar.styleDefault();

          if (this.platform.is('ios')){
            // Copy data.db from Application folder into Document Database folder
            window.plugins.sqlDB.copy("Q2data.db",2, success => {
              // Initialize database service (DbService)
              this.dbService.init();
            },error =>{
              console.log("Error Code = "+JSON.stringify(error));
            });
          } else {
            // Copy data.db - Android destination
            window.plugins.sqlDB.copy("Q2data.db",0, success => {
              // Initialize database service (DbService)
              this.dbService.init();
            },error =>{
              console.log("Error Code = "+JSON.stringify(error));
            });
          }

          // Initialize database service (DbService)
          this.dbService.init();

          // Initialize storeService (IAP)
          this.storeService.init();

        } else {
          this.ad = 0;
          // Initialize database service (DbService)
          this.dbService.init();
          this.storeService.init();
        }

        //To force Full Version as default comment the line below (149)
        //this.dbService.isFullVersion().then(data => this._Paid = data);

        // To force Full Version as default uncomment the 3 lines below (152-154)
        this.dbService.setProperty('fullversion','true');
        this.dbService.setFullVersion();
        this.dbService.setProperty('admob','false');
                //
          //native audio
          //musique d'ambiance
            console.log("Musique démarre");
            this.nativeAudio.preloadComplex('ambiant_1', 'assets/sound/ambiant.mp3', 1, 1, 0);
             console.log("Musique Play");
            this.nativeAudio.play('ambiant_1', () => console.log('uniqueId1 is done playing'));
      });
      this.events.subscribe('paid:full',() =>this._Paid=1);
      this.events.subscribe('paid:free',() =>this._Paid=0);

     //this.nav.setRoot(page.component);

 }

 openDeveloperPage(){
    this.nav.push(DeveloperPage);
  }

 openPage(page) {
   let user = "EVT";

   // Reset the content nav to have just this page
   // we wouldn't want the back button to show in this scenario
   if (page.component == this.rootPage) {
        this.nav.setRoot(this.rootPage);
      } else {
        this.nav.push(page.component, {
          readySource: this.readySource,
          user: user,
          adId: this.ad
        });
      }

 }
 translateToEnglish(){
        this.translateService.use('en');
    }
    translateToFrench(){
        this.translateService.use('fr');
    }

}

and my NativeAudio.java

//
//
//  NativeAudio.java
//
//  Created by Sidney Bofah on 2014-06-26.
//

package com.rjfun.cordova.plugin.nativeaudio;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Callable;

import org.json.JSONArray;
import org.json.JSONException;

import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.media.AudioManager;
import android.util.Log;
import android.view.KeyEvent;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status;
import org.json.JSONObject;

public class NativeAudio extends CordovaPlugin implements AudioManager.OnAudioFocusChangeListener {

    /* options */
    public static final String OPT_FADE_MUSIC = "fadeMusic";

    public static final String ERROR_NO_AUDIOID="A reference does not exist for the specified audio id.";
    public static final String ERROR_AUDIOID_EXISTS="A reference already exists for the specified audio id.";

    public static final String SET_OPTIONS="setOptions";
    public static final String PRELOAD_SIMPLE="preloadSimple";
    public static final String PRELOAD_COMPLEX="preloadComplex";
    public static final String PLAY="play";
    public static final String STOP="stop";
    public static final String LOOP="loop";
    public static final String UNLOAD="unload";
    public static final String ADD_COMPLETE_LISTENER="addCompleteListener";
    public static final String SET_VOLUME_FOR_COMPLEX_ASSET="setVolumeForComplexAsset";

    private static final String LOGTAG = "NativeAudio";

    private static HashMap<String, NativeAudioAsset> assetMap;
    private static ArrayList<NativeAudioAsset> resumeList;
    private static HashMap<String, CallbackContext> completeCallbacks;
    private boolean fadeMusic = false;

    public void setOptions(JSONObject options) {
        if(options != null) {
            if(options.has(OPT_FADE_MUSIC)) this.fadeMusic = options.optBoolean(OPT_FADE_MUSIC);
        }
    }

    private PluginResult executePreload(JSONArray data) {
        String audioID;
        try {
            audioID = data.getString(0);
            if (!assetMap.containsKey(audioID)) {
                String assetPath = data.getString(1);
                Log.d(LOGTAG, "preloadComplex - " + audioID + ": " + assetPath);

                double volume;
                if (data.length() <= 2) {
                    volume = 1.0;
                } else {
                    volume = data.getDouble(2);
                }

                int voices;
                if (data.length() <= 3) {
                    voices = 1;
                } else {
                    voices = data.getInt(3);
                }

                String fullPath = "www/".concat(assetPath);

                Context ctx = cordova.getActivity().getApplicationContext();
                AssetManager am = ctx.getResources().getAssets();
                AssetFileDescriptor afd = am.openFd(fullPath);

                NativeAudioAsset asset = new NativeAudioAsset(
                        afd, voices, (float)volume);
                assetMap.put(audioID, asset);

                return new PluginResult(Status.OK);
            } else {
                return new PluginResult(Status.ERROR, ERROR_AUDIOID_EXISTS);
            }
        } catch (JSONException e) {
            return new PluginResult(Status.ERROR, e.toString());
        } catch (IOException e) {
            return new PluginResult(Status.ERROR, e.toString());
        }       
    }

    private PluginResult executePlayOrLoop(String action, JSONArray data) {
        final String audioID;
        try {
            audioID = data.getString(0);
                        if ( !assetMap.containsKey(audioID) ) { this.executePreload( data ); }
//          Log.d( LOGTAG, "play - " + audioID );

            if (assetMap.containsKey(audioID)) {
                NativeAudioAsset asset = assetMap.get(audioID);
                if (LOOP.equals(action))
                    asset.loop();
                else
                    asset.play(new Callable<Void>() {
                        public Void call() throws Exception {
                if (completeCallbacks != null) {
                    CallbackContext callbackContext = completeCallbacks.get(audioID);
                    if (callbackContext != null) {
                    JSONObject done = new JSONObject();
                    done.put("id", audioID);
                    callbackContext.sendPluginResult(new PluginResult(Status.OK, done));
                    }
                }
                            return null;
                        }
                    });
            } else {
                return new PluginResult(Status.ERROR, ERROR_NO_AUDIOID);
            }
        } catch (JSONException e) {
            return new PluginResult(Status.ERROR, e.toString());
        } catch (IOException e) {
            return new PluginResult(Status.ERROR, e.toString());
        }

        return new PluginResult(Status.OK);
    }

    private PluginResult executeStop(JSONArray data) {
        String audioID;
        try {
            audioID = data.getString(0);
            //Log.d( LOGTAG, "stop - " + audioID );

            if (assetMap.containsKey(audioID)) {
                NativeAudioAsset asset = assetMap.get(audioID);
                asset.stop();
            } else {
                return new PluginResult(Status.ERROR, ERROR_NO_AUDIOID);
            }           
        } catch (JSONException e) {
            return new PluginResult(Status.ERROR, e.toString());
        }

        return new PluginResult(Status.OK);
    }

    private PluginResult executeUnload(JSONArray data) {
        String audioID;
        try {
            audioID = data.getString(0);
            Log.d( LOGTAG, "unload - " + audioID );

            if (assetMap.containsKey(audioID)) {
                NativeAudioAsset asset = assetMap.get(audioID);
                asset.unload();
                assetMap.remove(audioID);
            } else {
                return new PluginResult(Status.ERROR, ERROR_NO_AUDIOID);
            }
        } catch (JSONException e) {
            return new PluginResult(Status.ERROR, e.toString());
        } catch (IOException e) {
            return new PluginResult(Status.ERROR, e.toString());
        }

        return new PluginResult(Status.OK);
    }

    private PluginResult executeSetVolumeForComplexAsset(JSONArray data) {
        String audioID;
        float volume;
        try {
            audioID = data.getString(0);
            volume = (float) data.getDouble(1);
            Log.d( LOGTAG, "setVolume - " + audioID );

            if (assetMap.containsKey(audioID)) {
                NativeAudioAsset asset = assetMap.get(audioID);
                asset.setVolume(volume);
            } else {
                return new PluginResult(Status.ERROR, ERROR_NO_AUDIOID);
            }
        } catch (JSONException e) {
            return new PluginResult(Status.ERROR, e.toString());
        }
        return new PluginResult(Status.OK);
    }
    @Override
    protected void pluginInitialize() {
        AudioManager am = (AudioManager)cordova.getActivity().getSystemService(Context.AUDIO_SERVICE);

            int result = am.requestAudioFocus(this,
                    // Use the music stream.
                    AudioManager.STREAM_MUSIC,
                    // Request permanent focus.
                    AudioManager.AUDIOFOCUS_GAIN);

        // Allow android to receive the volume events
        this.webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_DOWN, false);
        this.webView.setButtonPlumbedToJs(KeyEvent.KEYCODE_VOLUME_UP, false);
    }

    @Override
    public boolean execute(final String action, final JSONArray data, final CallbackContext callbackContext) {
        Log.d(LOGTAG, "Plugin Called: " + action);

        PluginResult result = null;
        initSoundPool();

        try {
            if (SET_OPTIONS.equals(action)) {
                JSONObject options = data.optJSONObject(0);
                this.setOptions(options);
                callbackContext.sendPluginResult( new PluginResult(Status.OK) );

            } else if (PRELOAD_SIMPLE.equals(action)) {
                cordova.getThreadPool().execute(new Runnable() {
                    public void run() {
                        callbackContext.sendPluginResult( executePreload(data) );
                    }
                });             

            } else if (PRELOAD_COMPLEX.equals(action)) {
                cordova.getThreadPool().execute(new Runnable() {
                    public void run() {
                        callbackContext.sendPluginResult( executePreload(data) );
                    }
                });             

            } else if (PLAY.equals(action) || LOOP.equals(action)) {
                cordova.getThreadPool().execute(new Runnable() {
                    public void run() {
                        callbackContext.sendPluginResult( executePlayOrLoop(action, data) );
                    }
                });             

            } else if (STOP.equals(action)) {
                cordova.getThreadPool().execute(new Runnable() {
                    public void run() {
                        callbackContext.sendPluginResult( executeStop(data) );
                    }
                });

            } else if (UNLOAD.equals(action)) {
                cordova.getThreadPool().execute(new Runnable() {
                    public void run() {
                        executeStop(data);
                        callbackContext.sendPluginResult( executeUnload(data) );
                    }
                });
            } else if (ADD_COMPLETE_LISTENER.equals(action)) {
                if (completeCallbacks == null) {
                    completeCallbacks = new HashMap<String, CallbackContext>();
                }
                try {
                    String audioID = data.getString(0);
                    completeCallbacks.put(audioID, callbackContext);
                } catch (JSONException e) {
                    callbackContext.sendPluginResult(new PluginResult(Status.ERROR, e.toString()));
        }
        } else if (SET_VOLUME_FOR_COMPLEX_ASSET.equals(action)) {
                cordova.getThreadPool().execute(new Runnable() {
            public void run() {
                            callbackContext.sendPluginResult( executeSetVolumeForComplexAsset(data) );
                    }
                 });
        }
            else {
                result = new PluginResult(Status.OK);
            }
        } catch (Exception ex) {
            result = new PluginResult(Status.ERROR, ex.toString());
        }

        if(result != null) callbackContext.sendPluginResult( result );
        return true;
    }

    private void initSoundPool() {

        if (assetMap == null) {
            assetMap = new HashMap<String, NativeAudioAsset>();
        }

        if (resumeList == null) {
            resumeList = new ArrayList<NativeAudioAsset>();
        }
    }

    public void onAudioFocusChange(int focusChange) {
        if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
            // Pause playback
        } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
            // Resume playback
        } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
            // Stop playback
        }
    }

    @Override
    public void onPause(boolean multitasking) {
        super.onPause(multitasking);

        for (HashMap.Entry<String, NativeAudioAsset> entry : assetMap.entrySet()) {
            NativeAudioAsset asset = entry.getValue();
            boolean wasPlaying = asset.pause();
            if (wasPlaying) {
                resumeList.add(asset);
            }
        }
    }

    @Override
    public void onResume(boolean multitasking) {
        super.onResume(multitasking);
        while (!resumeList.isEmpty()) {
            NativeAudioAsset asset = resumeList.remove(0);
            asset.resume();
        }
    }
}
di3orlive commented 7 years ago

Someone found a solution? same thing with "ionic-angular": "3.5.3"

jeff235255 commented 7 years ago

@di3orlive Here is my code and it worked very well on android and simulator. We have to make sure the sound file loaded and we can play, loop or even stop it. In my case, I'm using the sound when my modal was loaded.

ionViewDidLoad() { this.nativeAudio.preloadComplex('newOder', 'assets/sounds/beep.mp3', 1, 1, 0).then(() => {
this.nativeAudio.play('newOder'); this.nativeAudio.loop('newOder'); }); }

And on my dismiss event, it should unload the ID to make sure we can load and use for the next time, otherwise you will get the message "A reference does not exist for the specified audio id"

// my dismiss event dismiss() { this.nativeAudio.stop('newOder'); this.nativeAudio.unload('newOder'); this.vc.dismiss({ }); }

di3orlive commented 7 years ago

@jeff235255 I found better and faster solution. just html5 audio + "cordova-plugin-music-controls": "~2.0.0" only cant find same controls for lockscreen, maybe you know something ?

jcsubmit7 commented 7 years ago

Hello ahm-hossein

What does this mean: " firstly I noticed that the 'id' that we have to pass to the plugin should be in json because plugin needs JSON format while it not handles this by itself. "

I tried the change to the java file ( this.dData -> data ) but still the same problem.

Thanks JC

andrezap commented 7 years ago

fixed the issue preloading the audios in the app.components.ts after check if the platform is ready

EugeneSnihovsky commented 7 years ago

I came here from ionic-native. I need to some looped alarm in my app. And here some conclusions.

  1. On android all works fine, problem begins on ios devices.
  2. preloadSimple can't be used with loop. That says in JSDoc comment
    Loads an audio file into memory. Optimized for short clips / single shots (up to five seconds). Cannot be stopped / looped.

    But it works on android normal, on ios return an error

    Action restricted to assets loaded using preloadComplex()
  3. preloadComplex + loop works on ios, but it launch audio just one time and then stops
  4. preloadComplex + 'play' + 'setInterval' works fine
  5. If call need on application start, it should be called only after platform.ready()
  6. In ionic 3 path to audio file should start from assets/... (not ./assets/...). And file should be placed in project_folder/src/assets/...

Here is solution based on this plugin

    public setPhoneAudio(): Promise<void> {
        if (this.platform.is('cordova')) {
            return this.platform.ready()
                .then(() => (<any> window).plugins.NativeAudio
                    .preloadComplex( 'ring', 'assets/sound/ring.mp3', 0.5, 1, 0,
                        (msg: string) => this._intervalId = setInterval(() => (<any> window).plugins.NativeAudio.play( 'ring'), 3000),
                        (msg: string) => alert( 'error: ' + msg )
                    ));
        }
        return Promise.resolve();
    }

    private _intervalId;

And solution based on ionic-native

    public constructor(
        public platform: Platform,
        private _nativeAudio: NativeAudio
    ) {}

    public setPhoneAudio(): Promise<any> {
        if (this.platform.is('cordova')) {
            return this.platform.ready()
                .then(() => this._nativeAudio.preloadComplex( 'ring', 'assets/sound/ring.mp3', 0.5, 1, 0))
                .then(() => this._intervalId = setInterval(() => this._nativeAudio.play( 'ring'), 3000));
        }
        return Promise.resolve();
    }

    private _intervalId;
dolthead commented 5 years ago

I got it to work with mp3 files on IOS and ANDROID (and WEB/HTML5) by wrapping the preloadSimple and play methods in cordova ready, AND fixing a bug in the Josh Morony tutorial. He was passing the asset path to the play method, and should be passing the key instead. Here is my solution:

https://github.com/dolthead/topzee/blob/master/src/app/services/audio.service.ts

I call the preload on each page where a sound is played, so it's very modular.

Frtrillo commented 3 years ago

I got it to work with mp3 files on IOS and ANDROID (and WEB/HTML5) by wrapping the preloadSimple and play methods in cordova ready, AND fixing a bug in the Josh Morony tutorial. He was passing the asset path to the play method, and should be passing the key instead. Here is my solution:

https://github.com/dolthead/topzee/blob/master/src/app/services/audio.service.ts

I call the preload on each page where a sound is played, so it's very modular.

And still won't work for me Line 1 - Msg: play error {"key":"tabSwitch","asset":"assets/audio/clickSound.mp3"} A reference does not exist for the specified audio id. on android studio, even tho with your solution its taking all the paths.