| /* |
| * Copyright (C) 2016 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.media; |
| |
| 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.facade.AndroidFacade; |
| import com.googlecode.android_scripting.facade.FacadeManager; |
| 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>() { |
| @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().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); |
| } |
| } |