diff --git a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java index e9f2c8cde..b556bc34e 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/VRBrowserActivity.java @@ -176,7 +176,7 @@ protected void onCreate(Bundle savedInstanceState) { mPermissionDelegate = new PermissionDelegate(this, this); - mAudioEngine = new AudioEngine(this, new VRAudioTheme()); + mAudioEngine = new AudioEngine(this, null); mAudioEngine.setEnabled(SettingsStore.getInstance(this).isAudioEnabled()); mAudioEngine.preloadAsync(() -> { Log.i(LOGTAG, "AudioEngine sounds preloaded!"); diff --git a/app/src/common/shared/org/mozilla/vrbrowser/audio/AudioEngine.java b/app/src/common/shared/org/mozilla/vrbrowser/audio/AudioEngine.java index dbc1bdf8a..5b23575c5 100644 --- a/app/src/common/shared/org/mozilla/vrbrowser/audio/AudioEngine.java +++ b/app/src/common/shared/org/mozilla/vrbrowser/audio/AudioEngine.java @@ -5,17 +5,12 @@ package org.mozilla.vrbrowser.audio; -import android.app.Activity; import android.content.Context; -import android.util.Log; - -import com.google.vr.sdk.audio.GvrAudioEngine; import java.util.concurrent.ConcurrentHashMap; public class AudioEngine { private Context mContext; - private GvrAudioEngine mEngine; - private AudioTheme mTheme; + private AudioEngineImpl mEngine; private ConcurrentHashMap mSourceIds; private float mMasterVolume = 1.0f; private static ConcurrentHashMap mEngines = new ConcurrentHashMap<>(); @@ -52,14 +47,24 @@ public interface AudioTheme { String getPath(Sound aSound); } + public interface AudioEngineImpl { + void preloadAsync(final Runnable aCallback); + void pause(); + void resume(); + void setPose(float qx, float qy, float qz, float qw, float px, float py, float pz); + void update(); + void release(); + void playSound(Sound aSound, float aVolume, boolean aLoop); + void stopSound(Sound aSound); + } + public static AudioEngine fromContext(Context aContext) { return mEngines.get(aContext); } - public AudioEngine(Context aContext, AudioTheme aTheme) { + public AudioEngine(Context aContext, AudioEngineImpl aImpl) { mContext = aContext; - mTheme = aTheme; - mEngine = new GvrAudioEngine(aContext, GvrAudioEngine.RenderingMode.BINAURAL_HIGH_QUALITY); + mEngine = aImpl; mSourceIds = new ConcurrentHashMap<>(); mEngines.put(aContext, this); mEnabled = true; @@ -69,20 +74,6 @@ public void setEnabled(boolean enabled) { mEnabled = enabled; } - private void preload() { - for (Sound sound: Sound.values()) { - if (sound.getType() == SoundType.FIELD) { - // Ambisonic soundfields do *not* need to be preloaded - // They are directly streamed and rendered from the compressed audio file. - // The handle automatically destroys itself at the moment the sound playback has stopped. - continue; - } - String path = mTheme.getPath(sound); - if (path != null && path.length() > 0) { - preloadFile(path); - } - } - } public void preloadAsync() { preloadAsync(null); @@ -90,156 +81,59 @@ public void preloadAsync() { // Perform preloading in a separate thread in order to avoid blocking the main thread public void preloadAsync(final Runnable aCallback) { - if (mEnabled) { - Thread thread = new Thread(new Runnable() { - @Override - public void run() { - preload(); - if (aCallback != null) { - ((Activity) mContext).runOnUiThread(aCallback); - } - } - }); - thread.start(); + if (mEngine != null) { + mEngine.preloadAsync(aCallback); } } public void release() { - mSourceIds.clear(); - mEngines.remove(mContext); - for (Sound sound: Sound.values()) { - String path = mTheme.getPath(sound); - if (path != null && sound.getType() != SoundType.FIELD) { - mEngine.unloadSoundFile(mTheme.getPath(sound)); - } + if (mEngine != null) { + mEngine.release(); } } public void pauseEngine() { - mEngine.pause(); - } - - public void resumeEngine() { - mEngine.resume(); - } - - public void setPose(float qx, float qy, float qz, float qw, float px, float py, float pz) { - mEngine.setHeadRotation(qx, qy, qz, qw); - mEngine.setHeadPosition(px, py, pz); - } - - public void update() { - mEngine.update(); - } - - public void playSound(Sound aSound) { - if (mEnabled) { - playSound(aSound, false); - } - } - - private void playSound(Sound aSound, boolean aLoopEnabled) { - String path = mTheme.getPath(aSound); - if (path == null || path.length() == 0) { - return; - } - int sourceId = createSound(aSound.getType(), mTheme.getPath(aSound)); - if (sourceId != GvrAudioEngine.INVALID_ID) { - mSourceIds.put(aSound, sourceId); - playSound(sourceId, aLoopEnabled); - } - } - - private void playSound(int aSourceId, boolean aLoopEnabled) { - mEngine.playSound(aSourceId, aLoopEnabled); - } - - public void pauseSound(Sound aSound) { - if (mEnabled) { - Integer sourceId = findSourceId(aSound); - if (sourceId != null) { - pauseSound(sourceId); - } + if (mEngine != null) { + mEngine.pause(); } } - private void pauseSound(int aSourceId) { - mEngine.pauseSound(aSourceId); - } - - private void resumeSound(Sound aSound) { - Integer sourceId = findSourceId(aSound); - if (sourceId != null) { - resumeSound(sourceId); + public void resumeEngine() { + if (mEngine != null) { + mEngine.resume(); } } - public void resumeSound(int aSourceId) { - if (mEnabled) { - mEngine.stopSound(aSourceId); + public void setPose(float qx, float qy, float qz, float qw, float px, float py, float pz) { + if (mEngine != null) { + mEngine.setPose(qx, qy, qz, qw, px, py, pz); } } - private void setSoundPosition(Sound aSound, float x, float y, float z) { - if (aSound.getType() != SoundType.OBJECT) { - Log.e(LOGTAG, "Sound position can only be set for SoundType.Object!"); - return; - } - Integer sourceId = findSourceId(aSound); - if (sourceId != null) { - setSoundPosition(sourceId, x, y, z); + public void update() { + if (mEngine != null) { + mEngine.update(); } } - public void setSoundPosition(int aSoundObjectId, float x, float y, float z) { - if (mEnabled) { - mEngine.setSoundObjectPosition(aSoundObjectId, x, y, z); - } + public void playSound(Sound aSound) { + playSound(aSound, 1.0f,false); } - private void setSoundVolume(Sound aSound, float aVolume) { - Integer sourceId = findSourceId(aSound); - if (sourceId != null) { - setSoundVolume(sourceId, aVolume); - } + public void setMasterVolume(float aVolume) { + mMasterVolume = aVolume; } - public void setSoundVolume(int aSourceId, float aVolume) { - if (mEnabled) { - mEngine.setSoundVolume(aSourceId, aVolume * mMasterVolume); + public void playSound(Sound aSound, float aVolume, boolean aLoop) { + if (mEnabled && mEngine != null) { + mEngine.playSound(aSound, aVolume * mMasterVolume, aLoop); } } - private void setMasterVolume(float aMasterVolume) { - mMasterVolume = aMasterVolume; - } - - - private int createSound(SoundType aType, String path) { - mEngine.preloadSoundFile(path); - int sourceId = GvrAudioEngine.INVALID_ID; - switch (aType) { - case FIELD: sourceId = mEngine.createSoundfield(path); break; - case OBJECT: sourceId = mEngine.createSoundObject(path); break; - case STEREO: sourceId = mEngine.createStereoSound(path); break; + public void stopSound(Sound aSound) { + if (mEnabled && mEngine != null) { + mEngine.stopSound(aSound); } - - if (sourceId == GvrAudioEngine.INVALID_ID) { - Log.e(LOGTAG, "Error loading sound from path: " + path); - } - - return sourceId; } - private void preloadFile(String path) { - mEngine.preloadSoundFile(path); - } - - private void unloadFile(String path) { - mEngine.unloadSoundFile(path); - } - - private Integer findSourceId(Sound aSound) { - return mSourceIds.get(aSound); - } } diff --git a/app/src/common/shared/org/mozilla/vrbrowser/audio/AudioEngineGVR.java b/app/src/common/shared/org/mozilla/vrbrowser/audio/AudioEngineGVR.java new file mode 100644 index 000000000..9436a0c0f --- /dev/null +++ b/app/src/common/shared/org/mozilla/vrbrowser/audio/AudioEngineGVR.java @@ -0,0 +1,159 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.vrbrowser.audio; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; + +import com.google.vr.sdk.audio.GvrAudioEngine; + +import java.util.concurrent.ConcurrentHashMap; + +public class AudioEngineGVR implements AudioEngine.AudioEngineImpl { + private Context mContext; + private GvrAudioEngine mEngine; + private AudioEngine.AudioTheme mTheme; + private ConcurrentHashMap mSourceIds; + private static final String LOGTAG = "VRB"; + + + public AudioEngineGVR(Context aContext, AudioEngine.AudioTheme aTheme) { + mContext = aContext; + mTheme = aTheme; + mEngine = new GvrAudioEngine(aContext, GvrAudioEngine.RenderingMode.BINAURAL_HIGH_QUALITY); + mSourceIds = new ConcurrentHashMap<>(); + } + + private void preload() { + for (AudioEngine.Sound sound: AudioEngine.Sound.values()) { + if (sound.getType() == AudioEngine.SoundType.FIELD) { + // Ambisonic soundfields do *not* need to be preloaded + // They are directly streamed and rendered from the compressed audio file. + // The handle automatically destroys itself at the moment the sound playback has stopped. + continue; + } + String path = mTheme.getPath(sound); + if (path != null && path.length() > 0) { + preloadFile(path); + } + } + } + + @Override + public void preloadAsync(final Runnable aCallback) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + preload(); + if (aCallback != null) { + ((Activity) mContext).runOnUiThread(aCallback); + } + } + }); + thread.start(); + } + + @Override + public void release() { + mSourceIds.clear(); + for (AudioEngine.Sound sound: AudioEngine.Sound.values()) { + String path = mTheme.getPath(sound); + if (path != null && sound.getType() != AudioEngine.SoundType.FIELD) { + mEngine.unloadSoundFile(mTheme.getPath(sound)); + } + } + } + + @Override + public void pause() { + mEngine.pause(); + } + + @Override + public void resume() { + mEngine.resume(); + } + + @Override + public void setPose(float qx, float qy, float qz, float qw, float px, float py, float pz) { + mEngine.setHeadRotation(qx, qy, qz, qw); + mEngine.setHeadPosition(px, py, pz); + } + + @Override + public void update() { + mEngine.update(); + } + + + @Override + public void playSound(AudioEngine.Sound aSound, float aVolume, boolean aLoop) { + String path = mTheme.getPath(aSound); + if (path == null || path.length() == 0) { + return; + } + int sourceId = createSound(aSound.getType(), mTheme.getPath(aSound)); + if (sourceId != GvrAudioEngine.INVALID_ID) { + mSourceIds.put(aSound, sourceId); + mEngine.playSound(sourceId, aLoop); + mEngine.setSoundVolume(sourceId, aVolume); + } + } + + @Override + public void stopSound(AudioEngine.Sound aSound) { + Integer sourceId = findSourceId(aSound); + if (sourceId != null) { + mEngine.stopSound(sourceId); + } + } + + + private void setSoundPosition(AudioEngine.Sound aSound, float x, float y, float z) { + if (aSound.getType() != AudioEngine.SoundType.OBJECT) { + Log.e(LOGTAG, "Sound position can only be set for SoundType.Object!"); + return; + } + Integer sourceId = findSourceId(aSound); + if (sourceId != null) { + setSoundPosition(sourceId, x, y, z); + } + } + + private void setSoundPosition(int aSoundObjectId, float x, float y, float z) { + mEngine.setSoundObjectPosition(aSoundObjectId, x, y, z); + } + + + private int createSound(AudioEngine.SoundType aType, String path) { + mEngine.preloadSoundFile(path); + int sourceId = GvrAudioEngine.INVALID_ID; + switch (aType) { + case FIELD: sourceId = mEngine.createSoundfield(path); break; + case OBJECT: sourceId = mEngine.createSoundObject(path); break; + case STEREO: sourceId = mEngine.createStereoSound(path); break; + } + + if (sourceId == GvrAudioEngine.INVALID_ID) { + Log.e(LOGTAG, "Error loading sound from path: " + path); + } + + return sourceId; + } + + private void preloadFile(String path) { + mEngine.preloadSoundFile(path); + } + + private void unloadFile(String path) { + mEngine.unloadSoundFile(path); + } + + private Integer findSourceId(AudioEngine.Sound aSound) { + return mSourceIds.get(aSound); + } +}