1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2014 Romain Chanoir <romain.chanoir@viacesi.fr>
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Android audio services, wraps a part of android audio API
19 # For this example, the sounds "test_sound" and "test_music" are located in the "assets/sounds" folder,
20 # they both have ".ogg" extension. "test_sound" is a short sound and "test_music" a music track
22 # # Note that you need to specify the path from "assets" folder and the extension
23 # var s = app.load_sound("sounds/test_sound.ogg")
24 # var m = app.load_music("sounds/test_music.ogg")
29 # Now, the sounds are in "res/raw"
31 # s = app.load_sound_from_res("test_sound")
32 # m = app.load_music_from_res("test_sound")
37 # See http://developer.android.com/reference/android/media/package-summary.html for more infos
42 import assets_and_resources
46 import android.media.MediaPlayer;
47 import android.media.SoundPool;
48 import java.io.IOException;
49 import android.media.AudioManager;
50 import android.media.AudioManager.OnAudioFocusChangeListener;
51 import android.content.Context;
52 import android.util.Log;
55 # FIXME: This listener is not working at the moment, but is needed to gain or give up the audio focus
58 static OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
59 public void onAudioFocusChange(int focusChange) {
60 if(focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
61 }else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
62 }else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
68 # AudioManager of the application, used to manage the audio mode
69 extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
73 # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
74 fun mode: Int in "Java" `{ return recv.getMode(); `}
76 # Sets the audio mode.
77 # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
78 fun mode
=(i
: Int) in "Java" `{ recv.setMode((int)i); `}
80 # Sends a request to obtain audio focus
81 fun request_audio_focus: Int in "Java" `{
82 return recv
.requestAudioFocus
(afChangeListener
, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
85 # Gives up audio focus
86 fun abandon_audio_focus: Int in "Java" `{ return recv.abandonAudioFocus(afChangeListener); `}
89 # Media Player from Java, used to play long sounds or musics, not simultaneously
90 # This is a low-level class, use `MediaPlater` instead
91 private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
95 MediaPlayer mp
= new MediaPlayer();
96 mp
.setAudioStreamType
(AudioManager.STREAM_MUSIC);
99 fun start in "Java" `{ recv.start(); `}
100 fun prepare
in "Java" `{
103 }catch(IOException e) {
104 Log.e("Error preparing the Media Player", e.getMessage());
109 fun create
(context
: NativeActivity, id
: Int): NativeMediaPlayer in "Java" `{ return recv.create(context, (int)id); `}
110 fun pause in "Java" `{ recv.pause(); `}
111 fun stop
in "Java" `{ recv.stop(); `}
112 fun playing: Bool in "Java" `{ return recv.isPlaying(); `}
113 fun release
in "Java" `{ recv.release(); `}
114 fun duration: Int in "Java" `{ return recv.getDuration(); `}
115 fun looping
: Bool in "Java" `{ return recv.isLooping(); `}
116 fun looping=(b: Bool) in "Java" `{ recv.setLooping(b); `}
117 fun volume
=(vol
: Float) in "Java" `{ recv.setVolume((float)vol, (float)vol); `}
118 fun both_volume(left_volume, right_volume: Float) in "Java" `{ recv.setVolume((float)left_volume, (float)right_volume); `}
119 fun stream_type
=(stream_type
: Int) in "Java" `{ recv.setAudioStreamType((int)stream_type); `}
120 fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int) in "Java" `{
122 recv
.setDataSource
(fd
, start_offset
, length
);
123 }catch
(IOException e
) {
124 Log.e
("Error loading the Media Player with a file descriptor", e
.getMessage
());
128 fun data_source_path(path: JavaString) in "Java" `{
130 recv
.setDataSource
(path
);
131 }catch
(IOException e
) {
132 Log.e
("Error loading the Media Player", e
.getMessage
());
136 fun reset in "Java" `{ recv.reset(); `}
139 # Sound Pool from Java, used to play sounds simultaneously
140 # This is a low-level class, use `SoundPool`instead
141 private extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
144 new(max_streams, stream_type, src_quality: Int) in "Java" `{
145 return new SoundPool((int
)max_streams
, (int
)stream_type
, (int
)src_quality
);
147 fun load_asset_fd(afd: NativeAssetFileDescriptor, priority: Int): Int in "Java" `{ return recv.load(afd, (int)priority); `}
148 fun load_id
(context
: NativeActivity, resid
, priority
: Int): Int in "Java" `{ return recv.load(context, (int)resid, (int)priority); `}
149 fun load_path(path: JavaString, priority: Int): Int in "Java" `{ return recv.load(path, (int)priority); `}
150 fun play
(sound_id
: Int, left_volume
, right_volume
: Float, priority
, l
: Int, rate
: Float): Int in "Java" `{
151 return recv.play((int)sound_id, (float)left_volume, (float)right_volume, (int)priority, (int)l, (float)rate);
153 fun pause
(stream_id
: Int) in "Java" `{ recv.pause((int)stream_id); `}
154 fun auto_pause in "Java" `{ recv.autoPause(); `}
155 fun auto_resume
in "Java" `{ recv.autoResume(); `}
156 fun resume(stream_id: Int) in "Java" `{ recv.resume((int)stream_id); `}
157 fun set_loop
(stream_id
, l
: Int) in "Java" `{ recv.setLoop((int)stream_id, (int)l); `}
158 fun set_priority(stream_id, priority: Int) in "Java" `{ recv.setPriority((int)stream_id, (int)priority); `}
159 fun set_rate
(stream_id
: Int, rate
: Float) in "Java" `{ recv.setRate((int)stream_id, (float)rate); `}
160 fun set_volume(stream_id: Int, left_volume, right_volume: Float) in "Java" `{ recv.setVolume((int)stream_id, (float)left_volume, (float)right_volume); `}
161 fun stop
(stream_id
: Int) in "Java" `{ recv.stop((int)stream_id); `}
162 fun unload(sound_id: Int): Bool in "Java" `{ return recv.unload((int)sound_id); `}
163 fun release
in "Java" `{ recv.release(); `}
167 # Used to play sound, best suited for sounds effects in apps or games
169 private var nsoundpool: NativeSoundPool is noinit
170 # The maximum number of simultaneous streams for this SoundPool
171 var max_streams = 10 is writable
173 # The audio stream type, 3 is STREAM_MUSIC, default for game application
174 var stream_type = 3 is writable
176 # The sample-rate converter quality, currently has no effect
177 var src_quality = 0 is writable
179 # Left volume value, range 0.0 to 1.0
180 var left_volume = 1.0 is writable
182 # Right volume value, range 0.0 to 1.0
183 var right_volume = 1.0 is writable
185 # Playback rate, 1.0 = normal playback, range 0.5 to 2.0
186 var rate = 1.0 is writable
188 # Loop mode, 0 = no loop, -1 = loop forever
189 var looping = 0 is writable
192 private var priority = 1
194 init do self.nsoundpool = new NativeSoundPool(max_streams, stream_type, src_quality)
196 # Load the sound from an asset file descriptor
197 # this function is for advanced use
198 fun load_asset_fd(afd: NativeAssetFileDescriptor): Sound do
199 return new SoundSP(null, nsoundpool.load_asset_fd(afd, priority), self)
202 # Load the sound from its resource id
203 fun load_id(context: NativeActivity, id:Int): Sound do
204 return new SoundSP(null, nsoundpool.load_id(context, id, priority), self)
207 # Load the sound from the specified path
208 fun load_path(path: String): Sound do
209 sys.jni_env.push_local_frame(1)
210 var return_value = new SoundSP(0, nsoundpool.load_path(path.to_java_string, priority), self)
211 sys.jni_env.pop_local_frame
215 # Play a sound from a sound ID
216 # return non-zero streamID if successful, zero if failed
217 fun play(id: Int): Int do
218 return nsoundpool.play(id, left_volume, right_volume, priority, looping, rate)
221 # Load a sound by its name in the resources, the sound must be in the `res
/raw
` folder
222 fun load_name(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Sound do
223 var id = resource_manager.raw_id(sound)
224 return new SoundSP(id, nsoundpool.load_id(context, id, priority), self)
227 # Pause a playback stream
228 fun pause_stream(stream_id: Int) do nsoundpool.pause(stream_id)
230 # Pause all active_streams
231 fun auto_pause do nsoundpool.auto_pause
233 # Resume all previously active streams
234 fun auto_resume do nsoundpool.auto_resume
236 # Resume a playback stream
237 fun resume(stream_id: Int) do nsoundpool.resume(stream_id)
239 # Set loop mode on a stream
240 fun stream_loop=(stream_id, looping: Int) do nsoundpool.set_loop(stream_id, looping)
242 # Change stream priority
243 fun stream_priority=(stream_id, priority: Int) do nsoundpool.set_priority(stream_id, priority)
245 # Change playback rate
246 fun stream_rate=(stream_id: Int, rate: Float) do nsoundpool.set_rate(stream_id, rate)
249 fun stream_volume(stream_id: Int, left_volume, right_volume: Float) do
250 nsoundpool.set_volume(stream_id, left_volume, right_volume)
253 # Stop a playback stream
254 fun stop_stream(stream_id: Int) do nsoundpool.stop(stream_id)
256 # Unload a sound from a sound ID
257 fun unload(sound: SoundSP): Bool do return nsoundpool.unload(sound.soundpool_id)
259 fun destroy do nsoundpool.release
262 # Used to play sounds, designed to use with medium sized sounds or streams
263 # The Android MediaPlayer has a complex state diagram that you'll need to
264 # respect if you want your MediaPlayer to work fine, see the android doc
266 private var nmedia_player: NativeMediaPlayer is noinit
268 # Used to control the state of the mediaplayer
269 private var is_prepared = false is writable
271 # The sound associated with this mediaplayer
272 var sound: nullable Sound = null is writable
274 # Create a new MediaPlayer, but no sound is attached, you'll need
275 # to use `load_sound
` before using it
276 init do self.nmedia_player = new NativeMediaPlayer
278 # Init the mediaplayer with a sound resource id
279 init from_id(context: NativeActivity, id: Int) do
280 self.nmedia_player = new NativeMediaPlayer
281 self.nmedia_player = nmedia_player.create(context, id)
282 self.sound = new SoundMP(id, self)
285 # Load a sound for a given resource id
286 fun load_sound(id: Int, context: NativeActivity): Sound do
287 self.nmedia_player = self.nmedia_player.create(context, id)
288 self.sound = new SoundMP(id, self)
289 self.is_prepared = true
290 return self.sound.as(not null)
293 # Starts or resumes playback
294 # REQUIRE `self.sound
!= null`
296 if not is_prepared then prepare
300 # Stops playback after playback has been stopped or paused
301 # REQUIRE `self.sound
!= null`
307 # Prepares the player for playback, synchronously
308 # REQUIRE `self.sound
!= null`
311 nmedia_player.prepare
316 # REQUIRE `self.sound
!= null`
322 # Checks whether the mediaplayer is playing
323 fun playing: Bool do return nmedia_player.playing
325 # Releases the resources associated with this MediaPlayer
327 nmedia_player.release
331 # Reset MediaPlayer to its initial state
332 fun reset do nmedia_player.reset
334 # Sets the datasource (file-path or http/rtsp URL) to use
335 fun data_source(path: String): Sound do
336 sys.jni_env.push_local_frame(1)
337 nmedia_player.data_source_path(path.to_java_string)
338 sys.jni_env.pop_local_frame
339 self.sound = new SoundMP(null, self)
340 return self.sound.as(not null)
342 # Sets the data source (NativeFileDescriptor) to use
343 fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int): Sound do
344 nmedia_player.data_source_fd(fd, start_offset, length)
345 self.sound = new SoundMP(null, self)
346 return self.sound.as(not null)
349 # Checks whether the MediaPlayer is looping or non-looping
350 fun looping: Bool do return nmedia_player.looping
352 # Sets the player to be looping or non-looping
353 fun looping=(b: Bool) do nmedia_player.looping = b
355 # Sets the volume on this player
356 fun volume=(volume: Float) do nmedia_player.volume = volume
358 # Sets the left volume and the right volume of this player
359 fun both_volume(left_volume, right_volume: Float) do nmedia_player.both_volume(left_volume, right_volume)
361 # Sets the audio stream type for this media player
362 fun stream_type=(stream_type: Int) do nmedia_player.stream_type = stream_type
367 # Resource ID of this sound
371 # Sound implemented with a SoundPool
375 # The SoundPool who loaded this sound
376 var soundpool: SoundPool
378 # The SoundID of this sound in his SoundPool
379 var soundpool_id: Int
381 private init (id: nullable Int, soundpool_id: Int, soundpool: SoundPool) do
383 self.soundpool_id = soundpool_id
384 self.soundpool = soundpool
387 redef fun play do soundpool.play(soundpool_id)
388 redef fun pause do soundpool.pause_stream(soundpool_id)
389 redef fun resume do soundpool.resume(soundpool_id)
392 # Sound Implemented with a MediaPlayer
396 # The MediaPlayer who loaded this sound
397 var media_player: MediaPlayer
399 private init (id: nullable Int, media_player: MediaPlayer) do
401 self.media_player = media_player
404 redef fun play do media_player.start
405 redef fun pause do media_player.pause
406 redef fun resume do play
411 # Sounds handled by the application, when you load a sound, it's added to this list.
412 # This array is used in `pause
` and `resume
`
413 private var sounds = new Array[Sound]
415 # Returns the default MediaPlayer of the application.
416 # When you load a music, it goes in this MediaPlayer.
417 # Use it for advanced sound management
418 var default_mediaplayer: MediaPlayer is lazy do return new MediaPlayer
420 # Returns the default MediaPlayer of the application.
421 # When you load a short sound (not a music), it's added to this soundpool.
422 # Use it for advanced sound management.
423 var default_soundpool: SoundPool is lazy do return new SoundPool
425 # Get the native audio manager
426 fun audio_manager: NativeAudioManager import native_activity in "Java" `{
427 return (AudioManager)App_native_activity(recv
).getSystemService
(Context.AUDIO_SERVICE);
430 # Sets the stream of the app to STREAM_MUSIC.
431 # STREAM_MUSIC is the default stream used by android apps.
432 private fun manage_audio_stream import native_activity, native_app_glue in "Java" `{
433 App_native_activity(recv
).setVolumeControlStream
(AudioManager.STREAM_MUSIC);
436 # Retrieves a sound with a soundpool in the `assets
` folder using its name.
437 # Used to play short songs, can play multiple sounds simultaneously
438 redef fun load_sound(path: String): Sound do
439 return add_to_sounds(default_soundpool.load_asset_fd(asset_manager.open_fd(path)))
442 # Retrieves a music with a media player in the `assets
` folder using its name.
443 # Used to play long sounds or musics, can't play multiple sounds simultaneously
444 fun load_music(path: String): Sound do
445 var fd = asset_manager.open_fd(path)
446 return add_to_sounds(default_mediaplayer.data_source_fd(fd.file_descriptor, fd.start_offset, fd.length))
449 # Same as `load_sound
` but load the sound from the `res
/raw
` folder
450 fun load_sound_from_res(sound_name: String): Sound do
451 return add_to_sounds(default_soundpool.load_name(resource_manager,self.native_activity, sound_name))
454 # Same as `load_music
` but load the sound from the `res
/raw
` folder
455 fun load_music_from_res(music: String): Sound do
456 return add_to_sounds(default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity))
459 # Factorizes `sounds
.add
` to use it in `load_music
`, `load_sound
`, `load_music_from_res
` and `load_sound_from_res
`
460 private fun add_to_sounds(sound: Sound): Sound do
466 for s in sounds do s.pause
467 audio_manager.abandon_audio_focus
470 redef fun init_window do
472 audio_manager.request_audio_focus
478 audio_manager.request_audio_focus
479 for s in sounds do s.resume