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