X-Git-Url: http://nitlanguage.org diff --git a/lib/android/audio.nit b/lib/android/audio.nit index c70dd40..6d4fe1a 100644 --- a/lib/android/audio.nit +++ b/lib/android/audio.nit @@ -28,8 +28,8 @@ # # ~~~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") +# var s = new Sound("sounds/test_sound.ogg") +# var m = new Music("sounds/test_music.ogg") # s.play # m.play # ~~~ @@ -115,9 +115,10 @@ private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `} } `} - fun create(context: NativeActivity, id: Int): NativeMediaPlayer in "Java" `{ + new create(context: NativeActivity, id: Int): NativeMediaPlayer + in "Java" `{ try { - return self.create(context, (int)id); + return MediaPlayer.create(context, (int)id); }catch(Exception e) { return null; } @@ -138,7 +139,6 @@ private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `} self.setDataSource(fd, start_offset, length); return 1; }catch(Exception e) { - Log.e("Error loading the Media Player with a file descriptor", e.getMessage()); return 0; } `} @@ -152,6 +152,13 @@ private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `} } `} 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 @@ -203,16 +210,24 @@ private extern class NativeSoundPool in "Java" `{ android.media.SoundPool `} 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 - # Error gestion + # 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 @@ -237,7 +252,7 @@ class SoundPool # Stream priority private var priority = 1 - init do self.nsoundpool = new NativeSoundPool(max_streams, stream_type, src_quality) + 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 @@ -279,7 +294,7 @@ class SoundPool 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) + 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) @@ -292,12 +307,12 @@ class SoundPool 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, sound: String): Sound do - var id = resource_manager.raw_id(sound) + # 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 : " + sound) + 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) @@ -363,12 +378,11 @@ class MediaPlayer # 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 + 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 - self.nmedia_player = nmedia_player.create(context, id) + 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) @@ -378,7 +392,18 @@ class MediaPlayer # Load a sound for a given resource id fun load_sound(id: Int, context: NativeActivity): Music do - self.nmedia_player = self.nmedia_player.create(context, id) + # 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")) @@ -483,7 +508,14 @@ class MediaPlayer end redef class PlayableAudio - redef init do app.add_to_sounds(self) + # 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 @@ -510,16 +542,24 @@ redef class Sound 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) + + # 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 " + self.name) + 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" + self.name) + self.error = new Error("Failed to load " + path) else self.soundpool_id = retval_assets self.soundpool = app.default_soundpool @@ -527,29 +567,29 @@ redef class Sound 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 + + var error = error + if error != null then print_error error end redef fun play do - if self.error != null then return 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 @@ -574,14 +614,25 @@ redef class Music 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 + + # 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 - var nam = app.asset_manager.open_fd(self.name) + 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 " + self.name) + 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 @@ -590,37 +641,34 @@ redef class Music 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 + + var error = error + if error != null then print_error error end redef fun play do - if self.error != null then return 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 - # 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] - # Returns the default MediaPlayer of the application. # When you load a music, it goes in this MediaPlayer. # Use it for advanced sound management @@ -632,71 +680,53 @@ redef class App var default_soundpool: SoundPool is lazy do return new SoundPool # Get the native audio manager - fun audio_manager: NativeAudioManager import native_activity in "Java" `{ - return (AudioManager)App_native_activity(self).getSystemService(Context.AUDIO_SERVICE); + 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 import native_activity in "Java" `{ - App_native_activity(self).setVolumeControlStream(AudioManager.STREAM_MUSIC); + private fun manage_audio_stream(native_activity: NativeActivity) in "Java" `{ + native_activity.setVolumeControlStream(AudioManager.STREAM_MUSIC); `} - # Retrieves a sound with a soundpool in the `assets` folder using its name. - # Used to play short songs, can play multiple sounds simultaneously - 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 - - # 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 - redef fun load_music(path) do - var fd = asset_manager.open_fd(path) - 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 fun load_sound_from_res(sound_name: String): Sound do - return add_to_sounds(default_soundpool.load_name(resource_manager,self.native_activity, sound_name)).as(Sound) + 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 add_to_sounds(default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity)).as(Music) - end - - # 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 + return default_mediaplayer.load_sound(resource_manager.raw_id(music), native_activity) end redef fun on_pause do super - for s in sounds do s.pause - audio_manager.abandon_audio_focus + 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.request_audio_focus - manage_audio_stream + audio_manager(native_activity).request_audio_focus + manage_audio_stream native_activity end redef fun on_resume do super - audio_manager.request_audio_focus - for s in sounds do s.resume + 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