Commit 98fd7ce8 authored by Vance's avatar Vance Committed by Jeffrey I. Schiller

Add Resizable and Full Screen Video Player

Change-Id: Ica112991b56c830fcd42eb436b3921f63ce81723
parent cba736dc
...@@ -77,10 +77,6 @@ public final class MockVideoPlayer extends MockVisibleComponent { ...@@ -77,10 +77,6 @@ public final class MockVideoPlayer extends MockVisibleComponent {
@Override @Override
protected boolean isPropertyVisible(String propertyName) { protected boolean isPropertyVisible(String propertyName) {
if (propertyName.equals(PROPERTY_NAME_WIDTH) ||
propertyName.equals(PROPERTY_NAME_HEIGHT)) {
return false;
}
return super.isPropertyVisible(propertyName); return super.isPropertyVisible(propertyName);
} }
} }
...@@ -753,6 +753,13 @@ public final class YoungAndroidFormUpgrader { ...@@ -753,6 +753,13 @@ public final class YoungAndroidFormUpgrader {
// No properties need to be modified to upgrade to version 3. // No properties need to be modified to upgrade to version 3.
srcCompVersion = 3; srcCompVersion = 3;
} }
if (srcCompVersion < 4) {
// The VideoPlayer.height and VideoPlayer.width getter and setters were marked as
// visible to the user.
// The FullScreen property was created.
// No properties need to be modified to upgrade to version 4.
srcCompVersion = 4;
}
return srcCompVersion; return srcCompVersion;
} }
......
...@@ -258,7 +258,7 @@ public class BlockSaveFile { ...@@ -258,7 +258,7 @@ public class BlockSaveFile {
} }
if (blkLangVersion < 17) { if (blkLangVersion < 17) {
// In BLOCKS_LANGUAGE_VERSION 17. // In BLOCKS_LANGUAGE_VERSION 17.
// Changed open-screen to open-another-screen // Changed open-screen to open-another-screen
// Changed open-screen-with-start-text to open-another-screen-with-start-value // Changed open-screen-with-start-text to open-another-screen-with-start-value
// Marked get-startup-text as a bad block // Marked get-startup-text as a bad block
// Added get-start-value // Added get-start-value
...@@ -268,8 +268,8 @@ public class BlockSaveFile { ...@@ -268,8 +268,8 @@ public class BlockSaveFile {
// Added close-screen-with-plain-text // Added close-screen-with-plain-text
changeGetStartTextAndOpenCloseScreenBlocks(); changeGetStartTextAndOpenCloseScreenBlocks();
blkLangVersion = 17; blkLangVersion = 17;
} }
if (blkLangVersion < sysLangVersion) { if (blkLangVersion < sysLangVersion) {
// If we got here, the blocks language needed to be upgraded, but nothing handled it. // If we got here, the blocks language needed to be upgraded, but nothing handled it.
// NOTE(lizlooney,user) - we need to make sure that this situation does not happen by // NOTE(lizlooney,user) - we need to make sure that this situation does not happen by
...@@ -381,10 +381,10 @@ public class BlockSaveFile { ...@@ -381,10 +381,10 @@ public class BlockSaveFile {
// NOTE(lizlooney,user) - when a component changes, increment the component's version // NOTE(lizlooney,user) - when a component changes, increment the component's version
// number in com.google.appinventor.components.common.YaVersion and add code here to upgrade blocks // number in com.google.appinventor.components.common.YaVersion and add code here to upgrade blocks
// as necessary. // as necessary.
if(genus.equals("AccelerometerSensor")){ if(genus.equals("AccelerometerSensor")){
blkCompVersion = upgradeAccelerometerSensorBlocks(blkCompVersion, componentName); blkCompVersion = upgradeAccelerometerSensorBlocks(blkCompVersion, componentName);
} else if (genus.equals("ActivityStarter")) { } else if (genus.equals("ActivityStarter")) {
blkCompVersion = upgradeActivityStarterBlocks(blkCompVersion, componentName); blkCompVersion = upgradeActivityStarterBlocks(blkCompVersion, componentName);
...@@ -427,9 +427,9 @@ public class BlockSaveFile { ...@@ -427,9 +427,9 @@ public class BlockSaveFile {
} else if (genus.equals("ListPicker")) { } else if (genus.equals("ListPicker")) {
blkCompVersion = upgradeListPickerBlocks(blkCompVersion, componentName); blkCompVersion = upgradeListPickerBlocks(blkCompVersion, componentName);
} else if (genus.equals("LocationSensor")) { } else if (genus.equals("LocationSensor")) {
blkCompVersion = upgradeLocationSensorBlocks(blkCompVersion, componentName); blkCompVersion = upgradeLocationSensorBlocks(blkCompVersion, componentName);
} else if (genus.equals("OrientationSensor")) { } else if (genus.equals("OrientationSensor")) {
blkCompVersion = upgradeOrientationSensorBlocks(blkCompVersion, componentName); blkCompVersion = upgradeOrientationSensorBlocks(blkCompVersion, componentName);
...@@ -500,14 +500,14 @@ public class BlockSaveFile { ...@@ -500,14 +500,14 @@ public class BlockSaveFile {
changeFirstMatchingSocketBlockConnectorLabel(block, oldConnectorLabel, newConnectorLabel); changeFirstMatchingSocketBlockConnectorLabel(block, oldConnectorLabel, newConnectorLabel);
} }
} }
private int upgradeAccelerometerSensorBlocks(int blkCompVersion, String componentName) { private int upgradeAccelerometerSensorBlocks(int blkCompVersion, String componentName) {
if (blkCompVersion < 2) { if (blkCompVersion < 2) {
// The AccelerometerSensor.MinimumInterval property was added. // The AccelerometerSensor.MinimumInterval property was added.
// No blocks need to be modified to upgrade to version 2. // No blocks need to be modified to upgrade to version 2.
blkCompVersion = 2; blkCompVersion = 2;
} }
return blkCompVersion; return blkCompVersion;
} }
private int upgradeActivityStarterBlocks(int blkCompVersion, String componentName) { private int upgradeActivityStarterBlocks(int blkCompVersion, String componentName) {
...@@ -951,6 +951,11 @@ public class BlockSaveFile { ...@@ -951,6 +951,11 @@ public class BlockSaveFile {
} }
blkCompVersion = 3; blkCompVersion = 3;
} }
if (blkCompVersion < 4) {
// The VideoPlayer.height and VideoPlayer.width getter and setters were marked as
// visible to the user
blkCompVersion = 4;
}
return blkCompVersion; return blkCompVersion;
} }
...@@ -1162,7 +1167,7 @@ public class BlockSaveFile { ...@@ -1162,7 +1167,7 @@ public class BlockSaveFile {
changeBlockLabel(block, oldLabel, newLabel); changeBlockLabel(block, oldLabel, newLabel);
} }
} }
private void changeGetStartTextAndOpenCloseScreenBlocks() { private void changeGetStartTextAndOpenCloseScreenBlocks() {
String oldLabel = "open screen"; String oldLabel = "open screen";
String newLabel = "open another screen"; String newLabel = "open another screen";
...@@ -1176,7 +1181,7 @@ public class BlockSaveFile { ...@@ -1176,7 +1181,7 @@ public class BlockSaveFile {
changeBlockLabel(block, oldLabel, newLabel); changeBlockLabel(block, oldLabel, newLabel);
changeBlockGenusName(block, "open-another-screen-with-start-value"); changeBlockGenusName(block, "open-another-screen-with-start-value");
changeFirstMatchingSocketBlockConnectorLabel(block, "startText", "startValue"); changeFirstMatchingSocketBlockConnectorLabel(block, "startText", "startValue");
} }
for (Element block : getAllMatchingGenusBlocks("get-startup-text")) { for (Element block : getAllMatchingGenusBlocks("get-startup-text")) {
markBlockBad(block, "The get startup text block is no longer used. " + markBlockBad(block, "The get startup text block is no longer used. " +
"Instead, please use get start value in multiple screen apps, " + "Instead, please use get start value in multiple screen apps, " +
...@@ -1186,7 +1191,7 @@ public class BlockSaveFile { ...@@ -1186,7 +1191,7 @@ public class BlockSaveFile {
markBlockBad(block, "The close screen with result block is no longer used. " + markBlockBad(block, "The close screen with result block is no longer used. " +
"Instead, please use close screen with value in multiple screen apps, " + "Instead, please use close screen with value in multiple screen apps, " +
"or close screen with plain text for returning to other apps."); "or close screen with plain text for returning to other apps.");
} }
} }
/* /*
......
...@@ -176,11 +176,12 @@ public class YaVersion { ...@@ -176,11 +176,12 @@ public class YaVersion {
// - PLAYER_COMPONENT_VERSION was incremented to 4. // - PLAYER_COMPONENT_VERSION was incremented to 4.
// For YOUNG_ANDROID_VERSION 58: // For YOUNG_ANDROID_VERSION 58:
// - FORM_COMPONENT_VERSION was incremented to 7. // - FORM_COMPONENT_VERSION was incremented to 7.
// For YOUNG_ANDROID_VERSION 59: // For YOUNG_ANDROID_VERION 59:
//The Camcorder component was added. // The Camcorder component was added.
// For YOUNG_ANDROID_VERION 60:
// - VIDEOPLAYER_COMPONENT_VERSION was incremented to 4.
public static final int YOUNG_ANDROID_VERSION = 60;
public static final int YOUNG_ANDROID_VERSION = 59;
// ............................... Blocks Language Version Number ............................... // ............................... Blocks Language Version Number ...............................
...@@ -499,7 +500,10 @@ public class YaVersion { ...@@ -499,7 +500,10 @@ public class YaVersion {
// - The VideoPlayer.VideoPlayerError event was added. // - The VideoPlayer.VideoPlayerError event was added.
// For VIDEOPLAYER_COMPONENT_VERSION 3: // For VIDEOPLAYER_COMPONENT_VERSION 3:
// - The VideoPlayer.VideoPlayerError event was marked userVisible false and is no longer used. // - The VideoPlayer.VideoPlayerError event was marked userVisible false and is no longer used.
public static final int VIDEOPLAYER_COMPONENT_VERSION = 3; // For VIDEOPLAYER_COMPONENT_VERSION 4:
// - The VideoPlayer.width and VideoPlayer.height variables were marked as user visible.
// - The FullScreen property was added to the VideoPlayer.
public static final int VIDEOPLAYER_COMPONENT_VERSION = 4;
public static final int VOTING_COMPONENT_VERSION = 1; public static final int VOTING_COMPONENT_VERSION = 1;
......
...@@ -6,7 +6,6 @@ import com.google.appinventor.components.annotations.DesignerComponent; ...@@ -6,7 +6,6 @@ import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty; import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent; 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.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.annotations.UsesPermissions; import com.google.appinventor.components.annotations.UsesPermissions;
...@@ -18,6 +17,7 @@ import com.google.appinventor.components.runtime.collect.Lists; ...@@ -18,6 +17,7 @@ import com.google.appinventor.components.runtime.collect.Lists;
import com.google.appinventor.components.runtime.collect.Maps; import com.google.appinventor.components.runtime.collect.Maps;
import com.google.appinventor.components.runtime.collect.Sets; import com.google.appinventor.components.runtime.collect.Sets;
import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.FullScreenVideoUtil;
import com.google.appinventor.components.runtime.util.JsonUtil; import com.google.appinventor.components.runtime.util.JsonUtil;
import com.google.appinventor.components.runtime.util.MediaUtil; import com.google.appinventor.components.runtime.util.MediaUtil;
import com.google.appinventor.components.runtime.util.SdkLevel; import com.google.appinventor.components.runtime.util.SdkLevel;
...@@ -25,6 +25,7 @@ import com.google.appinventor.components.runtime.util.ViewUtil; ...@@ -25,6 +25,7 @@ import com.google.appinventor.components.runtime.util.ViewUtil;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
...@@ -131,6 +132,8 @@ public class Form extends Activity ...@@ -131,6 +132,8 @@ public class Form extends Activity
// event. // event.
private String nextFormName; private String nextFormName;
private FullScreenVideoUtil fullScreenVideoUtil;
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
// Called when the activity is first created // Called when the activity is first created
...@@ -155,6 +158,8 @@ public class Form extends Activity ...@@ -155,6 +158,8 @@ public class Form extends Activity
startupValue = startIntent.getStringExtra(ARGUMENT_NAME); startupValue = startIntent.getStringExtra(ARGUMENT_NAME);
} }
fullScreenVideoUtil = new FullScreenVideoUtil(this, androidUIHandler);
// Add application components to the form // Add application components to the form
$define(); $define();
...@@ -350,6 +355,25 @@ public class Form extends Activity ...@@ -350,6 +355,25 @@ public class Form extends Activity
onDestroyListeners.add(component); onDestroyListeners.add(component);
} }
public Dialog onCreateDialog(int id, Bundle args) {
switch(id) {
case FullScreenVideoUtil.FULLSCREEN_VIDEO_DIALOG_FLAG:
return fullScreenVideoUtil.createFullScreenVideoDialog(args);
default:
return super.onCreateDialog(id, args);
}
}
public void onPrepareDialog(int id, Dialog dialog, Bundle args) {
switch(id) {
case FullScreenVideoUtil.FULLSCREEN_VIDEO_DIALOG_FLAG:
fullScreenVideoUtil.prepareFullScreenVideoDialog(dialog, args);
break;
default:
super.onPrepareDialog(id, dialog, args);
}
}
/** /**
* Compiler-generated method to initialize and add application components to * Compiler-generated method to initialize and add application components to
* the form. We just provide an implementation here to artificially make * the form. We just provide an implementation here to artificially make
...@@ -494,6 +518,7 @@ public class Form extends Activity ...@@ -494,6 +518,7 @@ public class Form extends Activity
if (backgroundDrawable != null) { if (backgroundDrawable != null) {
ViewUtil.setBackgroundImage(frameLayout, backgroundDrawable); ViewUtil.setBackgroundImage(frameLayout, backgroundDrawable);
} }
setContentView(frameLayout); setContentView(frameLayout);
frameLayout.requestLayout(); frameLayout.requestLayout();
} }
...@@ -1117,4 +1142,41 @@ public class Form extends Activity ...@@ -1117,4 +1142,41 @@ public class Form extends Activity
throw e.getTargetException(); throw e.getTargetException();
} }
} }
/**
* Perform some action related to fullscreen video display.
* @param action
* Can be any of the following:
* <ul>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_DURATION}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_FULLSCREEN}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_PAUSE}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_PLAY}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_SEEK}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_SOURCE}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_STOP}
* </li>
* </ul>
* @param source
* The VideoPlayer to use in some actions.
* @param data
* Used by the method. This object varies depending on the action.
* @return Varies depending on what action was passed in.
*/
public synchronized Bundle fullScreenVideoAction(int action, VideoPlayer source, Object data) {
return fullScreenVideoUtil.performAction(action, source, data);
}
} }
...@@ -15,12 +15,16 @@ import com.google.appinventor.components.common.ComponentConstants; ...@@ -15,12 +15,16 @@ import com.google.appinventor.components.common.ComponentConstants;
import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.util.ErrorMessages; import com.google.appinventor.components.runtime.util.ErrorMessages;
import com.google.appinventor.components.runtime.util.FullScreenVideoUtil;
import com.google.appinventor.components.runtime.util.MediaUtil; import com.google.appinventor.components.runtime.util.MediaUtil;
import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.MediaController; import android.widget.MediaController;
...@@ -50,45 +54,63 @@ import java.io.IOException; ...@@ -50,45 +54,63 @@ import java.io.IOException;
* things are working solidly. * things are working solidly.
*/ */
/**
* TODO: The resizing of the VideoPlayer at runtime does not work well on some
* devices such as the Motorola Droid. The behavior is almost random when resizing
* the VideoPlayer on such devices. When App Inventor includes the features to
* restrict certain devices, the VideoPlayer should be updated.
*/
/** /**
* Implementation of VideoPlayer, using {@link android.widget.VideoView}. * Implementation of VideoPlayer, using {@link android.widget.VideoView}.
* *
* @author halabelson@google.com (Hal Abelson) * @author halabelson@google.com (Hal Abelson)
*/ */
@DesignerComponent(version = YaVersion.VIDEOPLAYER_COMPONENT_VERSION, @DesignerComponent(
description = "A multimedia component capable of playing videos. " + version = YaVersion.VIDEOPLAYER_COMPONENT_VERSION,
"When the application is run, the VideoPlayer will be displayed as a " + description = "A multimedia component capable of playing videos. "
"rectangle on-screen. If the user touches the rectangle, controls will " + + "When the application is run, the VideoPlayer will be displayed as a "
"appear to play/pause, skip ahead, and skip backward within the video. " + + "rectangle on-screen. If the user touches the rectangle, controls will "
"The application can also control behavior by calling the " + + "appear to play/pause, skip ahead, and skip backward within the video. "
"<code>Start</code>, <code>Pause</code>, and <code>SeekTo</code> methods. " + + "The application can also control behavior by calling the "
"<p>Video files should be in Windows Media Video (.wmv) format, " + + "<code>Start</code>, <code>Pause</code>, and <code>SeekTo</code> methods. "
"3GPP (.3gp), or MPEG-4 (.mp4). For more details about legal " + + "<p>Video files should be in Windows Media Video (.wmv) format, "
"formats, see " + + "3GPP (.3gp), or MPEG-4 (.mp4). For more details about legal "
"<a href=\"http://developer.android.com/guide/appendix/media-formats.html\"" + + "formats, see "
" target=\"_blank\">Android Supported Media Formats</a>.</p>" + + "<a href=\"http://developer.android.com/guide/appendix/media-formats.html\""
"<p>App Inventor for Android only permits video files under 1 MB and " + + " target=\"_blank\">Android Supported Media Formats</a>.</p>"
"limits the total size of an application to 5 MB, not all of which is " + + "<p>App Inventor for Android only permits video files under 1 MB and "
"available for media (video, audio, and sound) files. If your media " + + "limits the total size of an application to 5 MB, not all of which is "
"files are too large, you may get errors when packaging or installing " + + "available for media (video, audio, and sound) files. If your media "
"your application, in which case you should reduce the number of media " + + "files are too large, you may get errors when packaging or installing "
"files or their sizes. Most video editing software, such as Windows " + + "your application, in which case you should reduce the number of media "
"Movie Maker and Apple iMovie, can help you decrease the size of videos " + + "files or their sizes. Most video editing software, such as Windows "
"by shortening them or re-encoding the video into a more compact format.</p>", + "Movie Maker and Apple iMovie, can help you decrease the size of videos "
+ "by shortening them or re-encoding the video into a more compact format.</p>",
category = ComponentCategory.MEDIA) category = ComponentCategory.MEDIA)
@SimpleObject @SimpleObject
@UsesPermissions(permissionNames = "android.permission.INTERNET") @UsesPermissions(permissionNames = "android.permission.INTERNET")
public final class VideoPlayer extends AndroidViewComponent public final class VideoPlayer extends AndroidViewComponent implements
implements OnDestroyListener, Deleteable, OnCompletionListener, OnErrorListener { OnDestroyListener, Deleteable, OnCompletionListener, OnErrorListener,
OnPreparedListener {
/* /*
* Video clip with player controls (touch it to activate) * Video clip with player controls (touch it to activate)
*/ */
private final VideoView videoView; private final ResizableVideoView videoView;
private String sourcePath; // name of media source
private boolean inFullScreen = false;
// The VideoView does not always start playing if Start is called
// shortly after the source is set. These flags are used to fix this
// problem.
private boolean mediaReady = false;
private String sourcePath; // name of media source private boolean delayedStart = false;
/** /**
* Creates a new VideoPlayer component. * Creates a new VideoPlayer component.
...@@ -98,16 +120,19 @@ public final class VideoPlayer extends AndroidViewComponent ...@@ -98,16 +120,19 @@ public final class VideoPlayer extends AndroidViewComponent
public VideoPlayer(ComponentContainer container) { public VideoPlayer(ComponentContainer container) {
super(container); super(container);
container.$form().registerForOnDestroy(this); container.$form().registerForOnDestroy(this);
videoView = new VideoView(container.$context()); videoView = new ResizableVideoView(container.$context());
videoView.setMediaController(new MediaController(container.$context())); videoView.setMediaController(new MediaController(container.$context()));
videoView.setOnCompletionListener(this); videoView.setOnCompletionListener(this);
videoView.setOnErrorListener(this); videoView.setOnErrorListener(this);
videoView.setOnPreparedListener(this);
// add the component to the designated container // add the component to the designated container
container.$add(this); container.$add(this);
// set a default size // set a default size
container.setChildWidth(this, ComponentConstants.VIDEOPLAYER_PREFERRED_WIDTH); container.setChildWidth(this,
container.setChildHeight(this, ComponentConstants.VIDEOPLAYER_PREFERRED_HEIGHT); ComponentConstants.VIDEOPLAYER_PREFERRED_WIDTH);
container.setChildHeight(this,
ComponentConstants.VIDEOPLAYER_PREFERRED_HEIGHT);
// Make volume buttons control media, not ringer. // Make volume buttons control media, not ringer.
container.$form().setVolumeControlStream(AudioManager.STREAM_MUSIC); container.$form().setVolumeControlStream(AudioManager.STREAM_MUSIC);
...@@ -122,61 +147,98 @@ public final class VideoPlayer extends AndroidViewComponent ...@@ -122,61 +147,98 @@ public final class VideoPlayer extends AndroidViewComponent
/** /**
* Sets the video source. * Sets the video source.
*
* <p/>
* See {@link MediaUtil#determineMediaSource} for information about what a
* path can be.
* *
* <p/>See {@link MediaUtil#determineMediaSource} for information about what * @param path
* a path can be. * the path to the video source
*
* @param path the path to the video source
*/ */
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_ASSET, @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_ASSET,
defaultValue = "") defaultValue = "")
@SimpleProperty( @SimpleProperty(
description = "The \"path\" to the video. Usually, this will be the " + description = "The \"path\" to the video. Usually, this will be the "
"name of the video file, which should be added in the Designer.", + "name of the video file, which should be added in the Designer.",
category = PropertyCategory.BEHAVIOR) category = PropertyCategory.BEHAVIOR)
public void Source(String path) { public void Source(String path) {
sourcePath = (path == null) ? "" : path; if (inFullScreen) {
container.$form().fullScreenVideoAction(
// Clear the previous video. FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_SOURCE, this, path);
if (videoView.isPlaying()) { } else {
videoView.stopPlayback(); sourcePath = (path == null) ? "" : path;
}
videoView.setVideoURI(null); // The source may change for the MediaPlayer, and
videoView.clearAnimation(); // getVideoWidth or getVideoHeight may be called
// creating an error in ResizableVideoView.
if (sourcePath.length() > 0) { videoView.invalidateMediaPlayer(true);
Log.i("VideoPlayer", "Source path is " + sourcePath);
// Clear the previous video.
try { if (videoView.isPlaying()) {
MediaUtil.loadVideoView(videoView, container.$form(), sourcePath); videoView.stopPlayback();
} catch (IOException e) { }
container.$form().dispatchErrorOccurredEvent(this, "Source", videoView.setVideoURI(null);
ErrorMessages.ERROR_UNABLE_TO_LOAD_MEDIA, sourcePath); videoView.clearAnimation();
return;
if (sourcePath.length() > 0) {
Log.i("VideoPlayer", "Source path is " + sourcePath);
try {
mediaReady = false;
MediaUtil.loadVideoView(videoView, container.$form(), sourcePath);
} catch (IOException e) {
container.$form().dispatchErrorOccurredEvent(this, "Source",
ErrorMessages.ERROR_UNABLE_TO_LOAD_MEDIA, sourcePath);
return;
}
Log.i("VideoPlayer", "loading video succeeded");
} }
Log.i("VideoPlayer", "loading video succeeded");
} }
} }
/** /**
* Plays the media specified by the source. These won't normally be used in * Plays the media specified by the source. These won't normally be used in
* the most elementary applications, because videoView brings up its own * the most elementary applications, because videoView brings up its own
* player controls when the video is touched. * player controls when the video is touched.
*/ */
@SimpleFunction( @SimpleFunction(description = "Starts playback of the video.")
description = "Starts playback of the video.")
public void Start() { public void Start() {
Log.i("VideoPlayer", "Calling Start"); Log.i("VideoPlayer", "Calling Start");
videoView.start(); if (inFullScreen) {
container.$form().fullScreenVideoAction(
FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_PLAY, this, null);
} else {
if (mediaReady) {
videoView.start();
} else {
delayedStart = true;
}
}
}
/**
* Method for starting the VideoPlayer once the media has been loaded.
* Not visible to users.
*/
public void delayedStart() {
delayedStart = true;
Start();
} }
@SimpleFunction( @SimpleFunction(
description = "Pauses playback of the video. Playback can be resumed " + description = "Pauses playback of the video. Playback can be resumed "
"at the same location by calling the <code>Start</code> method.") + "at the same location by calling the <code>Start</code> method.")
public void Pause() { public void Pause() {
Log.i("VideoPlayer", "Calling Pause"); Log.i("VideoPlayer", "Calling Pause");
videoView.pause(); if (inFullScreen) {
container.$form().fullScreenVideoAction(
FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_PAUSE, this, null);
delayedStart = false;
} else {
delayedStart = false;
videoView.pause();
}
} }
@SimpleFunction( @SimpleFunction(
...@@ -187,15 +249,30 @@ public final class VideoPlayer extends AndroidViewComponent ...@@ -187,15 +249,30 @@ public final class VideoPlayer extends AndroidViewComponent
if (ms < 0) { if (ms < 0) {
ms = 0; ms = 0;
} }
// There is no harm if the milliseconds is longer than the duration. if (inFullScreen) {
videoView.seekTo(ms); container.$form().fullScreenVideoAction(
FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_SEEK, this, ms);
} else {
// There is no harm if the milliseconds is longer than the duration.
videoView.seekTo(ms);
}
} }
@SimpleFunction( @SimpleFunction(
description = "Returns duration of the video in milliseconds.") description = "Returns duration of the video in milliseconds.")
public int GetDuration() { public int GetDuration() {
Log.i("VideoPlayer", "Calling GetDuration"); Log.i("VideoPlayer", "Calling GetDuration");
return videoView.getDuration(); if (inFullScreen) {
Bundle result = container.$form().fullScreenVideoAction(
FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_DURATION, this, null);
if (result.getBoolean(FullScreenVideoUtil.ACTION_SUCESS)) {
return result.getInt(FullScreenVideoUtil.ACTION_DATA);
} else {
return 0;
}
} else {
return videoView.getDuration();
}
} }
// OnCompletionListener implementation // OnCompletionListener implementation
...@@ -217,15 +294,39 @@ public final class VideoPlayer extends AndroidViewComponent ...@@ -217,15 +294,39 @@ public final class VideoPlayer extends AndroidViewComponent
@Override @Override
public boolean onError(MediaPlayer m, int what, int extra) { public boolean onError(MediaPlayer m, int what, int extra) {
Log.e("VideoPlayer", "onError: what is " + what + " 0x" + Integer.toHexString(what) +
", extra is " + extra + " 0x" + Integer.toHexString(extra)); // The ResizableVideoView onMeasure method attempts to use the MediaPlayer
// to measure
// the VideoPlayer; but in the event of an error, the MediaPlayer
// may report dimensions of zero video width and height.
// Since VideoPlayer currently (7/10/2012) sets its size always
// to some non-zero number, the MediaPlayer is invalidated here
// to prevent onMeasure from setting width and height as zero.
videoView.invalidateMediaPlayer(true);
delayedStart = false;
mediaReady = false;
Log.e("VideoPlayer",
"onError: what is " + what + " 0x" + Integer.toHexString(what)
+ ", extra is " + extra + " 0x" + Integer.toHexString(extra));
container.$form().dispatchErrorOccurredEvent(this, "Source", container.$form().dispatchErrorOccurredEvent(this, "Source",
ErrorMessages.ERROR_UNABLE_TO_LOAD_MEDIA, sourcePath); ErrorMessages.ERROR_UNABLE_TO_LOAD_MEDIA, sourcePath);
return true; return true;
} }
@SimpleEvent(description = "The VideoPlayerError event is no longer used. " + @Override
"Please use the Screen.ErrorOccurred event instead.", public void onPrepared(MediaPlayer newMediaPlayer) {
mediaReady = true;
delayedStart = false;
videoView.setMediaPlayer(newMediaPlayer, true);
if (delayedStart) {
Start();
}
}
@SimpleEvent(description = "The VideoPlayerError event is no longer used. "
+ "Please use the Screen.ErrorOccurred event instead.",
userVisible = false) userVisible = false)
public void VideoPlayerError(String message) { public void VideoPlayerError(String message) {
} }
...@@ -250,55 +351,328 @@ public final class VideoPlayer extends AndroidViewComponent ...@@ -250,55 +351,328 @@ public final class VideoPlayer extends AndroidViewComponent
} }
videoView.setVideoURI(null); videoView.setVideoURI(null);
videoView.clearAnimation(); videoView.clearAnimation();
delayedStart = false;
mediaReady = false;
if (inFullScreen) {
Bundle data = new Bundle();
data.putBoolean(FullScreenVideoUtil.VIDEOPLAYER_FULLSCREEN, false);
container.$form().fullScreenVideoAction(
FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_FULLSCREEN, this, data);
}
} }
/*
* Unfortunately, to prevent the user from setting the width and height
* of the component, we have to also prevent them from getting the width
* and height of the component.
*/
/** /**
* Returns the component's horizontal width, measured in pixels. * Returns the component's horizontal width, measured in pixels.
* *
* @return width in pixels * @return width in pixels
*/ */
@Override @Override
@SimpleProperty(userVisible = false) @SimpleProperty
public int Width() { public int Width() {
return super.Width(); return super.Width();
} }
/** /**
* Specifies the component's horizontal width, measured in pixels. * Specifies the component's horizontal width, measured in pixels.
* *
* @param width in pixels * @param width in pixels
*/ */
@Override @Override
@SimpleProperty(userVisible = false) @SimpleProperty(userVisible = true)
public void Width(int width) { public void Width(int width) {
super.Width(width); super.Width(width);
// Forces a layout of the ResizableVideoView
videoView.changeVideoSize(width, videoView.forcedHeight);
} }
/** /**
* Returns the component's vertical height, measured in pixels. * Returns the component's vertical height, measured in pixels.
* *
* @return height in pixels * @return height in pixels
*/ */
@Override @Override
@SimpleProperty(userVisible = false) @SimpleProperty
public int Height() { public int Height() {
return super.Height(); return super.Height();
} }
/** /**
* Specifies the component's vertical height, measured in pixels. * Specifies the component's vertical height, measured in pixels.
* *
* @param height in pixels * @param height
* in pixels
*/ */
@Override @Override
@SimpleProperty(userVisible = false) @SimpleProperty(userVisible = true)
public void Height(int height) { public void Height(int height) {
super.Height(height); super.Height(height);
// Forces a layout of the ResizableVideoView
videoView.changeVideoSize(videoView.forcedWidth, height);
}
/**
* Returns whether the VideoPlayer's video is currently being
* shown in fullscreen mode or not.
* @return True if video is being shown in fullscreen. False otherwise.
*/
@SimpleProperty
public boolean FullScreen() {
return inFullScreen;
}
/**
* Sets whether the video should be shown in fullscreen or not.
*
* @param value If True, the video will be shown in fullscreen.
* If False and {@link VideoPlayer#FullScreen()} returns True, fullscreen
* mode will be exited. If False and {@link VideoPlayer#FullScreen()}
* returns False, nothing occurs.
*/
@SimpleProperty(userVisible = true)
public void FullScreen(boolean value) {
if (value != inFullScreen) {
if (value) {
Bundle data = new Bundle();
data.putInt(FullScreenVideoUtil.VIDEOPLAYER_POSITION,
videoView.getCurrentPosition());
data.putBoolean(FullScreenVideoUtil.VIDEOPLAYER_PLAYING,
videoView.isPlaying());
videoView.pause();
data.putBoolean(FullScreenVideoUtil.VIDEOPLAYER_FULLSCREEN, true);
data.putString(FullScreenVideoUtil.VIDEOPLAYER_SOURCE, sourcePath);
Bundle result = container.$form().fullScreenVideoAction(
FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_FULLSCREEN, this, data);
if (result.getBoolean(FullScreenVideoUtil.ACTION_SUCESS)) {
inFullScreen = true;
} else {
inFullScreen = false;
container.$form().dispatchErrorOccurredEvent(this, "FullScreen",
ErrorMessages.ERROR_VIDEOPLAYER_FULLSCREEN_UNAVAILBLE, "");
}
} else {
Bundle values = new Bundle();
values.putBoolean(FullScreenVideoUtil.VIDEOPLAYER_FULLSCREEN, false);
Bundle result = container.$form().fullScreenVideoAction(
FullScreenVideoUtil.FULLSCREEN_VIDEO_ACTION_FULLSCREEN, this,
values);
if (result.getBoolean(FullScreenVideoUtil.ACTION_SUCESS)) {
fullScreenKilled((Bundle) result);
} else {
inFullScreen = true;
container.$form().dispatchErrorOccurredEvent(this, "FullScreen",
ErrorMessages.ERROR_VIDEOPLAYER_FULLSCREEN_CANT_EXIT, "");
}
}
}
}
/**
* Notify this VideoPlayer that its video is no longer being shown
* in fullscreen.
* @param data See {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil}
* for an example of what data should contain.
*/
public void fullScreenKilled(Bundle data) {
inFullScreen = false;
String newSource = data.getString(FullScreenVideoUtil.VIDEOPLAYER_SOURCE);
if (!newSource.equals(sourcePath)) {
Source(newSource);
}
videoView.setVisibility(View.VISIBLE);
videoView.requestLayout();
SeekTo(data.getInt(FullScreenVideoUtil.VIDEOPLAYER_POSITION));
if (data.getBoolean(FullScreenVideoUtil.VIDEOPLAYER_PLAYING)) {
Start();
}
}
/**
* Get the value passed in {@link VideoPlayer#Width(int)}
* @return The width value.
*/
public int getPassedWidth() {
return videoView.forcedWidth;
}
/**
* Get the value passed in {@link VideoPlayer#Height(int)}
* @return The height value.
*/
public int getPassedHeight() {
return videoView.forcedHeight;
}
/**
* Extends VideoView to allow resizing of the view that ignores the aspect
* ratio of the video being played.
*
* @author Vance Turnewitsch
*/
class ResizableVideoView extends VideoView {
private MediaPlayer mVideoPlayer;
/*
* Used by onMeasure to determine whether the mVideoPlayer should be used to
* measure the view.
*/
private Boolean mFoundMediaPlayer = false;
/**
* Used by onMeasure to determine what type of size the VideoPlayer should
* be.
*/
public int forcedWidth = LENGTH_PREFERRED;
/**
* Used by onMeasure to determine what type of size the VideoPlayer should
* be.
*/
public int forcedHeight = LENGTH_PREFERRED;
public ResizableVideoView(Context context) {
super(context);
}
public void onMeasure(int specwidth, int specheight) {
// Since super.onMeasure uses the aspect ratio of the video being
// played, it is not called.
// http://grepcode.com/file/repository.grepcode.com/java/ext/
// com.google.android/android/2.2.2_r1/android/widget/VideoView.java
// #VideoView.onMeasure%28int%2Cint%29
// Log messages in this method are not commented out for testing the
// changes
// on other devices.
Log.i("VideoPlayer..onMeasure", "AI setting dimensions as:" + forcedWidth
+ ":" + forcedHeight);
Log.i("VideoPlayer..onMeasure",
"Dimenions from super>>" + MeasureSpec.getSize(specwidth) + ":"
+ MeasureSpec.getSize(specheight));
// The VideoPlayer's dimensions must always be some non-zero number.
int width = ComponentConstants.VIDEOPLAYER_PREFERRED_WIDTH;
int height = ComponentConstants.VIDEOPLAYER_PREFERRED_HEIGHT;
switch (forcedWidth) {
case LENGTH_FILL_PARENT:
switch (MeasureSpec.getMode(specwidth)) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
width = MeasureSpec.getSize(specwidth);
break;
case MeasureSpec.UNSPECIFIED:
try {
width = ((View) getParent()).getMeasuredWidth();
} catch (ClassCastException cast) {
width = ComponentConstants.VIDEOPLAYER_PREFERRED_WIDTH;
} catch (NullPointerException nullParent) {
width = ComponentConstants.VIDEOPLAYER_PREFERRED_WIDTH;
}
}
break;
case LENGTH_PREFERRED:
if (mFoundMediaPlayer) {
try {
width = mVideoPlayer.getVideoWidth();
Log.i("VideoPlayer.onMeasure", "Got width from MediaPlayer>"
+ width);
} catch (NullPointerException nullVideoPlayer) {
Log.e(
"VideoPlayer..onMeasure",
"Failed to get MediaPlayer for width:\n"
+ nullVideoPlayer.getMessage());
width = ComponentConstants.VIDEOPLAYER_PREFERRED_WIDTH;
}
} else {
}
break;
default:
width = forcedWidth;
}
switch (forcedHeight) {
case LENGTH_FILL_PARENT:
switch (MeasureSpec.getMode(specheight)) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
height = MeasureSpec.getSize(specheight);
break;
case MeasureSpec.UNSPECIFIED:
// Use height from ComponentConstants
// The current measuring of components ignores FILL_PARENT for height,
// and does not actually fill the height of the parent container.
}
break;
case LENGTH_PREFERRED:
if (mFoundMediaPlayer) {
try {
height = mVideoPlayer.getVideoHeight();
Log.i("VideoPlayer.onMeasure", "Got height from MediaPlayer>"
+ height);
} catch (NullPointerException nullVideoPlayer) {
Log.e(
"VideoPlayer..onMeasure",
"Failed to get MediaPlayer for height:\n"
+ nullVideoPlayer.getMessage());
height = ComponentConstants.VIDEOPLAYER_PREFERRED_HEIGHT;
}
}
break;
default:
height = forcedHeight;
}
// Forces the video playing in the VideoView to scale.
// Some Android devices though will not scale the video playing.
Log.i("VideoPlayer.onMeasure", "Setting dimensions to:" + width + "x"
+ height);
getHolder().setFixedSize(width, height);
setMeasuredDimension(width, height);
}
/**
* Resize the view size and request a layout.
*/
public void changeVideoSize(int newWidth, int newHeight) {
forcedWidth = newWidth;
forcedHeight = newHeight;
forceLayout();
invalidate();
}
/*
* Used to keep onMeasure from using the mVideoPlayer in measuring.
*/
public void invalidateMediaPlayer(boolean triggerRedraw) {
mFoundMediaPlayer = false;
mVideoPlayer = null;
if (triggerRedraw) {
forceLayout();
invalidate();
}
}
public void
setMediaPlayer(MediaPlayer newMediaPlayer, boolean triggerRedraw) {
mVideoPlayer = newMediaPlayer;
mFoundMediaPlayer = true;
if (triggerRedraw) {
forceLayout();
invalidate();
}
}
} }
} }
// Copyright MIT
package com.google.appinventor.components.runtime.util;
import android.content.Context;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.MediaController;
/**
* A modified MediaController that allows adding to a
* {@link android.view.ViewGroup} instead of the Window that Android adds the
* MediaController to.
*
* This class manages displaying the controller GUI.
*
* @author Vance Turnewitsch
*
*/
public class CustomMediaController extends MediaController implements
View.OnTouchListener {
// The view whose touch events are listened to.
private View mAnchorView;
/*
* How long the GUI should be shown when the anchorView passed in
*/
private int mShowTime = 3000;
public CustomMediaController(Context context) {
super(context);
}
/**
* Sets the visibility of the GUI to {@link android.view.View#VISIBLE}
* and calls {@link android.widget.MediaController#show(int)}.
* @param timeout
* How long the GUI should be shown for.
*/
@Override
public void show(int timeout) {
setVisibility(VISIBLE);
super.show(timeout);
}
/**
* Sets the visibility of the GUI to {@link android.view.View#VISIBLE}
* and calls {@link android.widget.MediaController#show()}.
*/
@Override
public void show() {
setVisibility(VISIBLE);
super.show();
}
/**
* Attempts to remove this CustomMediaController from the Window that Android
* automatically adds it to and add it to another
* {@link android.view.ViewGroup}.
*
* @param parent
* The {@link android.view.ViewGroup} to add the CustomMediaController
* to.
* @param params
* The {@link android.view.ViewGroup.LayoutParams} to use when adding
* the CustomMediaController to the parent.
*/
public boolean addTo(ViewGroup parent, ViewGroup.LayoutParams params) {
Object mParent = getParent();
if (mParent != null && mParent instanceof ViewGroup) {
((ViewGroup) mParent).removeView(this);
parent.addView(this, params);
return true;
} else {
Log.e("CustomMediaController.addTo",
"MediaController not available in fullscreen.");
return false;
}
}
/**
* Calls {@link android.widget.MediaController#setAnchorView(View)} and sets
* up a listener that shows a GUI when the anchorView is touched.
*/
@Override
public void setAnchorView(View anchorView) {
mAnchorView = anchorView;
mAnchorView.setOnTouchListener(this);
super.setAnchorView(anchorView);
}
/**
* Calls {@link android.widget.MediaController#hide()} and sets the visibility
* of this object to {@link android.view.View#INVISIBLE}.
*/
@Override
public void hide() {
super.hide();
setVisibility(INVISIBLE);
}
/**
* Called when the anchorView passed in
* {@link CustomMediaController#setAnchorView(View)} is touched. Shows the
* controller GUI.
*/
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v == mAnchorView) {
show(mShowTime);
}
return false;
}
}
...@@ -115,8 +115,10 @@ public final class ErrorMessages { ...@@ -115,8 +115,10 @@ public final class ErrorMessages {
public static final int ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING = 1108; public static final int ERROR_PHONE_UNSUPPORTED_SEARCH_IN_CONTACT_PICKING = 1108;
//Camcorder errors //Camcorder errors
public static final int ERROR_CAMCORDER_NO_CLIP_RETURNED = 1201; public static final int ERROR_CAMCORDER_NO_CLIP_RETURNED = 1201;
// VideoPlayer errors
// Please start the next group of error numbers at 1301. public static final int ERROR_VIDEOPLAYER_FULLSCREEN_UNAVAILBLE = 1301;
public static final int ERROR_VIDEOPLAYER_FULLSCREEN_CANT_EXIT = 1302;
//Please start the next group of error numbers at 1401.
// Mapping of error numbers to error message format strings. // Mapping of error numbers to error message format strings.
private static final Map<Integer, String> errorMessages; private static final Map<Integer, String> errorMessages;
...@@ -315,6 +317,11 @@ public final class ErrorMessages { ...@@ -315,6 +317,11 @@ public final class ErrorMessages {
// Camcorder errors // Camcorder errors
errorMessages.put(ERROR_CAMCORDER_NO_CLIP_RETURNED, errorMessages.put(ERROR_CAMCORDER_NO_CLIP_RETURNED,
"The camcorder did not return a clip."); "The camcorder did not return a clip.");
// VideoPlayer errors
errorMessages.put(ERROR_VIDEOPLAYER_FULLSCREEN_UNAVAILBLE,
"Cannot start fullscreen mode.");
errorMessages.put(ERROR_VIDEOPLAYER_FULLSCREEN_CANT_EXIT,
"Cannot exit fullscreen mode.");
} }
private ErrorMessages() { private ErrorMessages() {
......
// Copyright MIT
package com.google.appinventor.components.runtime.util;
import com.google.appinventor.components.runtime.Form;
import com.google.appinventor.components.runtime.VideoPlayer;
import android.R;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnShowListener;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import android.widget.VideoView;
import java.io.IOException;
/**
* Used by the {@link com.google.appinventor.components.runtime.Form} class to
* display videos in fullscreen.
*
* @author Vance Turnewitsch
*/
public class FullScreenVideoUtil implements OnCompletionListener,
OnPreparedListener, OnShowListener {
// Constants
public static final int FULLSCREEN_VIDEO_DIALOG_FLAG = 189;
public static final int FULLSCREEN_VIDEO_ACTION_SEEK = 190;
public static final int FULLSCREEN_VIDEO_ACTION_PLAY = 191;
public static final int FULLSCREEN_VIDEO_ACTION_PAUSE = 192;
public static final int FULLSCREEN_VIDEO_ACTION_STOP = 193;
public static final int FULLSCREEN_VIDEO_ACTION_SOURCE = 194;
public static final int FULLSCREEN_VIDEO_ACTION_FULLSCREEN = 195;
public static final int FULLSCREEN_VIDEO_ACTION_DURATION = 196;
public static final String VIDEOPLAYER_FULLSCREEN = "FullScreenKey";
public static final String VIDEOPLAYER_PLAYING = "PlayingKey";
public static final String VIDEOPLAYER_POSITION = "PositionKey";
public static final String VIDEOPLAYER_SOURCE = "SourceKey";
public static final String ACTION_SUCESS = "ActionSuccess";
public static final String ACTION_DATA = "ActionData";
// The Dialog and other components used in the Dialog for displaying the
// video.
private Dialog mFullScreenVideoDialog;
private FrameLayout mFullScreenVideoHolder;
private VideoView mFullScreenVideoView;
private CustomMediaController mFullScreenVideoController;
private FrameLayout.LayoutParams mMediaControllerParams = new FrameLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, Gravity.BOTTOM);
private Form mForm;
// The player whose video is currently being shown.
private VideoPlayer mFullScreenPlayer = null;
// The data passed in by the player that requested
// fullscreen display of its video.
private Bundle mFullScreenVideoBundle;
// Used for showing a preview of a paused video.
private Handler mHandler;
/**
* @param form
* The {@link com.google.appinventor.components.runtime.Form} that
* this FullScreenVideoUtil will use.
* @param handler
* A {@link android.os.Handler} created on the UI thread. Used for
* displaying a preview of a paused video.
*/
public FullScreenVideoUtil(Form form, Handler handler) {
mForm = form;
mHandler = handler;
mFullScreenVideoDialog = new Dialog(mForm,
R.style.Theme_NoTitleBar_Fullscreen) {
public void onBackPressed() {
// Allows the user to force exiting full-screen.
Bundle values = new Bundle();
values.putInt(VIDEOPLAYER_POSITION,
mFullScreenVideoView.getCurrentPosition());
values.putBoolean(VIDEOPLAYER_PLAYING,
mFullScreenVideoView.isPlaying());
values.putString(VIDEOPLAYER_SOURCE,
mFullScreenVideoBundle
.getString(VIDEOPLAYER_SOURCE));
mFullScreenPlayer.fullScreenKilled(values);
super.onBackPressed();
}
};
}
/**
* Perform some action and get a result. The data to pass in and the result
* returned are controlled by what the action is.
*
* @param action
* Can be any of the following:
* <ul>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_DURATION}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_FULLSCREEN}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_PAUSE}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_PLAY}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_SEEK}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_SOURCE}
* </li>
* <li>
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#FULLSCREEN_VIDEO_ACTION_STOP}
* </li>
* </ul>
* @param source
* The VideoPlayer to use in some actions.
* @param data
* Used by the method. This object varies depending on the action.
* @return Varies depending on what action was passed in.
*/
public synchronized Bundle performAction(int action, VideoPlayer source,
Object data) {
Log.i("Form.fullScreenVideoAction", "Actions:" + action + " Source:"
+ source + ": Current Source:" + mFullScreenPlayer + " Data:" + data);
Bundle result = new Bundle();
result.putBoolean(ACTION_SUCESS, true);
if (source == mFullScreenPlayer) {
switch (action) {
case FULLSCREEN_VIDEO_ACTION_FULLSCREEN:
return doFullScreenVideoAction(source, (Bundle) data);
case FULLSCREEN_VIDEO_ACTION_PAUSE:
if (showing()) {
mFullScreenVideoView.pause();
return result;
}
result.putBoolean(ACTION_SUCESS, false);
return result;
case FULLSCREEN_VIDEO_ACTION_PLAY:
if (showing()) {
mFullScreenVideoView.start();
return result;
}
result.putBoolean(ACTION_SUCESS, false);
return result;
case FULLSCREEN_VIDEO_ACTION_SEEK:
if (showing()) {
mFullScreenVideoView.seekTo((Integer) data);
return result;
}
result.putBoolean(ACTION_SUCESS, false);
return result;
case FULLSCREEN_VIDEO_ACTION_STOP:
if (showing()) {
mFullScreenVideoView.stopPlayback();
return result;
}
result.putBoolean(ACTION_SUCESS, false);
return result;
case FULLSCREEN_VIDEO_ACTION_SOURCE:
if (showing()) {
result.putBoolean(ACTION_SUCESS,setSource((String) data, true));
return result;
}
result.putBoolean(ACTION_SUCESS, false);
return result;
case FULLSCREEN_VIDEO_ACTION_DURATION:
if (showing()) {
result.putInt(ACTION_DATA, mFullScreenVideoView.getDuration());
return result;
}
result.putBoolean(ACTION_SUCESS, false);
return result;
}
} else if (action == FULLSCREEN_VIDEO_ACTION_FULLSCREEN) {
// There may be a dialog already being shown.
if (showing() && mFullScreenPlayer != null) {
Bundle values = new Bundle();
values.putInt(VIDEOPLAYER_POSITION,
mFullScreenVideoView.getCurrentPosition());
values.putBoolean(VIDEOPLAYER_PLAYING,
mFullScreenVideoView.isPlaying());
values.putString(VIDEOPLAYER_SOURCE,
mFullScreenVideoBundle
.getString(VIDEOPLAYER_SOURCE));
mFullScreenPlayer.fullScreenKilled(values);
}
return doFullScreenVideoAction(source, (Bundle) data);
}
// This should never be called.
result.putBoolean(ACTION_SUCESS, false);
return result;
}
/*
* Displays or hides a full-screen video.
*/
private Bundle doFullScreenVideoAction(VideoPlayer source, Bundle data) {
Log.i("Form.doFullScreenVideoAction", "Source:" + source + " Data:" + data);
Bundle result = new Bundle();
result.putBoolean(ACTION_SUCESS, true);
if (data.getBoolean(VIDEOPLAYER_FULLSCREEN) == true) {
mFullScreenPlayer = source;
mFullScreenVideoBundle = data;
if (!mFullScreenVideoDialog.isShowing()) {
mForm.showDialog(FULLSCREEN_VIDEO_DIALOG_FLAG,
(Bundle) data);
return result;
} else {
mFullScreenVideoView.pause();
result.putBoolean(ACTION_SUCESS, setSource(
mFullScreenVideoBundle.getString(VIDEOPLAYER_SOURCE),false));
return result;
}
} else {
if (showing()) {
result.putBoolean(VIDEOPLAYER_PLAYING,
mFullScreenVideoView.isPlaying());
result.putInt(VIDEOPLAYER_POSITION,
mFullScreenVideoView.getCurrentPosition());
result.putString(VIDEOPLAYER_SOURCE,
mFullScreenVideoBundle
.getString(VIDEOPLAYER_SOURCE));
mFullScreenPlayer = null;
mFullScreenVideoBundle = null;
mForm.dismissDialog(FULLSCREEN_VIDEO_DIALOG_FLAG);
return result;
}
}
result.putBoolean(ACTION_SUCESS, false);
return result;
}
/**
* Creates the dialog for displaying a fullscreen VideoView.
*
* @param data
* The data to be used by this class in displaying the fullscreen
* video.
* @return The created Dialog
*/
public Dialog createFullScreenVideoDialog(final Bundle data) {
mFullScreenVideoBundle = data;
mFullScreenVideoView = new VideoView(mForm);
mFullScreenVideoHolder = new FrameLayout(mForm);
mFullScreenVideoController = new CustomMediaController(mForm);
mFullScreenVideoView.setId(mFullScreenVideoView.hashCode());
mFullScreenVideoHolder.setId(mFullScreenVideoHolder.hashCode());
mFullScreenVideoView.setMediaController(mFullScreenVideoController);
mFullScreenVideoView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
Log.i("FullScreenVideoUtil..onTouch", "Video Touched!!");
return false;
}
});
mFullScreenVideoController.setAnchorView(mFullScreenVideoView);
String orientation = mForm.ScreenOrientation();
if (orientation.equals("landscape")
|| orientation.equals("sensorLandscape")
|| orientation.equals("reverseLandscape")) {
mFullScreenVideoView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.FILL_PARENT, Gravity.CENTER));
} else {
mFullScreenVideoView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.FILL_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
}
mFullScreenVideoHolder
.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT));
mFullScreenVideoHolder.addView(mFullScreenVideoView);
// Add the MediaController to the Dialog
mFullScreenVideoController.addTo(mFullScreenVideoHolder,
mMediaControllerParams);
mFullScreenVideoDialog.setContentView(mFullScreenVideoHolder);
return mFullScreenVideoDialog;
}
/**
* Call just before displaying a fullscreen video Dialog. This method
* sets up some listeners.
*
* @param dia
* The dialog that will display the video.
* @param data
*/
public void prepareFullScreenVideoDialog(Dialog dia, Bundle data) {
dia.setOnShowListener(this);
mFullScreenVideoView.setOnPreparedListener(this);
mFullScreenVideoView.setOnCompletionListener(this);
}
/**
* @return True if the internal Dialog has been created. False otherwise.
*/
public boolean dialogInitialized() {
return mFullScreenVideoDialog != null;
}
/**
* @return True if {@link FullScreenVideoUtil#dialogInitialized()} is true and
* the Dialog is showing. False otherwise.
*/
public boolean showing() {
return dialogInitialized() && mFullScreenVideoDialog.isShowing();
}
/**
* Sets the source to be used by the fullscreen video Dialog. This method
* also attempts to load the internal VideoView with the source.
*
* @param source
* The source path to use. The {@link MediaUtil} is used to load the
* source.
* @param clearSeek
* If True, the video will start playing at position zero. If False,
* the video will start playing from the
* {@link com.google.appinventor.components.runtime.util.FullScreenVideoUtil#VIDEOPLAYER_POSITION}
* value of the Bundle passed in the
* {@link FullScreenVideoUtil#performAction(int, VideoPlayer, Object)}
* or {@link FullScreenVideoUtil#createFullScreenVideoDialog(Bundle)}
* @return True if the video was successfully loaded. False otherwise.
*/
public boolean setSource(String source, boolean clearSeek) {
try {
if (clearSeek) {
mFullScreenVideoBundle.putInt(VIDEOPLAYER_POSITION,
0);
}
MediaUtil.loadVideoView(mFullScreenVideoView, mForm, (String) source);
mFullScreenVideoBundle.putString(VIDEOPLAYER_SOURCE,
source);
return true;
} catch (IOException e) {
mForm.dispatchErrorOccurredEvent(mFullScreenPlayer, "Source",
ErrorMessages.ERROR_UNABLE_TO_LOAD_MEDIA, source);
return false;
}
}
/**
* Called when the video has finished playing.
*/
@Override
public void onCompletion(MediaPlayer arg0) {
if (mFullScreenPlayer != null) {
mFullScreenPlayer.Completed();
}
}
/**
* Called when the video has been loaded.
*/
@Override
public void onPrepared(MediaPlayer arg0) {
Log.i(
"FullScreenVideoUtil..onPrepared",
"Seeking to:"
+ mFullScreenVideoBundle
.getInt(VIDEOPLAYER_POSITION));
mFullScreenVideoView.seekTo(mFullScreenVideoBundle
.getInt(VIDEOPLAYER_POSITION));
if (mFullScreenVideoBundle
.getBoolean(VIDEOPLAYER_PLAYING)) {
mFullScreenVideoView.start();
} else {
mFullScreenVideoView.start();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mFullScreenVideoView.pause();
}
}, 100);
}
}
/**
* Called when the Dialog is about to be shown.
*/
@Override
public void onShow(DialogInterface arg0) {
try {
MediaUtil.loadVideoView(mFullScreenVideoView, mForm,
mFullScreenVideoBundle
.getString(VIDEOPLAYER_SOURCE));
} catch (IOException e) {
mForm.dispatchErrorOccurredEvent(mFullScreenPlayer, "Source",
ErrorMessages.ERROR_UNABLE_TO_LOAD_MEDIA, mFullScreenVideoBundle
.getString(VIDEOPLAYER_SOURCE));
return;
}
}
}
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