7 package com.google.appinventor.components.runtime;
24 import android.Manifest;
25 import android.media.MediaRecorder;
26 import android.media.MediaRecorder.OnErrorListener;
27 import android.media.MediaRecorder.OnInfoListener;
28 import android.os.Environment;
29 import android.util.Log;
31 import java.io.IOException;
39 @DesignerComponent(version = YaVersion.SOUND_RECORDER_COMPONENT_VERSION,
40 description =
"<p>Multimedia component that records audio.</p>",
41 category = ComponentCategory.MEDIA,
43 iconName =
"images/soundRecorder.png")
45 @UsesPermissions(permissionNames =
"android.permission.RECORD_AUDIO," +
46 "android.permission.WRITE_EXTERNAL_STORAGE," +
47 "android.permission.READ_EXTERNAL_STORAGE")
49 implements
Component, OnErrorListener, OnInfoListener {
51 private static final String TAG =
"SoundRecorder";
56 private String savedRecording =
"";
59 private boolean havePermission =
false;
64 private class RecordingController {
65 final MediaRecorder recorder;
71 RecordingController(String savedRecording)
throws IOException {
73 file = (savedRecording.equals(
"")) ?
77 recorder =
new MediaRecorder();
78 recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
79 recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
80 recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
81 Log.i(TAG,
"Setting output file to " + file);
82 recorder.setOutputFile(file);
83 Log.i(TAG,
"preparing");
89 void start()
throws IllegalStateException {
90 Log.i(TAG,
"starting");
94 }
catch (IllegalStateException e) {
98 Log.e(TAG,
"got IllegalStateException. Are there two recorders running?", e);
101 throw (
new IllegalStateException(
"Is there another recording running?"));
106 recorder.setOnErrorListener(
null);
107 recorder.setOnInfoListener(
null);
118 private RecordingController controller;
121 super(container.
$form());
134 description =
"Specifies the path to the file where the recording should be stored. " +
135 "If this property is the empty string, then starting a recording will create a file in " +
136 "an appropriate location. If the property is not the empty string, it should specify " +
137 "a complete path to a file in an existing directory, including a file name with the " +
140 public String SavedRecording() {
141 return savedRecording;
154 savedRecording = pathName;
163 if (!havePermission) {
165 form.runOnUiThread(
new Runnable() {
169 Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE) {
171 public void onGranted() {
172 me.havePermission = true;
181 if (controller !=
null) {
182 Log.i(TAG,
"Start() called, but already recording to " + controller.file);
185 Log.i(TAG,
"Start() called");
186 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
187 form.dispatchErrorOccurredEvent(
188 this,
"Start", ErrorMessages.ERROR_MEDIA_EXTERNAL_STORAGE_NOT_AVAILABLE);
192 controller =
new RecordingController(savedRecording);
193 }
catch (PermissionException e) {
194 form.dispatchPermissionDeniedEvent(
this,
"Start", e);
196 }
catch (Throwable t) {
197 form.dispatchErrorOccurredEvent(
198 this,
"Start", ErrorMessages.ERROR_SOUND_RECORDER_CANNOT_CREATE, t.getMessage());
203 }
catch (Throwable t) {
208 form.dispatchErrorOccurredEvent(
209 this,
"Start", ErrorMessages.ERROR_SOUND_RECORDER_CANNOT_CREATE, t.getMessage());
216 public void onError(MediaRecorder affectedRecorder,
int what,
int extra) {
217 if (controller ==
null || affectedRecorder != controller.recorder) {
218 Log.w(TAG,
"onError called with wrong recorder. Ignoring.");
224 }
catch (Throwable e) {
225 Log.w(TAG, e.getMessage());
233 public void onInfo(MediaRecorder affectedRecorder,
int what,
int extra) {
234 if (controller ==
null || affectedRecorder != controller.recorder) {
235 Log.w(TAG,
"onInfo called with wrong recorder. Ignoring.");
239 case MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED:
240 form.dispatchErrorOccurredEvent(
this,
"recording",
243 case MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED:
244 form.dispatchErrorOccurredEvent(
this,
"recording",
247 case MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN:
257 Log.i(TAG,
"Recoverable condition while recording. Will attempt to stop normally.");
258 controller.recorder.stop();
259 }
catch(IllegalStateException e) {
260 Log.i(TAG,
"SoundRecorder was not in a recording state.", e);
261 form.dispatchErrorOccurredEventDialog(
this,
"Stop",
274 if (controller ==
null) {
275 Log.i(TAG,
"Stop() called, but already stopped.");
279 Log.i(TAG,
"Stop() called");
280 Log.i(TAG,
"stopping");
282 Log.i(TAG,
"Firing AfterSoundRecorded with " + controller.file);
283 AfterSoundRecorded(controller.file);
284 }
catch (Throwable t) {
292 @
SimpleEvent(description =
"Provides the location of the newly created sound.")
293 public
void AfterSoundRecorded(final String sound) {
297 @
SimpleEvent(description =
"Indicates that the recorder has started, and can be stopped.")
298 public
void StartedRecording() {
302 @
SimpleEvent(description =
"Indicates that the recorder has stopped, and can be started again.")
303 public
void StoppedRecording() {