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

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

Introduced classes

class MediaPlayer

android :: MediaPlayer

Used to play sounds, designed to use with medium sized sounds or streams
class SoundPool

android :: SoundPool

Used to play sound, best suited for sounds effects in apps or games

Redefined classes

redef class App

android :: audio $ App

App subclasses are cross-platform applications
redef class Music

android :: audio $ Music

Long sound that can bee looped
redef abstract class PlayableAudio

android :: audio $ PlayableAudio

Abstraction of a playable Audio
redef class Sound

android :: audio $ Sound

Short sound

All class definitions

redef class App

android :: audio $ App

App subclasses are cross-platform applications
class MediaPlayer

android $ MediaPlayer

Used to play sounds, designed to use with medium sized sounds or streams
redef class Music

android :: audio $ Music

Long sound that can bee looped
redef abstract class PlayableAudio

android :: audio $ PlayableAudio

Abstraction of a playable Audio
redef class Sound

android :: audio $ Sound

Short sound
class SoundPool

android $ SoundPool

Used to play sound, best suited for sounds effects in apps or games
package_diagram android::audio audio android::assets_and_resources assets_and_resources android::audio->android::assets_and_resources app::audio audio android::audio->app::audio android::dalvik dalvik android::assets_and_resources->android::dalvik java::io io android::assets_and_resources->java::io app::app_base app_base app::audio->app::app_base ...android::dalvik ... ...android::dalvik->android::dalvik ...java::io ... ...java::io->java::io ...app::app_base ... ...app::app_base->app::app_base a_star-m a_star-m a_star-m->android::audio

Ancestors

module abstract_collection

core :: abstract_collection

Abstract collection classes and services.
module abstract_text

core :: abstract_text

Abstract class for manipulation of sequences of characters
module activities

android :: activities

Android Activities wrapper
module app

app :: app

app.nit is a framework to create cross-platform applications
module app_base

app :: app_base

Base of the app.nit framework, defines App
module array

core :: array

This module introduces the standard array structure.
module assets

app :: assets

Portable services to load resources from the assets folder
module aware

android :: aware

Android compatibility module
module bitset

core :: bitset

Services to handle BitSet
module bytes

core :: bytes

Services for byte streams and arrays
module circular_array

core :: circular_array

Efficient data structure to access both end of the sequence.
module codec_base

core :: codec_base

Base for codecs to use with streams
module codecs

core :: codecs

Group module for all codec-related manipulations
module collection

core :: collection

This module define several collection classes.
module collections

java :: collections

Basic Java collections
module core

core :: core

Standard classes and methods used by default by Nit programs and libraries.
module dalvik

android :: dalvik

Java related services specific to Android and its Dalvik VM
module environ

core :: environ

Access to the environment variables of the process
module error

core :: error

Standard error-management infrastructure.
module exec

core :: exec

Invocation and management of operating system sub-processes.
module ffi_support

java :: ffi_support

Core supporting services for the FFI with Java
module file

core :: file

File manipulations (create, read, write, etc.)
module fixed_ints

core :: fixed_ints

Basic integers of fixed-precision
module fixed_ints_text

core :: fixed_ints_text

Text services to complement fixed_ints
module flat

core :: flat

All the array-based text representations
module gc

core :: gc

Access to the Nit internal garbage collection mechanism
module hash_collection

core :: hash_collection

Introduce HashMap and HashSet.
module io

java :: io

Services from the java.io package
module iso8859_1

core :: iso8859_1

Codec for ISO8859-1 I/O
module java

java :: java

Supporting services for the FFI with Java and to access Java libraries
module jvm

jvm :: jvm

Java Virtual Machine invocation API and others services from the JNI C API
module kernel

core :: kernel

Most basic classes and methods.
module list

core :: list

This module handle double linked lists
module math

core :: math

Mathematical operations
module native

core :: native

Native structures for text and bytes
module numeric

core :: numeric

Advanced services for Numeric types
module platform

android :: platform

Triggers compilation for the android platform
module protocol

core :: protocol

module queue

core :: queue

Queuing data structures and wrappers
module range

core :: range

Module for range of discrete objects.
module re

core :: re

Regular expression support for all services based on Pattern
module ropes

core :: ropes

Tree-based representation of a String.
module sorter

core :: sorter

This module contains classes used to compare things and sorts arrays.
module stream

core :: stream

Input and output streams of characters
module text

core :: text

All the classes and methods related to the manipulation of text entities
module time

core :: time

Management of time and dates
module union_find

core :: union_find

union–find algorithm using an efficient disjoint-set data structure
module utf8

core :: utf8

Codec for UTF-8 I/O

Parents

module assets_and_resources

android :: assets_and_resources

Android Assets and Resources Management
module audio

app :: audio

Services to load and play Sound and Music from the assets folder

Children

module a_star-m

a_star-m

# 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