/* * Copyright (C) 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.googlecode.android_scripting.facade; import android.app.Service; import android.content.Intent; import android.media.MediaRecorder; import android.net.Uri; import android.provider.MediaStore; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; import android.view.WindowManager; import com.googlecode.android_scripting.BaseApplication; import com.googlecode.android_scripting.FutureActivityTaskExecutor; import com.googlecode.android_scripting.Log; import com.googlecode.android_scripting.future.FutureActivityTask; import com.googlecode.android_scripting.jsonrpc.RpcReceiver; import com.googlecode.android_scripting.rpc.Rpc; import com.googlecode.android_scripting.rpc.RpcDefault; import com.googlecode.android_scripting.rpc.RpcOptional; import com.googlecode.android_scripting.rpc.RpcParameter; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * A facade for recording media. * * Guidance notes: Use e.g. '/sdcard/file.ext' for your media destination file. A file extension of * mpg will use the default settings for format and codec (often h263 which won't work with common * PC media players). A file extension of mp4 or 3gp will use the appropriate format with the (more * common) h264 codec. A video player such as QQPlayer (from the android market) plays both codecs * and uses the composition matrix (embedded in the video file) to correct for image rotation. Many * PC based media players ignore this matrix. Standard video sizes may be specified. * * @author Felix Arends (felix.arends@gmail.com) * @author Damon Kohler (damonkohler@gmail.com) * @author John Karwatzki (jokar49@gmail.com) */ public class MediaRecorderFacade extends RpcReceiver { private final MediaRecorder mMediaRecorder = new MediaRecorder(); private final Service mService; public MediaRecorderFacade(FacadeManager manager) { super(manager); mService = manager.getService(); } @Rpc(description = "Records audio from the microphone and saves it to the given location.") public void recorderStartMicrophone(@RpcParameter(name = "targetPath") String targetPath) throws IOException { startAudioRecording(targetPath, MediaRecorder.AudioSource.MIC); } @Rpc(description = "Records video from the camera and saves it to the given location. " + "\nDuration specifies the maximum duration of the recording session. " + "\nIf duration is 0 this method will return and the recording will only be stopped " + "\nwhen recorderStop is called or when a scripts exits. " + "\nOtherwise it will block for the time period equal to the duration argument." + "\nvideoSize: 0=160x120, 1=320x240, 2=352x288, 3=640x480, 4=800x480.") public void recorderStartVideo(@RpcParameter(name = "targetPath") String targetPath, @RpcParameter(name = "duration") @RpcDefault("0") Integer duration, @RpcParameter(name = "videoSize") @RpcDefault("1") Integer videoSize) throws Exception { int ms = convertSecondsToMilliseconds(duration); startVideoRecording(new File(targetPath), ms, videoSize); } private void startVideoRecording(File file, int milliseconds, int videoSize) throws Exception { mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); int audioSource = MediaRecorder.AudioSource.MIC; try { Field source = Class.forName("android.media.MediaRecorder$AudioSource").getField("CAMCORDER"); source.getInt(null); } catch (Exception e) { Log.e(e); } int xSize; int ySize; switch (videoSize) { case 0: xSize = 160; ySize = 120; break; case 1: xSize = 320; ySize = 240; break; case 2: xSize = 352; ySize = 288; break; case 3: xSize = 640; ySize = 480; break; case 4: xSize = 800; ySize = 480; break; default: xSize = 320; ySize = 240; break; } mMediaRecorder.setAudioSource(audioSource); String extension = file.toString().split("\\.")[1]; if (extension.equals("mp4")) { mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mMediaRecorder.setVideoSize(xSize, ySize); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); } else if (extension.equals("3gp")) { mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); mMediaRecorder.setVideoSize(xSize, ySize); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); } else { mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mMediaRecorder.setVideoSize(xSize, ySize); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); } mMediaRecorder.setOutputFile(file.getAbsolutePath()); if (milliseconds > 0) { mMediaRecorder.setMaxDuration(milliseconds); } FutureActivityTask<Exception> prepTask = prepare(); mMediaRecorder.start(); if (milliseconds > 0) { new CountDownLatch(1).await(milliseconds, TimeUnit.MILLISECONDS); } prepTask.finish(); } @Rpc(description = "Records video (and optionally audio) from the camera and saves it to the given location. " + "\nDuration specifies the maximum duration of the recording session. " + "\nIf duration is not provided this method will return immediately and the recording will only be stopped " + "\nwhen recorderStop is called or when a scripts exits. " + "\nOtherwise it will block for the time period equal to the duration argument.") public void recorderCaptureVideo(@RpcParameter(name = "targetPath") String targetPath, @RpcParameter(name = "duration") @RpcOptional Integer duration, @RpcParameter(name = "recordAudio") @RpcDefault("true") Boolean recordAudio) throws Exception { int ms = convertSecondsToMilliseconds(duration); startVideoRecording(new File(targetPath), ms, recordAudio); } private void startVideoRecording(File file, int milliseconds, boolean withAudio) throws Exception { mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); if (withAudio) { int audioSource = MediaRecorder.AudioSource.MIC; try { Field source = Class.forName("android.media.MediaRecorder$AudioSource").getField("CAMCORDER"); audioSource = source.getInt(null); } catch (Exception e) { Log.e(e); } mMediaRecorder.setAudioSource(audioSource); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); } else { mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); } mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT); mMediaRecorder.setOutputFile(file.getAbsolutePath()); if (milliseconds > 0) { mMediaRecorder.setMaxDuration(milliseconds); } FutureActivityTask<Exception> prepTask = prepare(); mMediaRecorder.start(); if (milliseconds > 0) { new CountDownLatch(1).await(milliseconds, TimeUnit.MILLISECONDS); } prepTask.finish(); } private void startAudioRecording(String targetPath, int source) throws IOException { mMediaRecorder.setAudioSource(source); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); mMediaRecorder.setOutputFile(targetPath); mMediaRecorder.prepare(); mMediaRecorder.start(); } @Rpc(description = "Stops a previously started recording.") public void recorderStop() { mMediaRecorder.stop(); mMediaRecorder.reset(); } @Rpc(description = "Starts the video capture application to record a video and saves it to the specified path.") public void startInteractiveVideoRecording(@RpcParameter(name = "path") final String path) { Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); File file = new File(path); intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); AndroidFacade facade = mManager.getReceiver(AndroidFacade.class); facade.startActivityForResult(intent); } @Override public void shutdown() { mMediaRecorder.release(); } // TODO(damonkohler): This shares a lot of code with the CameraFacade. It's probably worth moving // it there. private FutureActivityTask<Exception> prepare() throws Exception { FutureActivityTask<Exception> task = new FutureActivityTask<Exception>() { @SuppressWarnings("deprecation") @Override public void onCreate() { super.onCreate(); final SurfaceView view = new SurfaceView(getActivity()); getActivity().setContentView(view); getActivity().getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED); view.getHolder().setType( SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); view.getHolder().addCallback(new Callback() { @Override public void surfaceDestroyed(SurfaceHolder holder) { } @Override public void surfaceCreated(SurfaceHolder holder) { try { mMediaRecorder.setPreviewDisplay(view.getHolder().getSurface()); mMediaRecorder.prepare(); setResult(null); } catch (IOException e) { setResult(e); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } }); } }; FutureActivityTaskExecutor taskExecutor = ((BaseApplication) mService.getApplication()).getTaskExecutor(); taskExecutor.execute(task); Exception e = task.getResult(); if (e != null) { throw e; } return task; } private int convertSecondsToMilliseconds(Integer seconds) { if (seconds == null) { return 0; } return (int) (seconds * 1000L); } }