/* * VideoRecordingScreen.java * * Copyright � 1998-2011 Research In Motion Limited * * 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. * * Note: For the sake of simplicity, this sample application may not leverage * resource bundles and resource strings. However, it is STRONGLY recommended * that application developers make use of the localization features available * within the BlackBerry development platform to ensure a seamless application * experience across a variety of languages and geographies. For more information * on localizing your application, please refer to the BlackBerry Java Development * Environment Development Guide associated with this release. */ package com.rim.samples.device.videorecordingdemo; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import javax.microedition.amms.control.camera.FlashControl; import javax.microedition.amms.control.camera.ZoomControl; import javax.microedition.amms.control.imageeffect.ImageEffectControl; import javax.microedition.media.MediaException; import javax.microedition.media.Player; import javax.microedition.media.control.GUIControl; import javax.microedition.media.control.RecordControl; import javax.microedition.media.control.VideoControl; import net.rim.device.api.command.Command; import net.rim.device.api.command.CommandHandler; import net.rim.device.api.command.ReadOnlyCommandMetadata; import net.rim.device.api.system.Characters; import net.rim.device.api.system.Display; import net.rim.device.api.ui.Field; import net.rim.device.api.ui.MenuItem; import net.rim.device.api.ui.UiApplication; import net.rim.device.api.ui.component.Dialog; import net.rim.device.api.ui.component.Menu; import net.rim.device.api.ui.component.RadioButtonField; import net.rim.device.api.ui.component.RadioButtonGroup; import net.rim.device.api.ui.component.RichTextField; import net.rim.device.api.ui.component.SeparatorField; import net.rim.device.api.ui.container.MainScreen; import net.rim.device.api.ui.container.PopupScreen; import net.rim.device.api.ui.container.VerticalFieldManager; import net.rim.device.api.util.StringProvider; /** * This screen allows the user to record videos to a file or to a stream and * enables the user to open the VideoPlaybackScreen to play the recorded video. */ public class VideoRecordingScreen extends MainScreen { private boolean _pendingCommit; private boolean _committed; private boolean _recording; private String _videoFile; private Player _player; private VideoControl _videoControl; private RecordControl _recordControl; private FlashControl _flashControl; private ZoomControl _zoomControl; private ImageEffectControl _effectControl; private boolean _displayVisible; private boolean _recordToStream; private ByteArrayOutputStream _outStream; /** * Constructs a screen to display and record the video being captured from * the device's camera. * * @param encoding * The non-null video encoding to be used when recording video to * a file * @param filePath * The file system to record the video file to, <code>null</code> * if no file system was chosen * * @throws NullPointerException * Thrown if <code>encoding</code> or <code>filePath</code> is * null. */ public VideoRecordingScreen(final String encoding, final String filePath) { if (encoding == null) { throw new NullPointerException("Video encoding can not be null"); } if (filePath == null) { throw new NullPointerException("File path can not be null"); } _commit = new MenuItem(new StringProvider("Commit recording"), 0x230010, 0); _commit.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { commitRecording(); } })); _playRecording = new MenuItem(new StringProvider("Play recording"), 0x230020, 0); _playRecording.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { // Create the playback screen from the chosen video source VideoPlaybackScreen playbackScreen; if (_recordToStream) { playbackScreen = new VideoPlaybackScreen(new ByteArrayInputStream( _outStream.toByteArray())); } else { playbackScreen = new VideoPlaybackScreen(_videoFile); } // Hide the video feed since we cannot display video from the // camera // and video from a file at the same time. _videoControl.setVisible(false); _displayVisible = false; UiApplication.getUiApplication().pushScreen(playbackScreen); } })); _reset = new MenuItem(new StringProvider("Reset recording"), 0x230030, 0); _reset.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { try { _recordControl.reset(); } catch (final Exception e) { VideoRecordingDemo.errorDialog("RecordControl#reset threw " + e.toString()); } } })); _showDisplay = new MenuItem(new StringProvider("Show display"), 0x230040, 0); _showDisplay.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { _videoControl.setVisible(true); _displayVisible = true; } })); _hideDisplay = new MenuItem(new StringProvider("Hide display"), 0x230050, 0); _hideDisplay.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { _videoControl.setVisible(false); _displayVisible = false; } })); _startRecord = new MenuItem(new StringProvider("Start recording"), 0x230060, 0); _startRecord.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { startRecord(); } })); _stopRecord = new MenuItem(new StringProvider("Stop recording"), 0x230070, 0); _stopRecord.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { stopRecord(); } })); _toggleFlash = new MenuItem(new StringProvider("Toggle flash"), 0x230080, 0); _toggleFlash.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { int newMode; switch (_flashControl.getMode()) { case FlashControl.OFF: newMode = FlashControl.FORCE; break; default: newMode = FlashControl.OFF; } try { _flashControl.setMode(newMode); } catch (final Exception e) { } } })); _chooseImageEffect = new MenuItem(new StringProvider("Choose effect"), 0x230090, 0); _chooseImageEffect.setCommand(new Command(new CommandHandler() { /** * @see net.rim.device.api.command.CommandHandler#execute(ReadOnlyCommandMetadata, * Object) */ public void execute(final ReadOnlyCommandMetadata metadata, final Object context) { String currentEffect = null; if (_effectControl.isEnabled()) { final String preset = _effectControl.getPreset(); if (preset != null) { currentEffect = preset; } } final ImageEffectDialog chooseDialog = new ImageEffectDialog(currentEffect); UiApplication.getUiApplication().pushModalScreen(chooseDialog); final String preset = chooseDialog.getImageEffectPreset(); if (preset == null) { // No preset chosen, turn off effects _effectControl.setEnabled(false); } else { // Turn on the chosen effect _effectControl.setPreset(preset); _effectControl.setEnabled(true); } } })); try { // Start capturing video from the camera _player = javax.microedition.media.Manager .createPlayer("capture://video?" + encoding); _player.start(); _videoControl = (VideoControl) _player.getControl("VideoControl"); _recordControl = (RecordControl) _player.getControl("RecordControl"); _flashControl = (FlashControl) _player .getControl("javax.microedition.amms.control.camera.FlashControl"); _zoomControl = (ZoomControl) _player .getControl("javax.microedition.amms.control.camera.ZoomControl"); _effectControl = (ImageEffectControl) _player .getControl("javax.microedition.amms.control.imageeffect.ImageEffectControl"); // Initialize the video display final Field videoField = (Field) _videoControl.initDisplayMode( GUIControl.USE_GUI_PRIMITIVE, "net.rim.device.api.ui.Field"); try { _videoControl.setDisplaySize(Display.getWidth(), Display .getHeight()); } catch (final MediaException me) { // setDisplaySize is not supported } add(videoField); final int choice = Dialog.ask(Dialog.D_YES_NO, "Record to stream?", 1); if (choice == Dialog.YES) { _recordToStream = true; _outStream = new ByteArrayOutputStream(); } else { _videoFile = filePath; } } catch (final Exception e) { // Dispose of the player if it was created if (_player != null) { _player.close(); } _player = null; deleteAll(); removeAllMenuItems(); VideoRecordingDemo.errorDialog(e.toString()); } } /** * A MenuItem to commit the current recording */ private final MenuItem _commit; /** * A MenuItem to play the recording */ private final MenuItem _playRecording; /** * A MenuItem to reset the recording */ private final MenuItem _reset; /** * A MenuItem to show the video display */ private final MenuItem _showDisplay; /** * A MenuItem to hide the video display */ private final MenuItem _hideDisplay; /** * A MenuItem to start the recording of video */ private final MenuItem _startRecord; /** * A MenuItem to stop the recording of video */ private final MenuItem _stopRecord; /** * A MenuItem to turn flash on or off */ private final MenuItem _toggleFlash; /** * A MenuItem to allow users to choose a filter effect */ private final MenuItem _chooseImageEffect; /** * @see net.rim.device.api.ui.Screen#onClose() */ public boolean onClose() { // Stop capturing video from the camera if (_player != null) { _player.close(); } return super.onClose(); } /** * @see net.rim.device.api.ui.container.MainScreen#onSavePrompt() */ protected boolean onSavePrompt() { // Suppress the save prompt return true; } /** * Creates the menu based on the current recording state * * @see net.rim.device.api.ui.Screen#makeMenu(Menu, int) */ protected void makeMenu(final Menu menu, final int instance) { super.makeMenu(menu, instance); if (_recording) { menu.add(_stopRecord); } else { menu.add(_startRecord); menu.add(_toggleFlash); menu.add(_chooseImageEffect); } // If currently recording video, allow the user to commit // and reset the current recording. if (_pendingCommit) { menu.add(_commit); menu.add(_reset); } if (_committed) { // Commit is complete, allow playback of the recording menu.add(_playRecording); } // Add menu item for hiding or showing display depending on // current display status. if (_displayVisible) { menu.add(_hideDisplay); } else { menu.add(_showDisplay); } } /** * @see net.rim.device.api.ui.Screen#invokeAction(int) */ protected boolean invokeAction(final int action) { if (action == ACTION_INVOKE) { if (_recording) { final int response = Dialog.ask(Dialog.D_YES_NO, "Recording paused. Commit recording?", Dialog.YES); if (response == Dialog.YES) { this.commitRecording(); } } return true; } return super.invokeAction(action); } /** * @see net.rim.device.api.ui.Screen#navigationMovement(int, int, int, int) */ protected boolean navigationMovement(final int dx, final int dy, final int status, final int time) { if (dy < 0) { // Upwards move _zoomControl.setDigitalZoom(ZoomControl.NEXT); return true; } else if (dy > 0) { // Downwards move _zoomControl.setDigitalZoom(ZoomControl.PREVIOUS); return true; } else { return false; } } /** * @see net.rim.device.api.ui.Field#onVisibilityChange(boolean) */ protected void onVisibilityChange(final boolean visible) { // If this screen is visible and the video player exists, // display the captured video. if (visible && _player != null) { _videoControl.setVisible(true); _displayVisible = true; } } /** * Starts the recording of video from the camera */ private void startRecord() { try { // If the recording is not pending a commit, then we need to set // the location to commit to if (!_pendingCommit) { if (_recordToStream) { _outStream.reset(); _recordControl.setRecordStream(_outStream); } else { _recordControl.setRecordLocation(_videoFile); } _pendingCommit = true; _committed = false; } _recordControl.startRecord(); _recording = true; } catch (final Exception e) { VideoRecordingDemo.errorDialog(e.toString()); } } /** * Stops the recording of video from the camera */ private void stopRecord() { try { _recordControl.stopRecord(); _recording = false; } catch (final Exception e) { VideoRecordingDemo.errorDialog("RecordControl#stopRecord() threw " + e.toString()); } } /** * Commits the current recording */ private void commitRecording() { try { _recordControl.commit(); // Reset the recording settings _pendingCommit = false; _committed = true; _recording = false; // Alert the user that the recording has been committed Dialog.alert("Committed"); } catch (final Exception e) { VideoRecordingDemo.errorDialog("RecordControl#commit() threw " + e.toString()); } } /** * A popup dialog class which allows the user to pick an image effect to use */ private final static class ImageEffectDialog extends PopupScreen { private final RadioButtonGroup _options; /** * Creates a new ImageEffectDialog object * * @param effect * The name of the effect currently in use by the video * recording Player */ public ImageEffectDialog(final String effect) { super(new VerticalFieldManager()); // Create radio buttons for the image effect choices _options = new RadioButtonGroup(); final RadioButtonField none = new RadioButtonField("None", _options, effect == null); final RadioButtonField greyscale = new RadioButtonField("Greyscale", _options, (effect != null && effect.equals("monochrome"))); final RadioButtonField sepia = new RadioButtonField("Sepia", _options, (effect != null && effect.equals("sepia"))); // Add fields to the dialog add(new RichTextField("Choose Effect", Field.NON_FOCUSABLE)); add(new SeparatorField()); add(none); add(greyscale); add(sepia); } /** * @see net.rim.device.api.ui.Screen#invokeAction(int) */ protected boolean invokeAction(final int action) { final boolean result = super.invokeAction(action); if (action == ACTION_INVOKE) { close(); return true; } else { return result; } } /** * @see net.rim.device.api.ui.Screen#keyChar(char, int, int) */ protected boolean keyChar(final char c, final int status, final int time) { final boolean result = super.keyChar(c, status, time); switch (c) { case Characters.ENTER: close(); return true; default: return result; } } /** * Returns the image effect currently selected by the user in this * dialog * * @return The selected image effect preset name */ public String getImageEffectPreset() { switch (_options.getSelectedIndex()) { case 1: return "monochrome"; case 2: return "sepia"; default: return null; } } } }