Commit a3471930 authored by ylwu's avatar ylwu

Merge remote-tracking branch 'logan/VideoCapture' into videocapture

parents f57b40fe 0026427e
......@@ -295,6 +295,8 @@ public class YaVersion {
// - The Shape property was added.
public static final int BUTTON_COMPONENT_VERSION = 4;
public static final int CAMCORDER_COMPONENT_VERSION = 1;
public static final int CAMERA_COMPONENT_VERSION = 1;
// For CANVAS_COMPONENT_VERSION 2:
......
package com.google.appinventor.components.runtime;
import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import java.io.File;
import java.util.Date;
/*
* Camcorder provides access to the phone's camcorder
*/
@DesignerComponent(version = YaVersion.CAMCORDER_COMPONENT_VERSION,
description = "A component to record a video using the device's camcorder. " +
"After the video is recorded, the name of the file on the phone " +
"containing the clip is available as an argument to the " +
"AfterRecording event. The file name can be used, for example, to set " +
"the source property of a VideoPlayer component.",
category = ComponentCategory.MEDIA,
nonVisible = true,
//TODO: Give this a right proper icon, mate.
iconName = "images/camcorder.png")
@SimpleObject
public class Camcorder extends AndroidNonvisibleComponent
implements ActivityResultListener, Component {
private static final String CAMCORDER_INTENT = "android.media.action.VIDEO_CAPTURE";
private static final String CAMCORDER_OUTPUT = "output";
private final ComponentContainer container;
private Uri clipFile;
/* Used to identify the call to startActivityForResult. Will be passed back
into the resultReturned() callback method. */
private int requestCode;
/**
* Creates a Camcorder component.
*
* @param container container, component will be placed in
*/
public Camcorder(ComponentContainer container) {
super(container.$form());
this.container = container;
}
/**
* Records a video, then raises the AfterPicture event.
*/
@SimpleFunction
public void RecordVideo() {
Date date = new Date();
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
Log.i("CamcorderComponent", "External storage is available and writable");
clipFile = Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
"/Pictures/app_inventor_" + date.getTime()
+ ".3gp"));
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DATA, clipFile.getPath());
values.put(MediaStore.Video.Media.MIME_TYPE, "clip/3gp");
values.put(MediaStore.Video.Media.TITLE, clipFile.getLastPathSegment());
if (requestCode == 0) {
requestCode = form.registerForActivityResult(this);
}
Uri clipUri = container.$context().getContentResolver().insert(
MediaStore.Video.Media.INTERNAL_CONTENT_URI, values);
Intent intent = new Intent(CAMCORDER_INTENT);
intent.putExtra(CAMCORDER_OUTPUT, clipUri);
container.$context().startActivityForResult(intent, requestCode);
} else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
form.dispatchErrorOccurredEvent(this, "RecordVideo",
ErrorMessages.ERROR_MEDIA_EXTERNAL_STORAGE_READONLY);
} else {
form.dispatchErrorOccurredEvent(this, "RecordVideo",
ErrorMessages.ERROR_MEDIA_EXTERNAL_STORAGE_NOT_AVAILABLE);
}
}
@Override
public void resultReturned(int requestCode, int resultCode, Intent data) {
Log.i("CamcorderComponent",
"Returning result. Request code = " + requestCode + ", result code = " + resultCode);
if (requestCode == this.requestCode && resultCode == Activity.RESULT_OK) {
File clip = new File(clipFile.getPath());
if (clip.length() != 0) {
AfterPicture(clipFile.toString());
} else {
deleteFile(clipFile); // delete empty file
// see if something useful got returned in the data
if (data != null && data.getData() != null) {
Uri tryClipUri = data.getData();
Log.i("CamcorderComponent", "Calling Camcorder.AfterPicture with clip path "
+ tryClipUri.toString());
AfterPicture(tryClipUri.toString());
} else {
Log.i("CamcorderComponent", "Couldn't find a clip file from the Camcorder result");
form.dispatchErrorOccurredEvent(this, "TakeVideo",
ErrorMessages.ERROR_CAMCORDER_NO_CLIP_RETURNED);
}
}
} else {
// delete empty file
deleteFile(clipFile);
}
}
private void deleteFile(Uri fileUri) {
File fileToDelete = new File(fileUri.getPath());
try {
if (fileToDelete.delete()) {
Log.i("CamcorderComponent", "Deleted file " + fileUri.toString());
} else {
Log.i("CamcorderComponent", "Could not delete file " + fileUri.toString());
}
} catch (SecurityException e) {
Log.i("CamcorderComponent", "Got security exception trying to delete file "
+ fileUri.toString());
}
}
/**
* Indicates that a photo was taken with the camera and provides the path to
* the stored picture.
*/
@SimpleEvent
public void AfterPicture(String clip) {
EventDispatcher.dispatchEvent(this, "AfterPicture", clip);
}
}
......@@ -21,6 +21,7 @@ public final class ErrorMessages {
public static final int ERROR_LOCATION_SENSOR_LONGITUDE_NOT_FOUND = 102;
// Camera errors
public static final int ERROR_CAMERA_NO_IMAGE_RETURNED = 201;
// Twitter errors
public static final int ERROR_TWITTER_UNSUPPORTED_LOGIN_FUNCTION = 301;
public static final int ERROR_TWITTER_BLANK_CONSUMER_KEY_OR_SECRET = 302;
......@@ -113,7 +114,10 @@ public final class ErrorMessages {
// Contact picker (and PhoneNumberPicker) errors
public static final int ERROR_PHONE_UNSUPPORTED_CONTACT_PICKER = 1107;
public static final int ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING = 1108;
// Please start the next group of error numbers at 1201.
//Camcorder errors
public static final int ERROR_CAMCORDER_NO_CLIP_RETURNED = 1201;
// Please start the next group of error numbers at 1301.
// Mapping of error numbers to error message format strings.
private static final Map<Integer, String> errorMessages;
......@@ -309,6 +313,9 @@ public final class ErrorMessages {
"The software used in this app cannot extract contacts from this type of phone.");
errorMessages.put(ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING,
"To pick contacts, pick them directly, without using search.");
// Camcorder errors
errorMessages.put(ERROR_CAMCORDER_NO_CLIP_RETURNED,
"The camcorder did not return a clip.");
}
private ErrorMessages() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment