X-Git-Url: http://nitlanguage.org diff --git a/lib/android/audio.nit b/lib/android/audio.nit index 9fe93e9..9cc54d7 100644 --- a/lib/android/audio.nit +++ b/lib/android/audio.nit @@ -1,4 +1,4 @@ -# this file is part of NIT ( http://www.nitlanguage.org ). +# This file is part of NIT ( http://www.nitlanguage.org ). # # Copyright 2014 Romain Chanoir # @@ -14,148 +14,224 @@ # 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 writable = 10 + var max_streams = 10 is writable # The audio stream type, 3 is STREAM_MUSIC, default for game application - var stream_type writable = 3 + var stream_type = 3 is writable # The sample-rate converter quality, currently has no effect - var src_quality writable = 0 + var src_quality = 0 is writable # Left volume value, range 0.0 to 1.0 - var left_volume writable = 1.0 + var left_volume = 1.0 is writable # Right volume value, range 0.0 to 1.0 - var right_volume writable = 1.0 + var right_volume = 1.0 is writable # Playback rate, 1.0 = normal playback, range 0.5 to 2.0 - var rate writable = 1.0 + var rate = 1.0 is writable # Loop mode, 0 = no loop, -1 = loop forever - var looping writable = 0 + var looping = 0 is writable # Stream priority private var priority = 1 @@ -164,21 +240,49 @@ class SoundPool # 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 @@ -187,10 +291,22 @@ class SoundPool 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 @@ -223,8 +339,9 @@ class SoundPool 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 @@ -232,10 +349,16 @@ 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 @@ -245,40 +368,67 @@ class MediaPlayer 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 @@ -295,19 +445,34 @@ class MediaPlayer # 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 @@ -326,94 +491,245 @@ class MediaPlayer 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