9374ac7e34e2bdff63792360be7b41a97b71dafd
[nit.git] / lib / android / audio.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2014 Romain Chanoir <romain.chanoir@viacesi.fr>
4 #
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
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
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.
16
17 # Android audio services, wraps a part of android audio API
18 #
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
21 # ~~~nitish
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")
25 # s.play
26 # m.play
27 # ~~~
28 #
29 # Now, the sounds are in "res/raw"
30 # ~~~nitish
31 # s = app.load_sound_from_res("test_sound")
32 # m = app.load_music_from_res("test_sound")
33 # s.play
34 # m.play
35 # ~~~
36 #
37 # See http://developer.android.com/reference/android/media/package-summary.html for more infos
38 module audio
39
40 import java
41 import java::io
42 import assets_and_resources
43 import native_app_glue # FIXME update this module to use nit_activity
44 import app::audio
45
46 in "Java" `{
47 import android.media.MediaPlayer;
48 import android.media.SoundPool;
49 import java.io.IOException;
50 import android.media.AudioManager;
51 import android.media.AudioManager.OnAudioFocusChangeListener;
52 import android.content.Context;
53 import android.util.Log;
54 `}
55
56 # FIXME: This listener is not working at the moment, but is needed to gain or give up the audio focus
57 # of the application
58 in "Java inner" `{
59 static OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
60 public void onAudioFocusChange(int focusChange) {
61 if(focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
62 }else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
63 }else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
64 }
65 }
66 };
67 `}
68
69 # AudioManager of the application, used to manage the audio mode
70 extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
71 super JavaObject
72
73 # Current audio mode.
74 # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
75 fun mode: Int in "Java" `{ return self.getMode(); `}
76
77 # Sets the audio mode.
78 # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
79 fun mode=(i: Int) in "Java" `{ self.setMode((int)i); `}
80
81 # Sends a request to obtain audio focus
82 fun request_audio_focus: Int in "Java" `{
83 return self.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
84 `}
85
86 # Gives up audio focus
87 fun abandon_audio_focus: Int in "Java" `{ return self.abandonAudioFocus(afChangeListener); `}
88 end
89
90 # Media Player from Java, used to play long sounds or musics, not simultaneously
91 # This is a low-level class, use `MediaPlater` instead
92 private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
93 super JavaObject
94
95 new in "Java" `{
96 MediaPlayer mp = new MediaPlayer();
97 mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
98 return mp;
99 `}
100 fun start in "Java" `{ self.start(); `}
101 fun prepare in "Java" `{
102 try {
103 self.prepare();
104 }catch(IOException e) {
105 Log.e("Error preparing the Media Player", e.getMessage());
106 e.printStackTrace();
107 }
108 `}
109
110 fun create(context: NativeActivity, id: Int): NativeMediaPlayer in "Java" `{ return self.create(context, (int)id); `}
111 fun pause in "Java" `{ self.pause(); `}
112 fun stop in "Java" `{ self.stop(); `}
113 fun playing: Bool in "Java" `{ return self.isPlaying(); `}
114 fun release in "Java" `{ self.release(); `}
115 fun duration: Int in "Java" `{ return self.getDuration(); `}
116 fun looping: Bool in "Java" `{ return self.isLooping(); `}
117 fun looping=(b: Bool) in "Java" `{ self.setLooping(b); `}
118 fun volume=(vol: Float) in "Java" `{ self.setVolume((float)vol, (float)vol); `}
119 fun both_volume(left_volume, right_volume: Float) in "Java" `{ self.setVolume((float)left_volume, (float)right_volume); `}
120 fun stream_type=(stream_type: Int) in "Java" `{ self.setAudioStreamType((int)stream_type); `}
121 fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int) in "Java" `{
122 try {
123 self.setDataSource(fd, start_offset, length);
124 }catch(IOException e) {
125 Log.e("Error loading the Media Player with a file descriptor", e.getMessage());
126 e.printStackTrace();
127 }
128 `}
129 fun data_source_path(path: JavaString) in "Java" `{
130 try {
131 self.setDataSource(path);
132 }catch(IOException e) {
133 Log.e("Error loading the Media Player", e.getMessage());
134 e.printStackTrace();
135 }
136 `}
137 fun reset in "Java" `{ self.reset(); `}
138 end
139
140 # Sound Pool from Java, used to play sounds simultaneously
141 # This is a low-level class, use `SoundPool`instead
142 private extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
143 super JavaObject
144
145 new(max_streams, stream_type, src_quality: Int) in "Java" `{
146 return new SoundPool((int)max_streams, (int)stream_type, (int)src_quality);
147 `}
148 fun load_asset_fd(afd: NativeAssetFileDescriptor, priority: Int): Int in "Java" `{ return self.load(afd, (int)priority); `}
149 fun load_id(context: NativeActivity, resid, priority: Int): Int in "Java" `{ return self.load(context, (int)resid, (int)priority); `}
150 fun load_path(path: JavaString, priority: Int): Int in "Java" `{ return self.load(path, (int)priority); `}
151 fun play(sound_id: Int, left_volume, right_volume: Float, priority, l: Int, rate: Float): Int in "Java" `{
152 return self.play((int)sound_id, (float)left_volume, (float)right_volume, (int)priority, (int)l, (float)rate);
153 `}
154 fun pause(stream_id: Int) in "Java" `{ self.pause((int)stream_id); `}
155 fun auto_pause in "Java" `{ self.autoPause(); `}
156 fun auto_resume in "Java" `{ self.autoResume(); `}
157 fun resume(stream_id: Int) in "Java" `{ self.resume((int)stream_id); `}
158 fun set_loop(stream_id, l: Int) in "Java" `{ self.setLoop((int)stream_id, (int)l); `}
159 fun set_priority(stream_id, priority: Int) in "Java" `{ self.setPriority((int)stream_id, (int)priority); `}
160 fun set_rate(stream_id: Int, rate: Float) in "Java" `{ self.setRate((int)stream_id, (float)rate); `}
161 fun set_volume(stream_id: Int, left_volume, right_volume: Float) in "Java" `{ self.setVolume((int)stream_id, (float)left_volume, (float)right_volume); `}
162 fun stop(stream_id: Int) in "Java" `{ self.stop((int)stream_id); `}
163 fun unload(sound_id: Int): Bool in "Java" `{ return self.unload((int)sound_id); `}
164 fun release in "Java" `{ self.release(); `}
165 end
166
167
168 # Used to play sound, best suited for sounds effects in apps or games
169 class SoundPool
170 private var nsoundpool: NativeSoundPool is noinit
171 # The maximum number of simultaneous streams for this SoundPool
172 var max_streams = 10 is writable
173
174 # The audio stream type, 3 is STREAM_MUSIC, default for game application
175 var stream_type = 3 is writable
176
177 # The sample-rate converter quality, currently has no effect
178 var src_quality = 0 is writable
179
180 # Left volume value, range 0.0 to 1.0
181 var left_volume = 1.0 is writable
182
183 # Right volume value, range 0.0 to 1.0
184 var right_volume = 1.0 is writable
185
186 # Playback rate, 1.0 = normal playback, range 0.5 to 2.0
187 var rate = 1.0 is writable
188
189 # Loop mode, 0 = no loop, -1 = loop forever
190 var looping = 0 is writable
191
192 # Stream priority
193 private var priority = 1
194
195 init do self.nsoundpool = new NativeSoundPool(max_streams, stream_type, src_quality)
196
197 # Load the sound from an asset file descriptor
198 # this function is for advanced use
199 fun load_asset_fd(afd: NativeAssetFileDescriptor): Sound do
200 return new SoundSP(null, nsoundpool.load_asset_fd(afd, priority), self)
201 end
202
203 # Load the sound from its resource id
204 fun load_id(context: NativeActivity, id:Int): Sound do
205 return new SoundSP(null, nsoundpool.load_id(context, id, priority), self)
206 end
207
208 # Load the sound from the specified path
209 fun load_path(path: String): Sound do
210 sys.jni_env.push_local_frame(1)
211 var return_value = new SoundSP(0, nsoundpool.load_path(path.to_java_string, priority), self)
212 sys.jni_env.pop_local_frame
213 return return_value
214 end
215
216 # Play a sound from a sound ID
217 # return non-zero streamID if successful, zero if failed
218 fun play(id: Int): Int do
219 return nsoundpool.play(id, left_volume, right_volume, priority, looping, rate)
220 end
221
222 # Load a sound by its name in the resources, the sound must be in the `res/raw` folder
223 fun load_name(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Sound do
224 var id = resource_manager.raw_id(sound)
225 return new SoundSP(id, nsoundpool.load_id(context, id, priority), self)
226 end
227
228 # Pause a playback stream
229 fun pause_stream(stream_id: Int) do nsoundpool.pause(stream_id)
230
231 # Pause all active_streams
232 fun auto_pause do nsoundpool.auto_pause
233
234 # Resume all previously active streams
235 fun auto_resume do nsoundpool.auto_resume
236
237 # Resume a playback stream
238 fun resume(stream_id: Int) do nsoundpool.resume(stream_id)
239
240 # Set loop mode on a stream
241 fun stream_loop=(stream_id, looping: Int) do nsoundpool.set_loop(stream_id, looping)
242
243 # Change stream priority
244 fun stream_priority=(stream_id, priority: Int) do nsoundpool.set_priority(stream_id, priority)
245
246 # Change playback rate
247 fun stream_rate=(stream_id: Int, rate: Float) do nsoundpool.set_rate(stream_id, rate)
248
249 # Set stream volume
250 fun stream_volume(stream_id: Int, left_volume, right_volume: Float) do
251 nsoundpool.set_volume(stream_id, left_volume, right_volume)
252 end
253
254 # Stop a playback stream
255 fun stop_stream(stream_id: Int) do nsoundpool.stop(stream_id)
256
257 # Unload a sound from a sound ID
258 fun unload(sound: SoundSP): Bool do return nsoundpool.unload(sound.soundpool_id)
259
260 fun destroy do nsoundpool.release
261 end
262
263 # Used to play sounds, designed to use with medium sized sounds or streams
264 # The Android MediaPlayer has a complex state diagram that you'll need to
265 # respect if you want your MediaPlayer to work fine, see the android doc
266 class MediaPlayer
267 private var nmedia_player: NativeMediaPlayer is noinit
268
269 # Used to control the state of the mediaplayer
270 private var is_prepared = false is writable
271
272 # The sound associated with this mediaplayer
273 var sound: nullable Sound = null is writable
274
275 # Create a new MediaPlayer, but no sound is attached, you'll need
276 # to use `load_sound` before using it
277 init do self.nmedia_player = new NativeMediaPlayer
278
279 # Init the mediaplayer with a sound resource id
280 init from_id(context: NativeActivity, id: Int) do
281 self.nmedia_player = new NativeMediaPlayer
282 self.nmedia_player = nmedia_player.create(context, id)
283 self.sound = new SoundMP(id, self)
284 end
285
286 # Load a sound for a given resource id
287 fun load_sound(id: Int, context: NativeActivity): Sound do
288 self.nmedia_player = self.nmedia_player.create(context, id)
289 self.sound = new SoundMP(id, self)
290 self.is_prepared = true
291 return self.sound.as(not null)
292 end
293
294 # Starts or resumes playback
295 # REQUIRE `self.sound != null`
296 fun start do
297 if not is_prepared then prepare
298 nmedia_player.start
299 end
300
301 # Stops playback after playback has been stopped or paused
302 # REQUIRE `self.sound != null`
303 fun stop do
304 is_prepared = false
305 nmedia_player.stop
306 end
307
308 # Prepares the player for playback, synchronously
309 # REQUIRE `self.sound != null`
310 fun prepare do
311 assert sound != null
312 nmedia_player.prepare
313 is_prepared = true
314 end
315
316 # Pauses playback
317 # REQUIRE `self.sound != null`
318 fun pause do
319 assert sound != null
320 nmedia_player.pause
321 end
322
323 # Checks whether the mediaplayer is playing
324 fun playing: Bool do return nmedia_player.playing
325
326 # Releases the resources associated with this MediaPlayer
327 fun destroy do
328 nmedia_player.release
329 self.sound = null
330 end
331
332 # Reset MediaPlayer to its initial state
333 fun reset do nmedia_player.reset
334
335 # Sets the datasource (file-path or http/rtsp URL) to use
336 fun data_source(path: String): Sound do
337 sys.jni_env.push_local_frame(1)
338 nmedia_player.data_source_path(path.to_java_string)
339 sys.jni_env.pop_local_frame
340 self.sound = new SoundMP(null, self)
341 return self.sound.as(not null)
342 end
343 # Sets the data source (NativeFileDescriptor) to use
344 fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int): Sound do
345 nmedia_player.data_source_fd(fd, start_offset, length)
346 self.sound = new SoundMP(null, self)
347 return self.sound.as(not null)
348 end
349
350 # Checks whether the MediaPlayer is looping or non-looping
351 fun looping: Bool do return nmedia_player.looping
352
353 # Sets the player to be looping or non-looping
354 fun looping=(b: Bool) do nmedia_player.looping = b
355
356 # Sets the volume on this player
357 fun volume=(volume: Float) do nmedia_player.volume = volume
358
359 # Sets the left volume and the right volume of this player
360 fun both_volume(left_volume, right_volume: Float) do nmedia_player.both_volume(left_volume, right_volume)
361
362 # Sets the audio stream type for this media player
363 fun stream_type=(stream_type: Int) do nmedia_player.stream_type = stream_type
364 end
365
366 redef class Sound
367
368 # Resource ID of this sound
369 var id: nullable Int
370 end
371
372 # Sound implemented with a SoundPool
373 class SoundSP
374 super Sound
375
376 # The SoundPool who loaded this sound
377 var soundpool: SoundPool
378
379 # The SoundID of this sound in his SoundPool
380 var soundpool_id: Int
381
382 private init (id: nullable Int, soundpool_id: Int, soundpool: SoundPool) do
383 self.id = id
384 self.soundpool_id = soundpool_id
385 self.soundpool = soundpool
386 end
387
388 redef fun play do soundpool.play(soundpool_id)
389 redef fun pause do soundpool.pause_stream(soundpool_id)
390 redef fun resume do soundpool.resume(soundpool_id)
391 end
392
393 # Sound Implemented with a MediaPlayer
394 class SoundMP
395 super Sound
396
397 # The MediaPlayer who loaded this sound
398 var media_player: MediaPlayer
399
400 private init (id: nullable Int, media_player: MediaPlayer) do
401 self.id = id
402 self.media_player = media_player
403 end
404
405 redef fun play do media_player.start
406 redef fun pause do media_player.pause
407 redef fun resume do play
408 end
409
410 redef class App
411
412 # Sounds handled by the application, when you load a sound, it's added to this list.
413 # This array is used in `pause` and `resume`
414 private var sounds = new Array[Sound]
415
416 # Returns the default MediaPlayer of the application.
417 # When you load a music, it goes in this MediaPlayer.
418 # Use it for advanced sound management
419 var default_mediaplayer: MediaPlayer is lazy do return new MediaPlayer
420
421 # Returns the default MediaPlayer of the application.
422 # When you load a short sound (not a music), it's added to this soundpool.
423 # Use it for advanced sound management.
424 var default_soundpool: SoundPool is lazy do return new SoundPool
425
426 # Get the native audio manager
427 fun audio_manager: NativeAudioManager import native_activity in "Java" `{
428 return (AudioManager)App_native_activity(self).getSystemService(Context.AUDIO_SERVICE);
429 `}
430
431 # Sets the stream of the app to STREAM_MUSIC.
432 # STREAM_MUSIC is the default stream used by android apps.
433 private fun manage_audio_stream import native_activity, native_app_glue in "Java" `{
434 App_native_activity(self).setVolumeControlStream(AudioManager.STREAM_MUSIC);
435 `}
436
437 # Retrieves a sound with a soundpool in the `assets` folder using its name.
438 # Used to play short songs, can play multiple sounds simultaneously
439 redef fun load_sound(path: String): Sound do
440 return add_to_sounds(default_soundpool.load_asset_fd(asset_manager.open_fd(path)))
441 end
442
443 # Retrieves a music with a media player in the `assets` folder using its name.
444 # Used to play long sounds or musics, can't play multiple sounds simultaneously
445 fun load_music(path: String): Sound do
446 var fd = asset_manager.open_fd(path)
447 return add_to_sounds(default_mediaplayer.data_source_fd(fd.file_descriptor, fd.start_offset, fd.length))
448 end
449
450 # Same as `load_sound` but load the sound from the `res/raw` folder
451 fun load_sound_from_res(sound_name: String): Sound do
452 return add_to_sounds(default_soundpool.load_name(resource_manager,self.native_activity, sound_name))
453 end
454
455 # Same as `load_music` but load the sound from the `res/raw` folder
456 fun load_music_from_res(music: String): Sound do
457 return add_to_sounds(default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity))
458 end
459
460 # Factorizes `sounds.add` to use it in `load_music`, `load_sound`, `load_music_from_res` and `load_sound_from_res`
461 private fun add_to_sounds(sound: Sound): Sound do
462 sounds.add(sound)
463 return sound
464 end
465
466 redef fun pause do
467 for s in sounds do s.pause
468 audio_manager.abandon_audio_focus
469 end
470
471 redef fun init_window do
472 super
473 audio_manager.request_audio_focus
474 manage_audio_stream
475 end
476
477 redef fun resume do
478 super
479 audio_manager.request_audio_focus
480 for s in sounds do s.resume
481 end
482 end