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.
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
var s = new Sound("sounds/test_sound.ogg")
var m = new Music("sounds/test_music.ogg")
s.play
m.play
Now, the sounds are in "res/raw"
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
core :: union_find
union–find algorithm using an efficient disjoint-set data structure
# 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.
#
# `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 = new Sound("sounds/test_sound.ogg")
# var m = new 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
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
private extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
super JavaObject
# 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
private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
super JavaObject
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 {
self.prepare();
}catch(Exception e) {
Log.e("Error preparing the Media Player", e.getMessage());
e.printStackTrace();
}
`}
new create(context: NativeActivity, id: Int): NativeMediaPlayer
in "Java" `{
try {
return MediaPlayer.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): Int in "Java" `{
try {
self.setDataSource(path);
return 1;
}catch(Exception e) {
Log.e("Error loading the Media Player", e.getMessage());
return 0;
}
`}
fun reset in "Java" `{ self.reset(); `}
# HACK for bug #845
redef fun new_global_ref import sys, Sys.jni_env `{
Sys sys = NativeMediaPlayer_sys(self);
JNIEnv *env = Sys_jni_env(sys);
return (*env)->NewGlobalRef(env, self);
`}
end
# Sound Pool from Java, used to play sounds simultaneously
# This is a low-level class, use `SoundPool`instead
private extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
super JavaObject
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" `{
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 self.play((int)sound_id, (float)left_volume, (float)right_volume, (int)priority, (int)l, (float)rate);
`}
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(); `}
# HACK for bug #845
redef fun new_global_ref import sys, Sys.jni_env `{
Sys sys = NativeSoundPool_sys(self);
JNIEnv *env = Sys_jni_env(sys);
return (*env)->NewGlobalRef(env, self);
`}
end
# Used to play sound, best suited for sounds effects in apps or games
class SoundPool
# Latest error on this sound pool
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
# The audio stream type, 3 is STREAM_MUSIC, default for game application
var stream_type = 3 is writable
# The sample-rate converter quality, currently has no effect
var src_quality = 0 is writable
# Left volume value, range 0.0 to 1.0
var left_volume = 1.0 is writable
# Right volume value, range 0.0 to 1.0
var right_volume = 1.0 is writable
# Playback rate, 1.0 = normal playback, range 0.5 to 2.0
var rate = 1.0 is writable
# Loop mode, 0 = no loop, -1 = loop forever
var looping = 0 is writable
# Stream priority
private var priority = 1
init do self.nsoundpool = (new NativeSoundPool(max_streams, stream_type, src_quality)).new_global_ref
# Load the sound from an asset file descriptor
# this function is for advanced use
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
# 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
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 resval = nsoundpool.load_path(path.to_java_string, priority)
sys.jni_env.pop_local_frame
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 non-zero streamID if successful, zero if failed
fun play(id: Int): Int do
return nsoundpool.play(id, left_volume, right_volume, priority, looping, rate)
end
# 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, name: String): Sound do
var id = resource_manager.raw_id(name)
var resval = nsoundpool.load_id(context, id, priority)
if resval == -1 then
self.error = new Error("Unable to load sound from resources: " + name)
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 pause_stream(stream_id: Int) do nsoundpool.pause(stream_id)
# Pause all active_streams
fun auto_pause do nsoundpool.auto_pause
# Resume all previously active streams
fun auto_resume do nsoundpool.auto_resume
# Resume a playback stream
fun resume(stream_id: Int) do nsoundpool.resume(stream_id)
# Set loop mode on a stream
fun stream_loop=(stream_id, looping: Int) do nsoundpool.set_loop(stream_id, looping)
# Change stream priority
fun stream_priority=(stream_id, priority: Int) do nsoundpool.set_priority(stream_id, priority)
# Change playback rate
fun stream_rate=(stream_id: Int, rate: Float) do nsoundpool.set_rate(stream_id, rate)
# Set stream volume
fun stream_volume(stream_id: Int, left_volume, right_volume: Float) do
nsoundpool.set_volume(stream_id, left_volume, right_volume)
end
# Stop a playback stream
fun stop_stream(stream_id: Int) do nsoundpool.stop(stream_id)
# Unload a sound from a sound ID
fun unload(sound: Sound): Bool do return nsoundpool.unload(sound.soundpool_id)
# Destroys the object
fun destroy do nsoundpool.release
end
# Used to play sounds, designed to use with medium sized sounds or streams
# 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 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 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 do self.nmedia_player = (new NativeMediaPlayer).new_global_ref
# Init the mediaplayer with a sound resource id
init from_id(context: NativeActivity, id: Int) do
self.nmedia_player = new NativeMediaPlayer.create(context, id)
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): 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 = new NativeMediaPlayer.create(context, id)
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 resumes playback
# REQUIRE `self.sound != null`
fun start do
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
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
# Checks whether the mediaplayer is playing
fun playing: Bool do return nmedia_player.playing
# Releases the resources associated with this MediaPlayer
fun destroy do
nmedia_player.release
self.sound = null
end
# Reset MediaPlayer to its initial state
fun reset do nmedia_player.reset
# 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)
var retval = nmedia_player.data_source_path(path.to_java_string)
sys.jni_env.pop_local_frame
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: 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 looping: Bool do return nmedia_player.looping
# Sets the player to be looping or non-looping
fun looping=(b: Bool) do nmedia_player.looping = b
# Sets the volume on this player
fun volume=(volume: Float) do nmedia_player.volume = volume
# Sets the left volume and the right volume of this player
fun both_volume(left_volume, right_volume: Float) do nmedia_player.both_volume(left_volume, right_volume)
# Sets the audio stream type for this media player
fun stream_type=(stream_type: Int) do nmedia_player.stream_type = stream_type
end
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
# Is `self` already loaded?
protected var is_loaded = false is writable
redef var error = null
end
redef class Sound
# Resource ID of this sound
var id: nullable Int is noinit
# The SoundPool who loaded this sound
var soundpool: SoundPool is noinit
# The SoundID of this sound in his SoundPool
var soundpool_id: Int is noinit
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
# Try resources (res)
var rid = app.default_soundpool.load_name_rid(app.resource_manager, app.native_activity, path.strip_extension)
if rid > 0 then
self.soundpool_id = rid
self.soundpool = app.default_soundpool
self.error = null
self.soundpool.error = null
else
# Try assets
var nam = app.asset_manager.open_fd(path)
if nam.is_java_null then
self.error = new Error("Failed to get file descriptor for " + path)
else
var retval_assets = app.default_soundpool.load_asset_fd_rid(nam)
nam.close
if retval_assets == -1 then
self.error = new Error("Failed to load " + path)
else
self.soundpool_id = retval_assets
self.soundpool = app.default_soundpool
self.error = null
self.soundpool.error = null
end
end
end
is_loaded = true
var error = error
if error != null then print_error error
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
end
redef class Music
# Resource ID of this sound
var id: nullable Int is noinit
# The MediaPlayer who loaded this sound
var media_player: MediaPlayer is noinit
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
# Try resources (res)
var rid = app.resource_manager.raw_id(path.strip_extension)
if rid > 0 then
var mp_sound_resources = app.default_mediaplayer.load_sound(rid, app.native_activity)
if mp_sound_resources.error != null then
self.media_player = app.default_mediaplayer
self.error = null
self.media_player.error = null
end
self.error = mp_sound_resources.error
else
# Try assets
var nam = app.asset_manager.open_fd(path)
if nam.is_java_null then
self.error = new Error("Failed to get file descriptor for " + path)
else
var mp_sound_assets = app.default_mediaplayer.data_source_fd(nam)
nam.close
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
end
is_loaded = true
var error = error
if error != null then print_error error
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 resume do
if self.error != null or not self.is_loaded then return
play
paused = false
end
end
redef class App
# 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(native_activity: NativeContext): NativeAudioManager in "Java" `{
return (AudioManager)native_activity.getSystemService(Context.AUDIO_SERVICE);
`}
# Sets the stream of the app to STREAM_MUSIC.
# STREAM_MUSIC is the default stream used by android apps.
private fun manage_audio_stream(native_activity: NativeActivity) in "Java" `{
native_activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
`}
# 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, native_activity, sound_name)
end
# Same as `load_music` but load the sound from the `res/raw` folder
fun load_music_from_res(music: String): Music do
return default_mediaplayer.load_sound(resource_manager.raw_id(music), native_activity)
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(native_activity).abandon_audio_focus
end
redef fun on_create do
super
audio_manager(native_activity).request_audio_focus
manage_audio_stream native_activity
end
redef fun on_resume do
super
audio_manager(native_activity).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
lib/android/audio.nit:17,1--732,3