b46a660e23968460997f5f5e2583f6ec2c283bbc
[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
18 #
19 # You can get a sound by loading it with a `SoundPool` or a `MediaPlayer`
20 # the recommended way to load a sound is by it's resource ID or it's name
21 # other ways are for advanced use
22 module audio
23
24 import java
25 import java_io
26 import assets_and_resources
27 import app
28
29 in "Java" `{
30 import android.media.MediaPlayer;
31 import android.media.SoundPool;
32 import java.io.IOException;
33 import android.media.AudioManager;
34 import android.content.Context;
35 import android.util.Log;
36 `}
37
38 # AudioManager of the application, used to manage the audio mode
39 extern class NativeAudioManager in "Java" `{ android.media.AudioManager `}
40 super JavaObject
41 redef type SELF: NativeAudioManager
42
43 fun mode: Int in "Java" `{ return recv.getMode(); `}
44 fun mode=(i: Int) in "Java" `{ recv.setMode(i); `}
45 fun wired_headset_on: Bool in "Java" `{ return recv.isWiredHeadsetOn(); `}
46 fun wired_headset_on=(b: Bool) in "Java" `{ recv.setWiredHeadsetOn(b); `}
47 fun speakerphone_on: Bool in "Java" `{ return recv.isSpeakerphoneOn(); `}
48 fun speakerphone_on=(b: Bool) in "Java" `{ recv.setSpeakerphoneOn(b); `}
49 fun manage_audio_mode in "Java" `{
50 recv.setMode(0);
51 if (recv.isWiredHeadsetOn()) {
52 recv.setSpeakerphoneOn(false);
53 } else {
54 recv.setSpeakerphoneOn(true);
55 }
56 `}
57
58 end
59
60 # Media Player from Java, used to play long sounds or musics, not simultaneously
61 # This is a low-level class, use `MediaPlater` instead
62 extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
63 super JavaObject
64 redef type SELF: NativeMediaPlayer
65
66 new in "Java" `{ return new MediaPlayer(); `}
67 fun start in "Java" `{ recv.start(); `}
68 fun prepare in "Java" `{
69 try {
70 recv.prepare();
71 }catch(IOException e) {
72 Log.e("Error preparing the Media Player", e.getMessage());
73 e.printStackTrace();
74 }
75 `}
76
77 fun create(context: NativeActivity, id: Int): NativeMediaPlayer in "Java" `{ return recv.create(context, id); `}
78 fun pause in "Java" `{ recv.pause(); `}
79 fun stop in "Java" `{ recv.stop(); `}
80 fun playing: Bool in "Java" `{ return recv.isPlaying(); `}
81 fun release in "Java" `{ recv.release(); `}
82 fun duration: Int in "Java" `{ return recv.getDuration(); `}
83 fun looping: Bool in "Java" `{ return recv.isLooping(); `}
84 fun looping=(b: Bool) in "Java" `{ recv.setLooping(b); `}
85 fun volume=(vol: Float) in "Java" `{ recv.setVolume((float)vol, (float)vol); `}
86 fun both_volume(left_volume, right_volume: Float) in "Java" `{ recv.setVolume((float)left_volume, (float)right_volume); `}
87 fun stream_type=(stream_type: Int) in "Java" `{ recv.setAudioStreamType(stream_type); `}
88 fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int) in "Java" `{
89 try {
90 recv.setDataSource(fd, start_offset, length);
91 }catch(IOException e) {
92 Log.e("Error loading the Media Player with a file descriptor", e.getMessage());
93 e.printStackTrace();
94 }
95 `}
96 fun data_source_path(path: JavaString) in "Java" `{
97 try {
98 recv.setDataSource(path);
99 }catch(IOException e) {
100 Log.e("Error loading the Media Player", e.getMessage());
101 e.printStackTrace();
102 }
103 `}
104 end
105
106 # Sound Pool from Java, used to play sounds simultaneously
107 # This is a low-level class, use `SoundPool`instead
108 extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
109 super JavaObject
110 redef type SELF: NativeSoundPool
111
112 new(max_streams, stream_type, src_quality: Int) in "Java" `{
113 return new SoundPool(max_streams, stream_type, src_quality);
114 `}
115 fun load_asset_fd(afd: NativeAssetFileDescriptor, priority: Int): Int in "Java" `{ return recv.load(afd, priority); `}
116 fun load_id(context: NativeActivity, resid, priority: Int): Int in "Java" `{ return recv.load(context, resid, priority); `}
117 fun load_path(path: JavaString, priority: Int): Int in "Java" `{ return recv.load(path, priority); `}
118 fun play(sound_id: Int, left_volume, right_volume: Float, priority, l: Int, rate: Float): Int in "Java" `{
119 return recv.play(sound_id, (float)left_volume, (float)right_volume, priority, l, (float)rate);
120 `}
121 fun pause(stream_id: Int) in "Java" `{ recv.pause(stream_id); `}
122 fun auto_pause in "Java" `{ recv.autoPause(); `}
123 fun auto_resume in "Java" `{ recv.autoResume(); `}
124 fun resume(stream_id: Int) in "Java" `{ recv.resume(stream_id); `}
125 fun set_loop(stream_id, l: Int) in "Java" `{ recv.setLoop(stream_id, l); `}
126 fun set_priority(stream_id, priority: Int) in "Java" `{ recv.setPriority(stream_id, priority); `}
127 fun set_rate(stream_id: Int, rate: Float) in "Java" `{ recv.setRate(stream_id, (float)rate); `}
128 fun set_volume(stream_id: Int, left_volume, right_volume: Float) in "Java" `{ recv.setVolume(stream_id, (float)left_volume, (float)right_volume); `}
129 fun stop(stream_id: Int) in "Java" `{ recv.stop(stream_id); `}
130 fun unload(sound_id: Int): Bool in "Java" `{ return recv.unload(sound_id); `}
131 end
132
133
134 # Used to play sound, best suited for sounds effects in apps or games
135 class SoundPool
136 private var nsoundpool: NativeSoundPool
137 # The maximum number of simultaneous streams for this SoundPool
138 var max_streams writable = 10
139
140 # The audio stream type, 3 is STREAM_MUSIC, default for game application
141 var stream_type writable = 3
142
143 # The sample-rate converter quality, currently has no effect
144 var src_quality writable = 0
145
146 # Left volume value, range 0.0 to 1.0
147 var left_volume writable = 1.0
148
149 # Right volume value, range 0.0 to 1.0
150 var right_volume writable = 1.0
151
152 # Playback rate, 1.0 = normal playback, range 0.5 to 2.0
153 var rate writable = 1.0
154
155 # Loop mode, 0 = no loop, -1 = loop forever
156 var looping writable = 0
157
158 # Stream priority
159 private var priority = 1
160
161 init do self.nsoundpool = new NativeSoundPool(max_streams, stream_type, src_quality)
162
163 # Load the sound from an asset file descriptor
164 # this function is for advanced use
165 fun load_asset_fd(afd: NativeAssetFileDescriptor): Sound do
166 return new SoundSP(null, nsoundpool.load_asset_fd(afd, priority), self)
167 end
168
169 # Load the sound from it's resource id
170 fun load_id(context: NativeActivity, id:Int): Sound do
171 return new SoundSP(null, nsoundpool.load_id(context, id, priority), self)
172 end
173
174 # Load the sound from the specified path
175 fun load_path(path: String): Sound do
176 sys.jni_env.push_local_frame(1)
177 var return_value = new SoundSP(0, nsoundpool.load_path(path.to_java_string, priority), self)
178 sys.jni_env.pop_local_frame
179 return return_value
180 end
181
182 # Play a sound from a sound ID
183 # return non-zero streamID if successful, zero if failed
184 fun play(id: Int): Int do
185 return nsoundpool.play(id, left_volume, right_volume, priority, looping, rate)
186 end
187
188 # Load a sound by it's name in the resources, the sound must be in the `res/raw` folder
189 fun load_name(resource_manager: ResourcesManager, context: NativeActivity, sound: String): Sound do
190 var id = resource_manager.raw_id(sound)
191 return new SoundSP(id, nsoundpool.load_id(context, id, priority), self)
192 end
193
194 # Pause a playback stream
195 fun pause_stream(stream_id: Int) do nsoundpool.pause(stream_id)
196
197 # Pause all active_streams
198 fun auto_pause do nsoundpool.auto_pause
199
200 # Resume all previously active streams
201 fun auto_resume do nsoundpool.auto_resume
202
203 # Resume a playback stream
204 fun resume(stream_id: Int) do nsoundpool.resume(stream_id)
205
206 # Set loop mode on a stream
207 fun stream_loop=(stream_id, looping: Int) do nsoundpool.set_loop(stream_id, looping)
208
209 # Change stream priority
210 fun stream_priority=(stream_id, priority: Int) do nsoundpool.set_priority(stream_id, priority)
211
212 # Change playback rate
213 fun stream_rate=(stream_id: Int, rate: Float) do nsoundpool.set_rate(stream_id, rate)
214
215 # Set stream volume
216 fun stream_volume(stream_id: Int, left_volume, right_volume: Float) do
217 nsoundpool.set_volume(stream_id, left_volume, right_volume)
218 end
219
220 # Stop a playback stream
221 fun stop_stream(stream_id: Int) do nsoundpool.stop(stream_id)
222
223 # Unload a sound from a sound ID
224 fun unload(sound: SoundSP): Bool do return nsoundpool.unload(sound.soundpool_id)
225 end
226
227 # Used to play sounds, designed to use with medium sized sounds or streams
228 # The Android MediaPlayer has a complex state diagram that you'll need to
229 # respect if you want your MediaPlayer to work fine, see the android doc
230 class MediaPlayer
231 private var nmedia_player: NativeMediaPlayer
232
233 # The sound associated with this mediaplayer
234 var sound: nullable Sound
235
236 # Create a new MediaPlayer, but no sound is attached, you'll need
237 # to use `load_sound` before using it
238 init do self.nmedia_player = new NativeMediaPlayer
239
240 # Init the mediaplayer with a sound resource id
241 init from_id(context: NativeActivity, id: Int) do
242 self.nmedia_player = new NativeMediaPlayer
243 self.nmedia_player = nmedia_player.create(context, id)
244 self.sound = new SoundMP(id, self)
245 end
246
247 # Load a sound for a given resource id
248 fun load_sound(id: Int, context: NativeActivity): Sound do
249 self.nmedia_player = self.nmedia_player.create(context, id)
250 self.sound = new SoundMP(id, self)
251 return self.sound.as(not null)
252 end
253
254 # Starts or resume playback
255 # REQUIRE `self.sound != null`
256 fun start do
257 assert sound != null
258 nmedia_player.start
259 end
260
261 # Stops playback after playback has been stopped or paused
262 # REQUIRE `self.sound != null`
263 fun stop do
264 assert sound != null
265 nmedia_player.stop
266 end
267
268 # Prepares the player for playback, synchronously
269 # REQUIRE `self.sound != null`
270 fun prepare do
271 assert sound != null
272 nmedia_player.prepare
273 end
274
275 # Pauses playback
276 # REQUIRE `self.sound != null`
277 fun pause do
278 assert sound != null
279 nmedia_player.pause
280 end
281
282 # Checks whether the mediaplayer is playing
283 fun playing: Bool do return nmedia_player.playing
284
285 # Releases the resources associated with this MediaPlayer
286 fun destroy do
287 nmedia_player.release
288 self.sound = null
289 end
290
291 # Sets the datasource (file-pathor http/rtsp URL) to use
292 fun data_source(path: String): Sound do
293 sys.jni_env.push_local_frame(1)
294 nmedia_player.data_source_path(path.to_java_string)
295 sys.jni_env.pop_local_frame
296 self.sound = new SoundMP(null, self)
297 return self.sound.as(not null)
298 end
299 # Sets the data source (NativeFileDescriptor) to use
300 fun data_source_fd(fd: NativeFileDescriptor, start_offset, length: Int): Sound do
301 nmedia_player.data_source_fd(fd, start_offset, length)
302 self.sound = new SoundMP(null, self)
303 return self.sound.as(not null)
304 end
305
306 # Checks whether the MediaPlayer is looping or non-looping
307 fun looping: Bool do return nmedia_player.looping
308
309 # Sets the player to be looping or non-looping
310 fun looping=(b: Bool) do nmedia_player.looping = b
311
312 # Sets the volume on this player
313 fun volume=(volume: Float) do nmedia_player.volume = volume
314
315 # Sets the left volume and the right volume of this player
316 fun both_volume(left_volume, right_volume: Float) do nmedia_player.both_volume(left_volume, right_volume)
317
318 # Sets the audio stream type for this media player
319 fun stream_type=(stream_type: Int) do nmedia_player.stream_type = stream_type
320
321 end
322
323 # Represents an android sound that can be played by a SoundPool or a MediaPlayer
324 # The only way to get a sound is by a MediaPlayer, a SoundPool, or the App
325 abstract class Sound
326
327 # The resource ID of this sound
328 var id: nullable Int
329
330 fun play is abstract
331 end
332
333 # Sound implemented with a SoundPool
334 class SoundSP
335 super Sound
336
337 # The SoundPool who loaded this sound
338 var soundpool: SoundPool
339
340 # The SoundID of this sound in his SoundPool
341 var soundpool_id: Int
342
343 private init (id: nullable Int, soundpool_id: Int, soundpool: SoundPool) do
344 self.id = id
345 self.soundpool_id = soundpool_id
346 self.soundpool = soundpool
347 end
348
349 redef fun play do soundpool.play(soundpool_id)
350 end
351
352 # Sound Implemented with a MediaPlayer
353 class SoundMP
354 super Sound
355
356 # The MediaPlayer who loaded this sound
357 var media_player: MediaPlayer
358
359 private init (id: nullable Int, media_player: MediaPlayer) do
360 self.id = id
361 self.media_player = media_player
362 end
363
364 redef fun play do self.media_player.start
365 end
366
367 redef class App
368
369 fun default_mediaplayer: MediaPlayer is cached do return new MediaPlayer
370 fun default_soundpool: SoundPool is cached do return new SoundPool
371
372 # Get the native audio manager
373 private fun audio_manager: NativeAudioManager import native_activity in "Java" `{
374 return (AudioManager)App_native_activity(recv).getSystemService(Context.AUDIO_SERVICE);
375 `}
376
377 # Manages whether the app sound need to be in headphones mode or not
378 # TODO: this method is not ideal, need to find a better way
379 fun manage_audio_mode import native_activity in "Java" `{
380 AudioManager manager = (AudioManager)App_native_activity(recv).getSystemService(Context.AUDIO_SERVICE);
381 manager.setMode(0);
382 if (manager.isWiredHeadsetOn()) {
383 manager.setSpeakerphoneOn(false);
384 } else {
385 manager.setSpeakerphoneOn(true);
386 }
387 `}
388
389 # Retrieve a sound with a soundpool in the `assets` folder using it's name
390 # Used to play short songs, can play multiple sounds simultaneously
391 fun load_sound(path: String): Sound do
392 return default_soundpool.load_asset_fd(asset_manager.open_fd(path))
393 end
394
395 # Retrieve a music with a media player in the `assets`folder using it's name
396 # Used to play long sounds or musics, can't play multiple sounds simultaneously
397 fun load_music(path: String): Sound do
398 var fd = asset_manager.open_fd(path)
399 var sound = default_mediaplayer.data_source_fd(fd.file_descriptor, fd.start_offset, fd.length)
400 return sound
401 end
402
403 # same as `load_sound` but load the sound from the `res\raw` folder
404 fun load_sound_from_res(sound_name: String): Sound do
405 return default_soundpool.load_name(resource_manager,self.native_activity, sound_name)
406 end
407
408 # same as `load_music` but load the sound from the `res\raw` folder
409 fun load_music_from_res(music: String): Sound do
410 return default_mediaplayer.load_sound(resource_manager.raw_id(music), self.native_activity)
411 end
412
413 end