audio: move some services between the abstraction and the Android layers
[nit.git] / lib / android / audio.nit
index c70dd40..a8d0afe 100644 (file)
@@ -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;
                }
        `}
@@ -209,10 +209,11 @@ 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
 
@@ -279,7 +280,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 +293,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)
@@ -367,8 +368,7 @@ class MediaPlayer
 
        # 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 +378,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 +494,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 +528,16 @@ 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)
+               var retval_resources = app.default_soundpool.load_name_rid(app.resource_manager, app.native_activity, path.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)
+                       self.error = new Error("Failed to load " + path)
+                       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)
                                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
@@ -534,22 +552,27 @@ redef class Sound
                        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,12 +597,12 @@ 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)
+               var mp_sound_resources = app.default_mediaplayer.load_sound(app.resource_manager.raw_id(path.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)
+                       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)
                                if mp_sound_assets.error != null then
@@ -596,31 +619,32 @@ redef class Music
                        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,7 +656,7 @@ 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" `{
+       private fun audio_manager: NativeAudioManager import native_activity in "Java" `{
                return (AudioManager)App_native_activity(self).getSystemService(Context.AUDIO_SERVICE);
        `}
 
@@ -642,49 +666,28 @@ redef class App
                App_native_activity(self).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
+               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
 
@@ -697,6 +700,9 @@ redef class App
        redef fun on_resume do
                super
                audio_manager.request_audio_focus
-               for s in sounds do s.resume
+               for s in sounds do
+                       # Resumes only the sounds paused by the App
+                       if not s.paused then s.resume
+               end
        end
 end