649a95d44e54d462778ea519baefe59d61228e47
[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 # This module modifies the default behaviour of the audio loading:
19 # It is first loaded from the `res/raw` folder.
20 # The file extension is not needed for the `res/raw` loading part.
21 # If it didn't work, it is loaded from the `assets` folder.
22 # The file extension is needed for the `assets` loading part.
23 #
24 # `assets` contains the portable version of sounds, since the `res` folder exsists only in android projects.
25 #
26 # For this example, the sounds "test_sound" and "test_music" are located in the "assets/sounds" folder,
27 # they both have ".ogg" extension. "test_sound" is a short sound and "test_music" a music track
28 #
29 # ~~~nitish
30 # # Note that you need to specify the path from "assets" folder and the extension
31 # var s = app.load_sound("sounds/test_sound.ogg")
32 # var m = app.load_music("sounds/test_music.ogg")
33 # s.play
34 # m.play
35 # ~~~
36 #
37 # Now, the sounds are in "res/raw"
38 # ~~~nitish
39 # s = app.load_sound_from_res("test_sound")
40 # m = app.load_music_from_res("test_sound")
41 # s.play
42 # m.play
43 # ~~~
44 #
45 # See http://developer.android.com/reference/android/media/package-summary.html for more infos
46 module audio
47
48 import java
49 import java::io
50 intrude import assets_and_resources
51 import activities
52 import app::audio
53
54 in "Java" `{
55 import android.media.MediaPlayer;
56 import android.media.SoundPool;
57 import java.io.IOException;
58 import android.media.AudioManager;
59 import android.media.AudioManager.OnAudioFocusChangeListener;
60 import android.content.Context;
61 import android.util.Log;
62 `}
63
64 # FIXME: This listener is not working at the moment, but is needed to gain or give up the audio focus
65 # of the application
66 in "Java inner" `{
67 static OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
68 public void onAudioFocusChange(int focusChange) {
69 if(focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
70 }else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
71 }else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
72 }
73 }
74 };
75 `}
76
77 # AudioManager of the application, used to manage the audio mode
78 private extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
79 super JavaObject
80
81 # Current audio mode.
82 # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
83 fun mode: Int in "Java" `{ return self.getMode(); `}
84
85 # Sets the audio mode.
86 # ( MODE_NORMAL = 0, MODE_RINGTONE = 1, MODE_IN_CALL = 2 or MODE_IN_COMMUNICATION = 3 )
87 fun mode=(i: Int) in "Java" `{ self.setMode((int)i); `}
88
89 # Sends a request to obtain audio focus
90 fun request_audio_focus: Int in "Java" `{
91 return self.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
92 `}
93
94 # Gives up audio focus
95 fun abandon_audio_focus: Int in "Java" `{ return self.abandonAudioFocus(afChangeListener); `}
96 end
97
98 # Media Player from Java, used to play long sounds or musics, not simultaneously
99 # This is a low-level class, use `MediaPlater` instead
100 private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
101 super JavaObject
102
103 new in "Java" `{
104 MediaPlayer mp = new MediaPlayer();
105 mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
106 return mp;
107 `}
108 fun start in "Java" `{ self.start(); `}
109 fun prepare in "Java" `{
110 try {
111 self.prepare();
112 }catch(Exception e) {
113 Log.e("Error preparing the Media Player", e.getMessage());
114 e.printStackTrace();
115 }
116 `}
117
118 fun create(context: NativeActivity, id: Int): NativeMediaPlayer in "Java" `{
119 try {
120 return self.create(context, (int)id);
121 }catch(Exception e) {
122 return null;
123 }
124 `}
125
126 fun pause in "Java" `{ self.pause(); `}
127 fun stop in "Java" `{ self.stop(); `}
128 fun playing: Bool in "Java" `{ return self.isPlaying(); `}
129 fun release in "Java" `{ self.release(); `}
130 fun duration: Int in "Java" `{ return self.getDuration(); `}
131 fun looping: Bool in "Java" `{ return self.isLooping(); `}
132 fun looping=(b: Bool) in "Java" `{ self.setLooping(b); `}
133 fun volume=(vol: Float) in "Java" `{ self.setVolume((float)vol, (float)vol); `}
134 fun both_volume(left_volume, right_volume: Float) in "Java" `{ self.setVolume((float)left_volume, (float)right_volume); `}
135 fun stream_type=(stream_type: Int) in "Java" `{ self.setAudioStreamType((int)stream_type); `}
136 fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int): Int in "Java" `{
137 try {
138 self.setDataSource(fd, start_offset, length);
139 return 1;
140 }catch(Exception e) {
141 return 0;
142 }
143 `}
144 fun data_source_path(path: JavaString): Int in "Java" `{
145 try {
146 self.setDataSource(path);
147 return 1;
148 }catch(Exception e) {
149 Log.e("Error loading the Media Player", e.getMessage());
150 return 0;
151 }
152 `}
153 fun reset in "Java" `{ self.reset(); `}
154 end
155
156 # Sound Pool from Java, used to play sounds simultaneously
157 # This is a low-level class, use `SoundPool`instead
158 private extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
159 super JavaObject
160
161 new(max_streams, stream_type, src_quality: Int) in "Java" `{
162 return new SoundPool((int)max_streams, (int)stream_type, (int)src_quality);
163 `}
164 fun load_asset_fd(afd: NativeAssetFileDescriptor, priority: Int): Int in "Java" `{
165 try {
166 int id = self.load(afd, (int)priority);
167 return id;
168 }catch(Exception e){
169 return -1;
170 }
171 `}
172
173 fun load_id(context: NativeActivity, resid, priority: Int): Int in "Java" `{
174 try {
175 int id = self.load(context, (int)resid, (int)priority);
176 return id;
177 }catch(Exception e){
178 return -1;
179 }
180 `}
181
182 fun load_path(path: JavaString, priority: Int): Int in "Java" `{
183 try {
184 int id = self.load(path, (int)priority);
185 return id;
186 }catch(Exception e){
187 return -1;
188 }
189 `}
190
191 fun play(sound_id: Int, left_volume, right_volume: Float, priority, l: Int, rate: Float): Int in "Java" `{
192 return self.play((int)sound_id, (float)left_volume, (float)right_volume, (int)priority, (int)l, (float)rate);
193 `}
194 fun pause(stream_id: Int) in "Java" `{ self.pause((int)stream_id); `}
195 fun auto_pause in "Java" `{ self.autoPause(); `}
196 fun auto_resume in "Java" `{ self.autoResume(); `}
197 fun resume(stream_id: Int) in "Java" `{ self.resume((int)stream_id); `}
198 fun set_loop(stream_id, l: Int) in "Java" `{ self.setLoop((int)stream_id, (int)l); `}
199 fun set_priority(stream_id, priority: Int) in "Java" `{ self.setPriority((int)stream_id, (int)priority); `}
200 fun set_rate(stream_id: Int, rate: Float) in "Java" `{ self.setRate((int)stream_id, (float)rate); `}
201 fun set_volume(stream_id: Int, left_volume, right_volume: Float) in "Java" `{ self.setVolume((int)stream_id, (float)left_volume, (float)right_volume); `}
202 fun stop(stream_id: Int) in "Java" `{ self.stop((int)stream_id); `}
203 fun unload(sound_id: Int): Bool in "Java" `{ return self.unload((int)sound_id); `}
204 fun release in "Java" `{ self.release(); `}
205 end
206
207
208 # Used to play sound, best suited for sounds effects in apps or games
209 class SoundPool
210
211 # Error gestion
212 var error: nullable Error = null
213
214 private var nsoundpool: NativeSoundPool is noinit
215 # The maximum number of simultaneous streams for this SoundPool
216 var max_streams = 10 is writable
217
218 # The audio stream type, 3 is STREAM_MUSIC, default for game application
219 var stream_type = 3 is writable
220
221 # The sample-rate converter quality, currently has no effect
222 var src_quality = 0 is writable
223
224 # Left volume value, range 0.0 to 1.0
225 var left_volume = 1.0 is writable
226
227 # Right volume value, range 0.0 to 1.0
228 var right_volume = 1.0 is writable
229
230 # Playback rate, 1.0 = normal playback, range 0.5 to 2.0
231 var rate = 1.0 is writable
232
233 # Loop mode, 0 = no loop, -1 = loop forever
234 var looping = 0 is writable
235
236 # Stream priority
237 private var priority = 1
238
239 init do self.nsoundpool = new NativeSoundPool(max_streams, stream_type, src_quality)
240
241 # Load the sound from an asset file descriptor
242 # this function is for advanced use
243 private fun load_asset_fd(afd: NativeAssetFileDescriptor): Sound do
244 var resval = nsoundpool.load_asset_fd(afd, self.priority)
245 if resval == -1 then
246 self.error = new Error("Unable to load sound from assets")
247 return new Sound.priv_init(null, -1, self, self.error)
248 else
249 return new Sound.priv_init(null, resval, self, null)
250 end
251 end
252
253 # Returns only the id corresponding to the soundpool where the sound is loaded.
254 # Needed by `load` of `Sound`.
255 private fun load_asset_fd_rid(afd: NativeAssetFileDescriptor): Int do
256 return nsoundpool.load_asset_fd(afd, self.priority)
257 end
258
259 # Load the sound from its resource id
260 fun load_id(context: NativeActivity, id:Int): Sound do
261 var resval = nsoundpool.load_id(context, id, priority)
262 if resval == -1 then
263 self.error = new Error("Unable to load sound from assets")
264 return new Sound.priv_init(null, -1, self, self.error)
265 else
266 return new Sound.priv_init(null, resval, self, null)
267 end
268 end
269
270 # Returns only the id corresponding to the soundpool where the sound is loaded.
271 private fun load_id_rid(context: NativeActivity, id: Int): Int do
272 return nsoundpool.load_id(context, id, priority)
273 end
274
275 # Load the sound from the specified path
276 fun load_path(path: String): Sound do
277 sys.jni_env.push_local_frame(1)
278 var resval = nsoundpool.load_path(path.to_java_string, priority)
279 sys.jni_env.pop_local_frame
280 if resval == -1 then
281 self.error = new Error("Unable to load sound from path : " + path)
282 return new Sound.priv_init(null, -1, self, self.error)
283 else
284 return new Sound.priv_init(null, resval, self, null)
285 end
286 end
287
288 # Play a sound from a sound ID
289 # return non-zero streamID if successful, zero if failed
290 fun play(id: Int): Int do
291 return nsoundpool.play(id, left_volume, right_volume, priority, looping, rate)
292 end
293
294 # Load a sound by its name in the resources, the sound must be in the `res/raw` folder
295 fun load_name(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Sound do
296 var id = resource_manager.raw_id(sound)
297 var resval = nsoundpool.load_id(context, id, priority)
298 if resval == -1 then
299 self.error = new Error("Unable to load sound from resources : " + sound)
300 return new Sound.priv_init(null, -1, self, self.error)
301 else
302 return new Sound.priv_init(null, resval, self, null)
303 end
304 end
305
306 # Returns only the id corresponding to the soundpool where the sound is loaded.
307 private fun load_name_rid(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Int do
308 var id = resource_manager.raw_id(sound)
309 return nsoundpool.load_id(context, id, priority)
310 end
311
312 # Pause a playback stream
313 fun pause_stream(stream_id: Int) do nsoundpool.pause(stream_id)
314
315 # Pause all active_streams
316 fun auto_pause do nsoundpool.auto_pause
317
318 # Resume all previously active streams
319 fun auto_resume do nsoundpool.auto_resume
320
321 # Resume a playback stream
322 fun resume(stream_id: Int) do nsoundpool.resume(stream_id)
323
324 # Set loop mode on a stream
325 fun stream_loop=(stream_id, looping: Int) do nsoundpool.set_loop(stream_id, looping)
326
327 # Change stream priority
328 fun stream_priority=(stream_id, priority: Int) do nsoundpool.set_priority(stream_id, priority)
329
330 # Change playback rate
331 fun stream_rate=(stream_id: Int, rate: Float) do nsoundpool.set_rate(stream_id, rate)
332
333 # Set stream volume
334 fun stream_volume(stream_id: Int, left_volume, right_volume: Float) do
335 nsoundpool.set_volume(stream_id, left_volume, right_volume)
336 end
337
338 # Stop a playback stream
339 fun stop_stream(stream_id: Int) do nsoundpool.stop(stream_id)
340
341 # Unload a sound from a sound ID
342 fun unload(sound: Sound): Bool do return nsoundpool.unload(sound.soundpool_id)
343
344 # Destroys the object
345 fun destroy do nsoundpool.release
346 end
347
348 # Used to play sounds, designed to use with medium sized sounds or streams
349 # The Android MediaPlayer has a complex state diagram that you'll need to
350 # respect if you want your MediaPlayer to work fine, see the android doc
351 class MediaPlayer
352 private var nmedia_player: NativeMediaPlayer is noinit
353
354 # Used to control the state of the mediaplayer
355 private var is_prepared = false is writable
356
357 # The sound associated with this mediaplayer
358 var sound: nullable Music = null is writable
359
360 # Error gestion
361 var error: nullable Error = null
362
363 # Create a new MediaPlayer, but no sound is attached, you'll need
364 # to use `load_sound` before using it
365 init do self.nmedia_player = new NativeMediaPlayer
366
367 # Init the mediaplayer with a sound resource id
368 init from_id(context: NativeActivity, id: Int) do
369 self.nmedia_player = new NativeMediaPlayer
370 self.nmedia_player = nmedia_player.create(context, id)
371 if self.nmedia_player.is_java_null then
372 self.error = new Error("Failed to create the MediaPlayer")
373 self.sound = new Music.priv_init(id, self, self.error)
374 end
375 self.sound = new Music.priv_init(id, self, null)
376 end
377
378 # Load a sound for a given resource id
379 fun load_sound(id: Int, context: NativeActivity): Music do
380 # FIXME: maybe find a better way to handle this situation
381 # If two different music are loaded with the same `MediaPlayer`,
382 # a new `NativeMediaPlayer` will be created for the secondd music
383 # and the nit program will loose the handle to the previous one
384 # If the previous music is playing, we need to stop it
385 if playing then
386 stop
387 reset
388 destroy
389 end
390 self.nmedia_player = self.nmedia_player.create(context, id)
391 if self.nmedia_player.is_java_null then
392 self.error = new Error("Failed to load a sound")
393 self.sound = new Music.priv_init(id, self, new Error("Sound loading failed"))
394 return self.sound.as(not null)
395 else
396 if self.error != null then self.error = null
397 self.sound = new Music.priv_init(id, self, null)
398 self.is_prepared = true
399 return self.sound.as(not null)
400 end
401 end
402
403 # Starts or resumes playback
404 # REQUIRE `self.sound != null`
405 fun start do
406 if self.error != null then return
407 if not is_prepared then prepare
408 nmedia_player.start
409 end
410
411 # Stops playback after playback has been stopped or paused
412 # REQUIRE `self.sound != null`
413 fun stop do
414 if self.error != null then return
415 is_prepared = false
416 nmedia_player.stop
417 end
418
419 # Prepares the player for playback, synchronously
420 # REQUIRE `self.sound != null`
421 fun prepare do
422 if self.error != null then return
423 assert sound != null
424 nmedia_player.prepare
425 is_prepared = true
426 end
427
428 # Pauses playback
429 # REQUIRE `self.sound != null`
430 fun pause do
431 if self.error != null then return
432 assert sound != null
433 nmedia_player.pause
434 end
435
436 # Checks whether the mediaplayer is playing
437 fun playing: Bool do return nmedia_player.playing
438
439 # Releases the resources associated with this MediaPlayer
440 fun destroy do
441 nmedia_player.release
442 self.sound = null
443 end
444
445 # Reset MediaPlayer to its initial state
446 fun reset do nmedia_player.reset
447
448 # Sets the datasource (file-path or http/rtsp URL) to use
449 fun data_source(path: String): Music do
450 sys.jni_env.push_local_frame(1)
451 var retval = nmedia_player.data_source_path(path.to_java_string)
452 sys.jni_env.pop_local_frame
453 if retval == 0 then
454 self.error = new Error("could not load the sound " + path)
455 self.sound = new Music.priv_init(null, self, self.error)
456
457 else
458 self.sound = new Music.priv_init(null, self, null)
459 end
460 return self.sound.as(not null)
461 end
462 # Sets the data source (NativeFileDescriptor) to use
463 fun data_source_fd(fd: NativeAssetFileDescriptor): Music do
464 if not fd.is_java_null then
465 if nmedia_player.data_source_fd(fd.file_descriptor, fd.start_offset, fd.length) == 0 then
466 self.error = new Error("could not load the sound")
467 self.sound = new Music.priv_init(null, self, self.error)
468 else
469 self.sound = new Music.priv_init(null, self, null)
470 end
471 return self.sound.as(not null)
472 else
473 var error = new Error("could not load the sound")
474 return new Music.priv_init(null, self, error)
475 end
476 end
477
478 # Checks whether the MediaPlayer is looping or non-looping
479 fun looping: Bool do return nmedia_player.looping
480
481 # Sets the player to be looping or non-looping
482 fun looping=(b: Bool) do nmedia_player.looping = b
483
484 # Sets the volume on this player
485 fun volume=(volume: Float) do nmedia_player.volume = volume
486
487 # Sets the left volume and the right volume of this player
488 fun both_volume(left_volume, right_volume: Float) do nmedia_player.both_volume(left_volume, right_volume)
489
490 # Sets the audio stream type for this media player
491 fun stream_type=(stream_type: Int) do nmedia_player.stream_type = stream_type
492 end
493
494 redef class PlayableAudio
495 # Flag to know if the user paused the sound
496 # Used when the app pause all sounds or resume all sounds
497 var paused: Bool = false
498
499 redef init do add_to_sounds(self)
500 end
501
502 redef class Sound
503
504 # Resource ID of this sound
505 var id: nullable Int is noinit
506
507 # The SoundPool who loaded this sound
508 var soundpool: SoundPool is noinit
509
510 # The SoundID of this sound in his SoundPool
511 var soundpool_id: Int is noinit
512
513 private init priv_init(id: nullable Int, soundpool_id: Int, soundpool: SoundPool, error: nullable Error) is nosuper do
514 self.id = id
515 self.soundpool_id = soundpool_id
516 self.soundpool = soundpool
517 if error != null then
518 self.error = error
519 else
520 self.is_loaded = true
521 end
522 end
523
524 redef fun load do
525 if is_loaded then return
526 var retval_resources = app.default_soundpool.load_name_rid(app.resource_manager, app.native_activity, self.name.strip_extension)
527 if retval_resources == -1 then
528 self.error = new Error("failed to load" + self.name)
529 var nam = app.asset_manager.open_fd(self.name)
530 if nam.is_java_null then
531 self.error = new Error("Failed to get file descriptor for " + self.name)
532 else
533 var retval_assets = app.default_soundpool.load_asset_fd_rid(nam)
534 if retval_assets == -1 then
535 self.error = new Error("Failed to load" + self.name)
536 else
537 self.soundpool_id = retval_assets
538 self.soundpool = app.default_soundpool
539 self.error = null
540 self.soundpool.error = null
541 end
542 end
543 else
544 self.soundpool_id = retval_resources
545 self.soundpool = app.default_soundpool
546 self.error = null
547 self.soundpool.error = null
548 end
549 is_loaded = true
550 end
551
552 redef fun play do
553 if not is_loaded then load
554 if self.error != null then return
555 soundpool.play(soundpool_id)
556 end
557
558 redef fun pause do
559 if self.error != null or not self.is_loaded then return
560 soundpool.pause_stream(soundpool_id)
561 paused = true
562 end
563
564 redef fun resume do
565 if self.error != null or not self.is_loaded then return
566 soundpool.resume(soundpool_id)
567 paused = false
568 end
569
570 end
571
572 redef class Music
573
574 # Resource ID of this sound
575 var id: nullable Int is noinit
576
577 # The MediaPlayer who loaded this sound
578 var media_player: MediaPlayer is noinit
579
580 private init priv_init(id: nullable Int, media_player: MediaPlayer, error: nullable Error) is nosuper do
581 self.id = id
582 self.media_player = media_player
583 if error != null then
584 self.error = error
585 else
586 self.is_loaded = true
587 end
588 end
589
590 redef fun load do
591 if is_loaded then return
592 var mp_sound_resources = app.default_mediaplayer.load_sound(app.resource_manager.raw_id(self.name.strip_extension), app.native_activity)
593 if mp_sound_resources.error != null then
594 self.error = mp_sound_resources.error
595 var nam = app.asset_manager.open_fd(self.name)
596 if nam.is_java_null then
597 self.error = new Error("Failed to get file descriptor for " + self.name)
598 else
599 var mp_sound_assets = app.default_mediaplayer.data_source_fd(nam)
600 if mp_sound_assets.error != null then
601 self.error = mp_sound_assets.error
602 else
603 self.media_player = app.default_mediaplayer
604 self.error = null
605 self.media_player.error = null
606 end
607 end
608 else
609 self.media_player = app.default_mediaplayer
610 self.error = null
611 self.media_player.error = null
612 end
613 is_loaded = true
614 end
615
616 redef fun play do
617 if not is_loaded then load
618 if self.error != null then return
619 media_player.start
620 end
621
622 redef fun pause do
623 if self.error != null or not self.is_loaded then return
624 media_player.pause
625 paused = true
626 end
627
628 redef fun resume do
629 if self.error != null or not self.is_loaded then return
630 play
631 paused = false
632 end
633 end
634
635 redef class App
636
637
638 # Returns the default MediaPlayer of the application.
639 # When you load a music, it goes in this MediaPlayer.
640 # Use it for advanced sound management
641 var default_mediaplayer: MediaPlayer is lazy do return new MediaPlayer
642
643 # Returns the default MediaPlayer of the application.
644 # When you load a short sound (not a music), it's added to this soundpool.
645 # Use it for advanced sound management.
646 var default_soundpool: SoundPool is lazy do return new SoundPool
647
648 # Get the native audio manager
649 private fun audio_manager: NativeAudioManager import native_activity in "Java" `{
650 return (AudioManager)App_native_activity(self).getSystemService(Context.AUDIO_SERVICE);
651 `}
652
653 # Sets the stream of the app to STREAM_MUSIC.
654 # STREAM_MUSIC is the default stream used by android apps.
655 private fun manage_audio_stream import native_activity in "Java" `{
656 App_native_activity(self).setVolumeControlStream(AudioManager.STREAM_MUSIC);
657 `}
658
659 # Retrieves a sound with a soundpool in the `assets` folder using its name.
660 # Used to play short songs, can play multiple sounds simultaneously
661 redef fun load_sound(path) do
662 var fd = asset_manager.open_fd(path)
663 if not fd.is_java_null then
664 return add_to_sounds(default_soundpool.load_asset_fd(fd)).as(Sound)
665 else
666 var error = new Error("Failed to load Sound {path}")
667 return new Sound.priv_init(null, -1, default_soundpool, error)
668 end
669 end
670
671 # Retrieves a music with a media player in the `assets` folder using its name.
672 # Used to play long sounds or musics, can't play multiple sounds simultaneously
673 redef fun load_music(path) do
674 var fd = asset_manager.open_fd(path)
675 if not fd.is_java_null then
676 return add_to_sounds(default_mediaplayer.data_source_fd(fd)).as(Music)
677 else
678 var error = new Error("Failed to load music {path}")
679 return new Music.priv_init(null, default_mediaplayer, error)
680 end
681 end
682
683 # Same as `load_sound` but load the sound from the `res/raw` folder
684 fun load_sound_from_res(sound_name: String): Sound do
685 return add_to_sounds(default_soundpool.load_name(resource_manager,self.native_activity, sound_name)).as(Sound)
686 end
687
688 # Same as `load_music` but load the sound from the `res/raw` folder
689 fun load_music_from_res(music: String): Music do
690 return add_to_sounds(default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity)).as(Music)
691 end
692
693 redef fun on_pause do
694 super
695 for s in sounds do
696 # Pausing sounds that are not already paused by user
697 # `s.paused` is set to false because `pause` set it to true
698 # and we want to know which sound has been paused by the user
699 # and which one has been paused by the app
700 if not s.paused then
701 s.pause
702 s.paused = false
703 end
704 end
705 audio_manager.abandon_audio_focus
706 end
707
708 redef fun on_create do
709 super
710 audio_manager.request_audio_focus
711 manage_audio_stream
712 end
713
714 redef fun on_resume do
715 super
716 audio_manager.request_audio_focus
717 for s in sounds do
718 # Resumes only the sounds paused by the App
719 if not s.paused then s.resume
720 end
721 end
722 end
723
724 redef class Sys
725
726 # Sounds handled by the application, when you load a sound, it's added to this list.
727 # This array is used in `pause` and `resume`
728 private var sounds = new Array[PlayableAudio]
729
730 # Factorizes `sounds.add` to use it in `load_music`, `load_sound`, `load_music_from_res` and `load_sound_from_res`
731 private fun add_to_sounds(sound: PlayableAudio): PlayableAudio do
732 sounds.add(sound)
733 return sound
734 end
735 end