Commit 204a57be authored by Jeffrey Schiller's avatar Jeffrey Schiller

Merge pull request #50 from ylwu/videocapture

Added Camcorder component
parents 420bcabe 9c0003f7
......@@ -79,6 +79,12 @@ public interface Images extends Resources {
*/
@Source("com/google/appinventor/images/camera.png")
ImageResource camera();
/**
* Designer palette item: camcorder declaration
*/
@Source("com/google/appinventor/images/camcorder.png")
ImageResource camcorder();
/**
* Designer palette item: canvas component
......
......@@ -82,6 +82,7 @@ public final class SimpleComponentDescriptor {
bundledImages.put("images/barcodeScanner.png", images.barcodeScanner());
bundledImages.put("images/bluetooth.png", images.bluetooth());
bundledImages.put("images/camera.png", images.camera());
bundledImages.put("images/camcorder.png", images.camcorder());
bundledImages.put("images/clock.png", images.clock());
bundledImages.put("images/fusiontables.png", images.fusiontables());
bundledImages.put("images/gameClient.png", images.gameclient());
......
......@@ -176,8 +176,11 @@ public class YaVersion {
// - PLAYER_COMPONENT_VERSION was incremented to 4.
// For YOUNG_ANDROID_VERSION 58:
// - FORM_COMPONENT_VERSION was incremented to 7.
// For YOUNG_ANDROID_VERSION 59:
//The Camcorder component was added.
public static final int YOUNG_ANDROID_VERSION = 58;
public static final int YOUNG_ANDROID_VERSION = 59;
// ............................... Blocks Language Version Number ...............................
......@@ -297,6 +300,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:
......
// Copyright 2010 Google Inc. All Rights Reserved.
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,
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 AfterRecoding 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(),
"/Video/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) {
AfterRecording(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());
AfterRecording(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 video was recorded with the camera and provides the path to
* the stored picture.
*/
@SimpleEvent
public void AfterRecording(String clip) {
EventDispatcher.dispatchEvent(this, "AfterRecording", clip);
}
}
......@@ -113,8 +113,11 @@ 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;
static {
......@@ -309,6 +312,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() {
......
......@@ -38,6 +38,11 @@
Media Components
</h1>
<ul>
<li>
<a href="#Camcorder">
Camcorder
</a>
</li>
<li>
<a href="#Camera">
Camera
......@@ -64,6 +69,44 @@
</a>
</li>
</ul>
<h2 id="Camcorder">
Camcorder
</h2>
<img alt="" src="images/camcorder.png" />
<p>
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.
</p>
<h3>
Properties
</h3>
none
<h3>
Events
</h3>
<dl>
<dt>
<code>
AfterRecording(text clip)
</code>
</dt>
<dd>
Indicates that a video was recorded with the camera and provides the path to
the stored picture.
</dd>
</dl>
<h3>
Methods
</h3>
<dl>
<dt>
<code>
RecordVideo()
</code>
</dt>
<dd>
Records a video, then raises the AfterRecoding event.
</dd>
</dl>
<h2 id="Camera">
Camera
</h2>
......@@ -624,4 +667,5 @@
</div>
</div>
</body>
</html>
\ No newline at end of file
</html>
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