-# this file is part of NIT ( http://www.nitlanguage.org ).
+# This file is part of NIT ( http://www.nitlanguage.org ).
#
# Copyright 2014 Romain Chanoir <romain.chanoir@viacesi.fr>
#
# See the License for the specific language governing permissions and
# limitations under the License.
-# Android audio services
+# Android audio services, wraps a part of android audio API
+# This module modifies the default behaviour of the audio loading:
+# It is first loaded from the `res/raw` folder.
+# The file extension is not needed for the `res/raw` loading part.
+# If it didn't work, it is loaded from the `assets` folder.
+# The file extension is needed for the `assets` loading part.
#
-# You can get a sound by loading it with a `SoundPool` or a `MediaPlayer`
-# the recommended way to load a sound is by it's resource ID or it's name
-# other ways are for advanced use
+# `assets` contains the portable version of sounds, since the `res` folder exsists only in android projects.
+#
+# For this example, the sounds "test_sound" and "test_music" are located in the "assets/sounds" folder,
+# they both have ".ogg" extension. "test_sound" is a short sound and "test_music" a music track
+#
+# ~~~nitish
+# # Note that you need to specify the path from "assets" folder and the extension
+# var s = app.load_sound("sounds/test_sound.ogg")
+# var m = app.load_music("sounds/test_music.ogg")
+# s.play
+# m.play
+# ~~~
+#
+# Now, the sounds are in "res/raw"
+# ~~~nitish
+# s = app.load_sound_from_res("test_sound")
+# m = app.load_music_from_res("test_sound")
+# s.play
+# m.play
+# ~~~
+#
+# See http://developer.android.com/reference/android/media/package-summary.html for more infos
module audio
import java
import java::io
-import assets_and_resources
-import app
+intrude import assets_and_resources
+import activities
+import app::audio
in "Java" `{
import android.media.MediaPlayer;
import android.media.SoundPool;
import java.io.IOException;
import android.media.AudioManager;
+ import android.media.AudioManager.OnAudioFocusChangeListener;
import android.content.Context;
import android.util.Log;
`}
+# FIXME: This listener is not working at the moment, but is needed to gain or give up the audio focus
+# of the application
+in "Java inner" `{
+ static OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
+ public void onAudioFocusChange(int focusChange) {
+ if(focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
+ }else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ }else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
+ }
+ }
+ };
+`}
+
# AudioManager of the application, used to manage the audio mode
-extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
+private extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
super JavaObject
- redef type SELF: NativeAudioManager
-
- fun mode: Int in "Java" `{ return recv.getMode(); `}
- fun mode=(i: Int) in "Java" `{ recv.setMode((int)i); `}
- fun wired_headset_on: Bool in "Java" `{ return recv.isWiredHeadsetOn(); `}
- fun wired_headset_on=(b: Bool) in "Java" `{ recv.setWiredHeadsetOn(b); `}
- fun speakerphone_on: Bool in "Java" `{ return recv.isSpeakerphoneOn(); `}
- fun speakerphone_on=(b: Bool) in "Java" `{ recv.setSpeakerphoneOn(b); `}
- fun manage_audio_mode in "Java" `{
- recv.setMode(0);
- if (recv.isWiredHeadsetOn()) {
- recv.setSpeakerphoneOn(false);
- } else {
- recv.setSpeakerphoneOn(true);
- }
+
+ # Current audio mode.
+ # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
+ fun mode: Int in "Java" `{ return self.getMode(); `}
+
+ # Sets the audio mode.
+ # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
+ fun mode=(i: Int) in "Java" `{ self.setMode((int)i); `}
+
+ # Sends a request to obtain audio focus
+ fun request_audio_focus: Int in "Java" `{
+ return self.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
`}
+ # Gives up audio focus
+ fun abandon_audio_focus: Int in "Java" `{ return self.abandonAudioFocus(afChangeListener); `}
end
# Media Player from Java, used to play long sounds or musics, not simultaneously
# This is a low-level class, use `MediaPlater` instead
-extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
+private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
super JavaObject
- redef type SELF: NativeMediaPlayer
- new in "Java" `{ return new MediaPlayer(); `}
- fun start in "Java" `{ recv.start(); `}
+ new in "Java" `{
+ MediaPlayer mp = new MediaPlayer();
+ mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ return mp;
+ `}
+ fun start in "Java" `{ self.start(); `}
fun prepare in "Java" `{
try {
- recv.prepare();
- }catch(IOException e) {
+ self.prepare();
+ }catch(Exception e) {
Log.e("Error preparing the Media Player", e.getMessage());
e.printStackTrace();
}
`}
- fun create(context: NativeActivity, id: Int): NativeMediaPlayer in "Java" `{ return recv.create(context, (int)id); `}
- fun pause in "Java" `{ recv.pause(); `}
- fun stop in "Java" `{ recv.stop(); `}
- fun playing: Bool in "Java" `{ return recv.isPlaying(); `}
- fun release in "Java" `{ recv.release(); `}
- fun duration: Int in "Java" `{ return recv.getDuration(); `}
- fun looping: Bool in "Java" `{ return recv.isLooping(); `}
- fun looping=(b: Bool) in "Java" `{ recv.setLooping(b); `}
- fun volume=(vol: Float) in "Java" `{ recv.setVolume((float)vol, (float)vol); `}
- fun both_volume(left_volume, right_volume: Float) in "Java" `{ recv.setVolume((float)left_volume, (float)right_volume); `}
- fun stream_type=(stream_type: Int) in "Java" `{ recv.setAudioStreamType((int)stream_type); `}
- fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int) in "Java" `{
+ fun create(context: NativeActivity, id: Int): NativeMediaPlayer in "Java" `{
try {
- recv.setDataSource(fd, start_offset, length);
- }catch(IOException e) {
- Log.e("Error loading the Media Player with a file descriptor", e.getMessage());
- e.printStackTrace();
+ return self.create(context, (int)id);
+ }catch(Exception e) {
+ return null;
+ }
+ `}
+
+ fun pause in "Java" `{ self.pause(); `}
+ fun stop in "Java" `{ self.stop(); `}
+ fun playing: Bool in "Java" `{ return self.isPlaying(); `}
+ fun release in "Java" `{ self.release(); `}
+ fun duration: Int in "Java" `{ return self.getDuration(); `}
+ fun looping: Bool in "Java" `{ return self.isLooping(); `}
+ fun looping=(b: Bool) in "Java" `{ self.setLooping(b); `}
+ fun volume=(vol: Float) in "Java" `{ self.setVolume((float)vol, (float)vol); `}
+ fun both_volume(left_volume, right_volume: Float) in "Java" `{ self.setVolume((float)left_volume, (float)right_volume); `}
+ fun stream_type=(stream_type: Int) in "Java" `{ self.setAudioStreamType((int)stream_type); `}
+ fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int): Int in "Java" `{
+ try {
+ self.setDataSource(fd, start_offset, length);
+ return 1;
+ }catch(Exception e) {
+ return 0;
}
`}
- fun data_source_path(path: JavaString) in "Java" `{
+ fun data_source_path(path: JavaString): Int in "Java" `{
try {
- recv.setDataSource(path);
- }catch(IOException e) {
+ self.setDataSource(path);
+ return 1;
+ }catch(Exception e) {
Log.e("Error loading the Media Player", e.getMessage());
- e.printStackTrace();
+ return 0;
}
`}
- fun reset in "Java" `{ recv.reset(); `}
+ fun reset in "Java" `{ self.reset(); `}
end
# Sound Pool from Java, used to play sounds simultaneously
# This is a low-level class, use `SoundPool`instead
-extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
+private extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
super JavaObject
- redef type SELF: NativeSoundPool
new(max_streams, stream_type, src_quality: Int) in "Java" `{
return new SoundPool((int)max_streams, (int)stream_type, (int)src_quality);
`}
- fun load_asset_fd(afd: NativeAssetFileDescriptor, priority: Int): Int in "Java" `{ return recv.load(afd, (int)priority); `}
- fun load_id(context: NativeActivity, resid, priority: Int): Int in "Java" `{ return recv.load(context, (int)resid, (int)priority); `}
- fun load_path(path: JavaString, priority: Int): Int in "Java" `{ return recv.load(path, (int)priority); `}
+ fun load_asset_fd(afd: NativeAssetFileDescriptor, priority: Int): Int in "Java" `{
+ try {
+ int id = self.load(afd, (int)priority);
+ return id;
+ }catch(Exception e){
+ return -1;
+ }
+ `}
+
+ fun load_id(context: NativeActivity, resid, priority: Int): Int in "Java" `{
+ try {
+ int id = self.load(context, (int)resid, (int)priority);
+ return id;
+ }catch(Exception e){
+ return -1;
+ }
+ `}
+
+ fun load_path(path: JavaString, priority: Int): Int in "Java" `{
+ try {
+ int id = self.load(path, (int)priority);
+ return id;
+ }catch(Exception e){
+ return -1;
+ }
+ `}
+
fun play(sound_id: Int, left_volume, right_volume: Float, priority, l: Int, rate: Float): Int in "Java" `{
- return recv.play((int)sound_id, (float)left_volume, (float)right_volume, (int)priority, (int)l, (float)rate);
+ return self.play((int)sound_id, (float)left_volume, (float)right_volume, (int)priority, (int)l, (float)rate);
`}
- fun pause(stream_id: Int) in "Java" `{ recv.pause((int)stream_id); `}
- fun auto_pause in "Java" `{ recv.autoPause(); `}
- fun auto_resume in "Java" `{ recv.autoResume(); `}
- fun resume(stream_id: Int) in "Java" `{ recv.resume((int)stream_id); `}
- fun set_loop(stream_id, l: Int) in "Java" `{ recv.setLoop((int)stream_id, (int)l); `}
- fun set_priority(stream_id, priority: Int) in "Java" `{ recv.setPriority((int)stream_id, (int)priority); `}
- fun set_rate(stream_id: Int, rate: Float) in "Java" `{ recv.setRate((int)stream_id, (float)rate); `}
- fun set_volume(stream_id: Int, left_volume, right_volume: Float) in "Java" `{ recv.setVolume((int)stream_id, (float)left_volume, (float)right_volume); `}
- fun stop(stream_id: Int) in "Java" `{ recv.stop((int)stream_id); `}
- fun unload(sound_id: Int): Bool in "Java" `{ return recv.unload((int)sound_id); `}
- fun release in "Java" `{ recv.release(); `}
+ fun pause(stream_id: Int) in "Java" `{ self.pause((int)stream_id); `}
+ fun auto_pause in "Java" `{ self.autoPause(); `}
+ fun auto_resume in "Java" `{ self.autoResume(); `}
+ fun resume(stream_id: Int) in "Java" `{ self.resume((int)stream_id); `}
+ fun set_loop(stream_id, l: Int) in "Java" `{ self.setLoop((int)stream_id, (int)l); `}
+ fun set_priority(stream_id, priority: Int) in "Java" `{ self.setPriority((int)stream_id, (int)priority); `}
+ fun set_rate(stream_id: Int, rate: Float) in "Java" `{ self.setRate((int)stream_id, (float)rate); `}
+ fun set_volume(stream_id: Int, left_volume, right_volume: Float) in "Java" `{ self.setVolume((int)stream_id, (float)left_volume, (float)right_volume); `}
+ fun stop(stream_id: Int) in "Java" `{ self.stop((int)stream_id); `}
+ fun unload(sound_id: Int): Bool in "Java" `{ return self.unload((int)sound_id); `}
+ fun release in "Java" `{ self.release(); `}
end
# Used to play sound, best suited for sounds effects in apps or games
class SoundPool
+
+ # Error gestion
+ var error: nullable Error = null
+
private var nsoundpool: NativeSoundPool is noinit
# The maximum number of simultaneous streams for this SoundPool
var max_streams = 10 is writable
# Load the sound from an asset file descriptor
# this function is for advanced use
- fun load_asset_fd(afd: NativeAssetFileDescriptor): Sound do
- return new SoundSP(null, nsoundpool.load_asset_fd(afd, priority), self)
+ private fun load_asset_fd(afd: NativeAssetFileDescriptor): Sound do
+ var resval = nsoundpool.load_asset_fd(afd, self.priority)
+ if resval == -1 then
+ self.error = new Error("Unable to load sound from assets")
+ return new Sound.priv_init(null, -1, self, self.error)
+ else
+ return new Sound.priv_init(null, resval, self, null)
+ end
end
- # Load the sound from it's resource id
+ # Returns only the id corresponding to the soundpool where the sound is loaded.
+ # Needed by `load` of `Sound`.
+ private fun load_asset_fd_rid(afd: NativeAssetFileDescriptor): Int do
+ return nsoundpool.load_asset_fd(afd, self.priority)
+ end
+
+ # Load the sound from its resource id
fun load_id(context: NativeActivity, id:Int): Sound do
- return new SoundSP(null, nsoundpool.load_id(context, id, priority), self)
+ var resval = nsoundpool.load_id(context, id, priority)
+ if resval == -1 then
+ self.error = new Error("Unable to load sound from assets")
+ return new Sound.priv_init(null, -1, self, self.error)
+ else
+ return new Sound.priv_init(null, resval, self, null)
+ end
+ end
+
+ # Returns only the id corresponding to the soundpool where the sound is loaded.
+ private fun load_id_rid(context: NativeActivity, id: Int): Int do
+ return nsoundpool.load_id(context, id, priority)
end
# Load the sound from the specified path
fun load_path(path: String): Sound do
sys.jni_env.push_local_frame(1)
- var return_value = new SoundSP(0, nsoundpool.load_path(path.to_java_string, priority), self)
+ var resval = nsoundpool.load_path(path.to_java_string, priority)
sys.jni_env.pop_local_frame
- return return_value
+ if resval == -1 then
+ self.error = new Error("Unable to load sound from path : " + path)
+ return new Sound.priv_init(null, -1, self, self.error)
+ else
+ return new Sound.priv_init(null, resval, self, null)
+ end
end
# Play a sound from a sound ID
return nsoundpool.play(id, left_volume, right_volume, priority, looping, rate)
end
- # Load a sound by it's name in the resources, the sound must be in the `res/raw` folder
+ # Load a sound by its name in the resources, the sound must be in the `res/raw` folder
fun load_name(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Sound do
var id = resource_manager.raw_id(sound)
- return new SoundSP(id, nsoundpool.load_id(context, id, priority), self)
+ var resval = nsoundpool.load_id(context, id, priority)
+ if resval == -1 then
+ self.error = new Error("Unable to load sound from resources : " + sound)
+ return new Sound.priv_init(null, -1, self, self.error)
+ else
+ return new Sound.priv_init(null, resval, self, null)
+ end
+ end
+
+ # Returns only the id corresponding to the soundpool where the sound is loaded.
+ private fun load_name_rid(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Int do
+ var id = resource_manager.raw_id(sound)
+ return nsoundpool.load_id(context, id, priority)
end
# Pause a playback stream
fun stop_stream(stream_id: Int) do nsoundpool.stop(stream_id)
# Unload a sound from a sound ID
- fun unload(sound: SoundSP): Bool do return nsoundpool.unload(sound.soundpool_id)
+ fun unload(sound: Sound): Bool do return nsoundpool.unload(sound.soundpool_id)
+ # Destroys the object
fun destroy do nsoundpool.release
end
# The Android MediaPlayer has a complex state diagram that you'll need to
# respect if you want your MediaPlayer to work fine, see the android doc
class MediaPlayer
- private var nmedia_player: NativeMediaPlayer
+ private var nmedia_player: NativeMediaPlayer is noinit
+
+ # Used to control the state of the mediaplayer
+ private var is_prepared = false is writable
# The sound associated with this mediaplayer
- var sound: nullable Sound
+ var sound: nullable Music = null is writable
+
+ # Error gestion
+ var error: nullable Error = null
# Create a new MediaPlayer, but no sound is attached, you'll need
# to use `load_sound` before using it
init from_id(context: NativeActivity, id: Int) do
self.nmedia_player = new NativeMediaPlayer
self.nmedia_player = nmedia_player.create(context, id)
- self.sound = new SoundMP(id, self)
+ if self.nmedia_player.is_java_null then
+ self.error = new Error("Failed to create the MediaPlayer")
+ self.sound = new Music.priv_init(id, self, self.error)
+ end
+ self.sound = new Music.priv_init(id, self, null)
end
# Load a sound for a given resource id
- fun load_sound(id: Int, context: NativeActivity): Sound do
+ fun load_sound(id: Int, context: NativeActivity): Music do
+ # FIXME: maybe find a better way to handle this situation
+ # If two different music are loaded with the same `MediaPlayer`,
+ # a new `NativeMediaPlayer` will be created for the secondd music
+ # and the nit program will loose the handle to the previous one
+ # If the previous music is playing, we need to stop it
+ if playing then
+ stop
+ reset
+ destroy
+ end
self.nmedia_player = self.nmedia_player.create(context, id)
- self.sound = new SoundMP(id, self)
- return self.sound.as(not null)
+ if self.nmedia_player.is_java_null then
+ self.error = new Error("Failed to load a sound")
+ self.sound = new Music.priv_init(id, self, new Error("Sound loading failed"))
+ return self.sound.as(not null)
+ else
+ if self.error != null then self.error = null
+ self.sound = new Music.priv_init(id, self, null)
+ self.is_prepared = true
+ return self.sound.as(not null)
+ end
end
- # Starts or resume playback
+ # Starts or resumes playback
# REQUIRE `self.sound != null`
fun start do
- assert sound != null
+ if self.error != null then return
+ if not is_prepared then prepare
nmedia_player.start
end
# Stops playback after playback has been stopped or paused
# REQUIRE `self.sound != null`
fun stop do
- assert sound != null
+ if self.error != null then return
+ is_prepared = false
nmedia_player.stop
end
# Prepares the player for playback, synchronously
# REQUIRE `self.sound != null`
fun prepare do
+ if self.error != null then return
assert sound != null
nmedia_player.prepare
+ is_prepared = true
end
# Pauses playback
# REQUIRE `self.sound != null`
fun pause do
+ if self.error != null then return
assert sound != null
nmedia_player.pause
end
# Reset MediaPlayer to its initial state
fun reset do nmedia_player.reset
- # Sets the datasource (file-pathor http/rtsp URL) to use
- fun data_source(path: String): Sound do
+ # Sets the datasource (file-path or http/rtsp URL) to use
+ fun data_source(path: String): Music do
sys.jni_env.push_local_frame(1)
- nmedia_player.data_source_path(path.to_java_string)
+ var retval = nmedia_player.data_source_path(path.to_java_string)
sys.jni_env.pop_local_frame
- self.sound = new SoundMP(null, self)
+ if retval == 0 then
+ self.error = new Error("could not load the sound " + path)
+ self.sound = new Music.priv_init(null, self, self.error)
+
+ else
+ self.sound = new Music.priv_init(null, self, null)
+ end
return self.sound.as(not null)
end
# Sets the data source (NativeFileDescriptor) to use
- fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int): Sound do
- nmedia_player.data_source_fd(fd, start_offset, length)
- self.sound = new SoundMP(null, self)
- return self.sound.as(not null)
+ fun data_source_fd(fd: NativeAssetFileDescriptor): Music do
+ if not fd.is_java_null then
+ if nmedia_player.data_source_fd(fd.file_descriptor, fd.start_offset, fd.length) == 0 then
+ self.error = new Error("could not load the sound")
+ self.sound = new Music.priv_init(null, self, self.error)
+ else
+ self.sound = new Music.priv_init(null, self, null)
+ end
+ return self.sound.as(not null)
+ else
+ var error = new Error("could not load the sound")
+ return new Music.priv_init(null, self, error)
+ end
end
# Checks whether the MediaPlayer is looping or non-looping
fun stream_type=(stream_type: Int) do nmedia_player.stream_type = stream_type
end
-# Represents an android sound that can be played by a SoundPool or a MediaPlayer
-# The only way to get a sound is by a MediaPlayer, a SoundPool, or the App
-abstract class Sound
-
- # The resource ID of this sound
- var id: nullable Int
+redef class PlayableAudio
+ # Flag to know if the user paused the sound
+ # Used when the app pause all sounds or resume all sounds
+ var paused: Bool = false
- fun play is abstract
+ redef init do add_to_sounds(self)
end
-# Sound implemented with a SoundPool
-class SoundSP
- super Sound
+redef class Sound
+
+ # Resource ID of this sound
+ var id: nullable Int is noinit
# The SoundPool who loaded this sound
- var soundpool: SoundPool
+ var soundpool: SoundPool is noinit
# The SoundID of this sound in his SoundPool
- var soundpool_id: Int
+ var soundpool_id: Int is noinit
- private init (id: nullable Int, soundpool_id: Int, soundpool: SoundPool) do
+ private init priv_init(id: nullable Int, soundpool_id: Int, soundpool: SoundPool, error: nullable Error) is nosuper do
self.id = id
self.soundpool_id = soundpool_id
self.soundpool = soundpool
+ if error != null then
+ self.error = error
+ else
+ self.is_loaded = true
+ end
+ end
+
+ redef fun load do
+ if is_loaded then return
+ var retval_resources = app.default_soundpool.load_name_rid(app.resource_manager, app.native_activity, self.name.strip_extension)
+ if retval_resources == -1 then
+ self.error = new Error("failed to load" + self.name)
+ var nam = app.asset_manager.open_fd(self.name)
+ if nam.is_java_null then
+ self.error = new Error("Failed to get file descriptor for " + self.name)
+ else
+ var retval_assets = app.default_soundpool.load_asset_fd_rid(nam)
+ if retval_assets == -1 then
+ self.error = new Error("Failed to load" + self.name)
+ else
+ self.soundpool_id = retval_assets
+ self.soundpool = app.default_soundpool
+ self.error = null
+ self.soundpool.error = null
+ end
+ end
+ else
+ self.soundpool_id = retval_resources
+ self.soundpool = app.default_soundpool
+ self.error = null
+ self.soundpool.error = null
+ end
+ is_loaded = true
+ end
+
+ redef fun play do
+ if not is_loaded then load
+ if self.error != null then return
+ soundpool.play(soundpool_id)
+ end
+
+ redef fun pause do
+ if self.error != null or not self.is_loaded then return
+ soundpool.pause_stream(soundpool_id)
+ paused = true
+ end
+
+ redef fun resume do
+ if self.error != null or not self.is_loaded then return
+ soundpool.resume(soundpool_id)
+ paused = false
end
- redef fun play do soundpool.play(soundpool_id)
end
-# Sound Implemented with a MediaPlayer
-class SoundMP
- super Sound
+redef class Music
+
+ # Resource ID of this sound
+ var id: nullable Int is noinit
# The MediaPlayer who loaded this sound
- var media_player: MediaPlayer
+ var media_player: MediaPlayer is noinit
- private init (id: nullable Int, media_player: MediaPlayer) do
+ private init priv_init(id: nullable Int, media_player: MediaPlayer, error: nullable Error) is nosuper do
self.id = id
self.media_player = media_player
+ if error != null then
+ self.error = error
+ else
+ self.is_loaded = true
+ end
+ end
+
+ redef fun load do
+ if is_loaded then return
+ var mp_sound_resources = app.default_mediaplayer.load_sound(app.resource_manager.raw_id(self.name.strip_extension), app.native_activity)
+ if mp_sound_resources.error != null then
+ self.error = mp_sound_resources.error
+ var nam = app.asset_manager.open_fd(self.name)
+ if nam.is_java_null then
+ self.error = new Error("Failed to get file descriptor for " + self.name)
+ else
+ var mp_sound_assets = app.default_mediaplayer.data_source_fd(nam)
+ if mp_sound_assets.error != null then
+ self.error = mp_sound_assets.error
+ else
+ self.media_player = app.default_mediaplayer
+ self.error = null
+ self.media_player.error = null
+ end
+ end
+ else
+ self.media_player = app.default_mediaplayer
+ self.error = null
+ self.media_player.error = null
+ end
+ is_loaded = true
+ end
+
+ redef fun play do
+ if not is_loaded then load
+ if self.error != null then return
+ media_player.start
+ end
+
+ redef fun pause do
+ if self.error != null or not self.is_loaded then return
+ media_player.pause
+ paused = true
end
- redef fun play do self.media_player.start
+ redef fun resume do
+ if self.error != null or not self.is_loaded then return
+ play
+ paused = false
+ end
end
redef class App
- fun default_mediaplayer: MediaPlayer is cached do return new MediaPlayer
- fun default_soundpool: SoundPool is cached do return new SoundPool
+
+ # Returns the default MediaPlayer of the application.
+ # When you load a music, it goes in this MediaPlayer.
+ # Use it for advanced sound management
+ var default_mediaplayer: MediaPlayer is lazy do return new MediaPlayer
+
+ # Returns the default MediaPlayer of the application.
+ # When you load a short sound (not a music), it's added to this soundpool.
+ # Use it for advanced sound management.
+ var default_soundpool: SoundPool is lazy do return new SoundPool
# Get the native audio manager
- private fun audio_manager: NativeAudioManager import native_activity in "Java" `{
- return (AudioManager)App_native_activity(recv).getSystemService(Context.AUDIO_SERVICE);
+ fun audio_manager: NativeAudioManager import native_activity in "Java" `{
+ return (AudioManager)App_native_activity(self).getSystemService(Context.AUDIO_SERVICE);
`}
- # Manages whether the app sound need to be in headphones mode or not
- # TODO: this method is not ideal, need to find a better way
- fun manage_audio_mode import native_activity in "Java" `{
- AudioManager manager = (AudioManager)App_native_activity(recv).getSystemService(Context.AUDIO_SERVICE);
- manager.setMode(0);
- if (manager.isWiredHeadsetOn()) {
- manager.setSpeakerphoneOn(false);
- } else {
- manager.setSpeakerphoneOn(true);
- }
+ # Sets the stream of the app to STREAM_MUSIC.
+ # STREAM_MUSIC is the default stream used by android apps.
+ private fun manage_audio_stream import native_activity in "Java" `{
+ App_native_activity(self).setVolumeControlStream(AudioManager.STREAM_MUSIC);
`}
- # Retrieve a sound with a soundpool in the `assets` folder using it's name
+ # Retrieves a sound with a soundpool in the `assets` folder using its name.
# Used to play short songs, can play multiple sounds simultaneously
- fun load_sound(path: String): Sound do
- return default_soundpool.load_asset_fd(asset_manager.open_fd(path))
+ redef fun load_sound(path) do
+ var fd = asset_manager.open_fd(path)
+ if not fd.is_java_null then
+ return add_to_sounds(default_soundpool.load_asset_fd(fd)).as(Sound)
+ else
+ var error = new Error("Failed to load Sound {path}")
+ return new Sound.priv_init(null, -1, default_soundpool, error)
+ end
end
- # Retrieve a music with a media player in the `assets`folder using it's name
+ # Retrieves a music with a media player in the `assets` folder using its name.
# Used to play long sounds or musics, can't play multiple sounds simultaneously
- fun load_music(path: String): Sound do
+ redef fun load_music(path) do
var fd = asset_manager.open_fd(path)
- var sound = default_mediaplayer.data_source_fd(fd.file_descriptor, fd.start_offset, fd.length)
- return sound
+ if not fd.is_java_null then
+ return add_to_sounds(default_mediaplayer.data_source_fd(fd)).as(Music)
+ else
+ var error = new Error("Failed to load music {path}")
+ return new Music.priv_init(null, default_mediaplayer, error)
+ end
end
- # same as `load_sound` but load the sound from the `res\raw` folder
+ # Same as `load_sound` but load the sound from the `res/raw` folder
fun load_sound_from_res(sound_name: String): Sound do
- return default_soundpool.load_name(resource_manager,self.native_activity, sound_name)
+ return add_to_sounds(default_soundpool.load_name(resource_manager,self.native_activity, sound_name)).as(Sound)
+ end
+
+ # Same as `load_music` but load the sound from the `res/raw` folder
+ fun load_music_from_res(music: String): Music do
+ return add_to_sounds(default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity)).as(Music)
+ end
+
+ redef fun on_pause do
+ super
+ for s in sounds do
+ # Pausing sounds that are not already paused by user
+ # `s.paused` is set to false because `pause` set it to true
+ # and we want to know which sound has been paused by the user
+ # and which one has been paused by the app
+ if not s.paused then
+ s.pause
+ s.paused = false
+ end
+ end
+ audio_manager.abandon_audio_focus
+ end
+
+ redef fun on_create do
+ super
+ audio_manager.request_audio_focus
+ manage_audio_stream
end
- # same as `load_music` but load the sound from the `res\raw` folder
- fun load_music_from_res(music: String): Sound do
- return default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity)
+ redef fun on_resume do
+ super
+ audio_manager.request_audio_focus
+ for s in sounds do
+ # Resumes only the sounds paused by the App
+ if not s.paused then s.resume
+ end
end
+end
+
+redef class Sys
+
+ # Sounds handled by the application, when you load a sound, it's added to this list.
+ # This array is used in `pause` and `resume`
+ private var sounds = new Array[PlayableAudio]
+ # Factorizes `sounds.add` to use it in `load_music`, `load_sound`, `load_music_from_res` and `load_sound_from_res`
+ private fun add_to_sounds(sound: PlayableAudio): PlayableAudio do
+ sounds.add(sound)
+ return sound
+ end
end