Java tutorial
/* * PhoneGap is available under *either* the terms of the modified BSD license *or* the * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. * */ package com.simplec.phonegap.plugins.videoplayer; import java.io.File; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaResourceApi; import org.apache.cordova.PluginResult; import org.json.JSONException; import org.json.JSONObject; import android.R.color; import android.annotation.TargetApi; import android.app.Dialog; import android.content.res.AssetFileDescriptor; import android.graphics.Point; import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnErrorListener; import android.media.MediaPlayer.OnPreparedListener; import android.net.Uri; import android.os.Build; import android.util.Log; import android.view.Display; import android.view.GestureDetector; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.VideoView; public class VideoPlayer extends CordovaPlugin implements OnCompletionListener, OnPreparedListener, OnErrorListener { protected static final String LOG_TAG = "VideoPlayer"; protected static final String ASSETS = "/android_asset/"; protected static final String FILE = "file://"; private boolean prepared = false; private Dialog dialog; private VideoView videoView; private MediaPlayer player; private PlayerGestureDetection detector; private CallbackContext callbackContext; public final static String PAUSE = "pause"; public final static String PLAY = "play"; public final static String STOP = "stop"; public final static String DURATION = "duration"; public final static String SEEK = "seek"; public final static String PLAYING = "playing"; public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.v(LOG_TAG, "got command: " + action); if (action.equals("pause")) { pause(); return true; } if (action.equals(PLAY)) { String target = null; try { target = args.getString(0); } catch (Exception e) { target = null; } if (target == null && this.player != null) { Log.v(LOG_TAG, "resuming"); resume(); return true; } Log.v(LOG_TAG, "playing file: " + target); play(args, callbackContext); return true; } if (action.equals(STOP)) { Log.v(LOG_TAG, "stopping"); stop(); return true; } if (action.equals(PLAYING)) { if (player != null) { callbackContext.success(player.isPlaying() ? 1 : 0); } else { callbackContext.error("no player"); } return true; } if (action.equals(DURATION)) { if (player != null) { callbackContext.success(player.getDuration() / 1000); } else { callbackContext.error("no player"); } return true; } if (action.equals(SEEK)) { if (player != null) { int sec = args.getInt(0); int msec = sec * 1000; if (msec < 0) { msec = player.getDuration() - 1; } player.seekTo(msec); callbackContext.success(); } else { callbackContext.error("no player"); } return true; } return false; } /** * Removes the "file://" prefix from the given URI string, if applicable. If * the given URI string doesn't have a "file://" prefix, it is returned * unchanged. * * @param uriString * the URI string to operate on * @return a path without the "file://" prefix */ public static String stripFileProtocol(String uriString) { if (uriString.startsWith("file://")) { return Uri.parse(uriString).getPath(); } return uriString; } public boolean play(CordovaArgs args, final CallbackContext callbackContext) { Log.v(LOG_TAG, "stopping if necessary"); stop(); Log.v(LOG_TAG, "playing"); try { CordovaResourceApi resourceApi = webView.getResourceApi(); String target = args.getString(0); JSONObject optionsTmp = new JSONObject(); try { optionsTmp = args.getJSONObject(1); } catch (Exception e) { // gobble. no options sent } final JSONObject options = optionsTmp; String fileUriStr; try { Uri targetUri = resourceApi.remapUri(Uri.parse(target)); fileUriStr = targetUri.toString(); } catch (IllegalArgumentException e) { fileUriStr = target; } Log.v(LOG_TAG, fileUriStr); Log.v(LOG_TAG, "playing file: " + fileUriStr); if (fileUriStr.startsWith("file:")) { final String path = stripFileProtocol(fileUriStr); File f = new File(path); if (!f.exists()) { Log.v(LOG_TAG, "does not exist: " + fileUriStr); callbackContext.error("video does not exist"); return true; } Log.v(LOG_TAG, "playing path: " + path); // Create dialog in new thread cordova.getActivity().runOnUiThread(new Runnable() { public void run() { Log.v(LOG_TAG, "openVideoDialog"); openVideoDialog(path, options, callbackContext); } }); } else if (fileUriStr.startsWith("http:") || fileUriStr.startsWith("https:")) { final String path = fileUriStr; Log.v(LOG_TAG, "playing URL: " + path); // Create dialog in new thread cordova.getActivity().runOnUiThread(new Runnable() { public void run() { Log.v(LOG_TAG, "openVideoDialog"); openVideoDialog(path, options, callbackContext); } }); } else { Log.v(LOG_TAG, "unknown protocol: " + fileUriStr); callbackContext.error("video unknown protocol"); } return true; } catch (JSONException je) { Log.v(LOG_TAG, "error2: " + je.getMessage()); callbackContext.error(je.getMessage()); je.printStackTrace(); return false; } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN) protected void openVideoDialog(String path, JSONObject options, final CallbackContext callbackContext) { this.callbackContext = callbackContext; MediaMetadataRetriever metaRetriever = new MediaMetadataRetriever(); if (path.startsWith(ASSETS)) { String f = path.substring(ASSETS.length()); AssetFileDescriptor fd = null; try { fd = cordova.getActivity().getAssets().openFd(f); metaRetriever.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); } catch (Exception e) { callbackContext.error(e.getLocalizedMessage()); Log.v(LOG_TAG, "error: " + e.getLocalizedMessage()); e.printStackTrace(); } } else { try { Log.v(LOG_TAG, "setDataSource file"); metaRetriever.setDataSource(path); } catch (Exception e) { callbackContext.error(e.getLocalizedMessage()); Log.v(LOG_TAG, "error: " + e.getLocalizedMessage()); e.printStackTrace(); } } double mVideoHeight = 0; double mVideoWidth = 0; try { String height = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); String width = metaRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); mVideoHeight = Float.parseFloat(height); mVideoWidth = Float.parseFloat(width); } catch (NumberFormatException e) { mVideoHeight = 900; mVideoWidth = 1600; Log.d(LOG_TAG, e.getMessage()); } Display display = this.cordova.getActivity().getWindowManager().getDefaultDisplay(); Point size = new Point(); display.getSize(size); size.x = size.x * 9 / 10; size.y = size.y * 9 / 10; double displayAspect = ((double) size.x) / ((double) size.y); double videoAspect = ((double) mVideoWidth) / ((double) mVideoHeight); if (displayAspect > videoAspect) { mVideoHeight = size.y; mVideoWidth = mVideoHeight * videoAspect; } else { mVideoWidth = mVideoHeight * displayAspect; } // Let's create the main dialog dialog = new Dialog(cordova.getActivity(), android.R.style.Theme_NoTitleBar); dialog.getWindow().getAttributes().windowAnimations = android.R.style.Animation_Dialog; dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); dialog.setCancelable(false); Log.v(LOG_TAG, "getting dimensions"); int h = (int) mVideoHeight; int w = (int) mVideoWidth; Log.v(LOG_TAG, "width: " + w); Log.v(LOG_TAG, "height: " + h); // Main container layout LinearLayout main = new LinearLayout(cordova.getActivity()); main.setBackgroundColor(color.black); main.setLayoutParams(new LinearLayout.LayoutParams(w, h)); // main.setOrientation(LinearLayout.VERTICAL); main.setHorizontalGravity(Gravity.CENTER_HORIZONTAL); main.setVerticalGravity(Gravity.CENTER_VERTICAL); main.setLayerType(View.LAYER_TYPE_NONE, null); videoView = new VideoView(cordova.getActivity()); videoView.setBackgroundColor(color.black); videoView.setLayoutParams(new LinearLayout.LayoutParams(w, h)); //videoView.setZOrderOnTop(true); // videoView.setVideoURI(uri); // videoView.setVideoPath(path); main.addView(videoView); detector = new PlayerGestureDetection(cordova.getActivity(), new PlayerGestureDetection.SimpleGestureListener() { @Override public void onSwipe(int direction) { // TODO Auto-generated method stub Log.v(LOG_TAG, " onSwipe " + direction); JSONObject event = new JSONObject(); try { event.put("type", "swipe"); if (direction == PlayerGestureDetection.SWIPE_LEFT) { event.put("direction", "right"); } if (direction == PlayerGestureDetection.SWIPE_RIGHT) { event.put("direction", "left"); } if (direction == PlayerGestureDetection.SWIPE_UP) { event.put("direction", "up"); } if (direction == PlayerGestureDetection.SWIPE_DOWN) { event.put("direction", "down"); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } PluginResult errorResult = new PluginResult(PluginResult.Status.OK, event); errorResult.setKeepCallback(true); callbackContext.sendPluginResult(errorResult); } @Override public void onSingleTap() { Log.v(LOG_TAG, " onSingleTapUp "); if (player.isPlaying()) { Log.v(LOG_TAG, " pausing "); player.pause(); videoView.pause(); } else { Log.v(LOG_TAG, " resuming "); videoView.start(); player.start(); } } }); player = new MediaPlayer(); player.setOnPreparedListener(this); player.setOnCompletionListener(this); player.setOnErrorListener(this); if (path.startsWith(ASSETS)) { String f = path.substring(ASSETS.length()); AssetFileDescriptor fd = null; try { fd = cordova.getActivity().getAssets().openFd(f); player.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); } catch (Exception e) { callbackContext.error(e.getLocalizedMessage()); Log.v(LOG_TAG, "error3: " + e.getLocalizedMessage()); e.printStackTrace(); stop(); return; } } else { try { Log.v(LOG_TAG, "setDataSource file"); player.setDataSource(path); } catch (Exception e) { callbackContext.error(e.getLocalizedMessage()); Log.v(LOG_TAG, "error4: " + e.getLocalizedMessage()); e.printStackTrace(); stop(); return; } } videoView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { // TODO Auto-generated method stub } }); videoView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.v(LOG_TAG, "onTouch "); if (detector != null) { detector.onTouchEvent(event); } return true; } }); videoView.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent keyEvent) { Log.v(LOG_TAG, "onKey keyCode=" + keyCode + " evt.keycode" + keyEvent.getKeyCode() + " evt.flags" + keyEvent.getFlags()); JSONObject event = new JSONObject(); try { event.put("type", "key"); event.put("keyCode", keyCode); event.put("flags", keyEvent.getFlags()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } PluginResult errorResult = new PluginResult(PluginResult.Status.OK, event); errorResult.setKeepCallback(true); callbackContext.sendPluginResult(errorResult); return true; } }); try { float volume = Float.valueOf(options.getString("volume")); player.setVolume(volume, volume); } catch (Exception e) { Log.v(LOG_TAG, "error: " + e.getLocalizedMessage()); } if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { int scalingMode = 0; try { scalingMode = options.getInt("scalingMode"); } catch (Exception e) { scalingMode = 0; } Log.v(LOG_TAG, "Scaling: " + scalingMode); switch (scalingMode) { case MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING: player.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); break; case MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT: player.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT); break; default: player.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); } } final SurfaceHolder mHolder = videoView.getHolder(); mHolder.setKeepScreenOn(true); //mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); mHolder.addCallback(new SurfaceHolder.Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { Log.v(LOG_TAG, "surfaceCreated"); if (player != null) { player.setDisplay(holder); try { Log.v(LOG_TAG, "preparing player"); if (!prepared) { player.prepare(); prepared = true; } } catch (Exception e) { callbackContext.error(e.getLocalizedMessage()); } } } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.v(LOG_TAG, "surfaceDestroyed"); if (player != null) { // don't destroy the player } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } }); WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); lp.copyFrom(dialog.getWindow().getAttributes()); lp.width = WindowManager.LayoutParams.MATCH_PARENT; lp.height = WindowManager.LayoutParams.MATCH_PARENT; dialog.setContentView(main); dialog.show(); dialog.getWindow().setAttributes(lp); } @Override public boolean onError(MediaPlayer mp, int what, int extra) { Log.e(LOG_TAG, "VideoPlayer.onError(" + what + ", " + extra + ")"); JSONObject event = new JSONObject(); try { event.put("type", "error"); event.put("what", what); event.put("extra", extra); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } PluginResult errorResult = new PluginResult(PluginResult.Status.OK, event); errorResult.setKeepCallback(false); callbackContext.sendPluginResult(errorResult); mp.stop(); mp.release(); dialog.dismiss(); prepared = false; dialog = null; player = null; videoView = null; detector = null; callbackContext = null; return false; } @Override public void onPrepared(MediaPlayer mp) { Log.v(LOG_TAG, "onPrepared"); JSONObject event = new JSONObject(); try { event.put("type", "prepared"); event.put("duration", player.getDuration() / 1000); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.v(LOG_TAG, "starting video"); mp.start(); PluginResult errorResult = new PluginResult(PluginResult.Status.OK, event); errorResult.setKeepCallback(true); callbackContext.sendPluginResult(errorResult); /*MediaController mc = new MediaController(cordova.getActivity()); mc.setAnchorView(videoView); mc.setMediaPlayer(videoView); videoView.setMediaController(mc); mc.setVisibility(View.VISIBLE); //((ViewGroup)videoView.getParent()).addView(mc);*/ } @Override public void onCompletion(MediaPlayer mp) { Log.v(LOG_TAG, "onCompletion"); JSONObject event = new JSONObject(); try { event.put("type", "completed"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } PluginResult errorResult = new PluginResult(PluginResult.Status.OK, event); errorResult.setKeepCallback(false); callbackContext.sendPluginResult(errorResult); mp.stop(); mp.release(); dialog.dismiss(); prepared = false; dialog = null; player = null; videoView = null; detector = null; callbackContext = null; } public boolean pause() { if (this.player != null) { this.player.pause(); JSONObject event = new JSONObject(); try { event.put("type", "paused"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } PluginResult eventResult = new PluginResult(PluginResult.Status.OK, event); eventResult.setKeepCallback(true); callbackContext.sendPluginResult(eventResult); return true; } return false; } public boolean resume() { if (this.player != null) { videoView.resume(); this.player.start(); JSONObject event = new JSONObject(); try { event.put("type", "playing"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } PluginResult eventResult = new PluginResult(PluginResult.Status.OK, event); eventResult.setKeepCallback(true); callbackContext.sendPluginResult(eventResult); return true; } return false; } public boolean stop() { if (player != null) { JSONObject event = new JSONObject(); try { event.put("type", "completed"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } PluginResult errorResult = new PluginResult(PluginResult.Status.OK, event); errorResult.setKeepCallback(false); callbackContext.sendPluginResult(errorResult); if (player != null) { player.stop(); player.release(); player = null; } if (dialog != null) { dialog.dismiss(); dialog = null; } prepared = false; videoView = null; detector = null; callbackContext = null; return true; } return false; } @Override public void onPause(boolean multitasking) { Log.v(LOG_TAG, "ON PAUSE"); super.onPause(multitasking); if (videoView != null) { player.pause(); videoView.pause(); videoView.suspend(); } } @Override public void onResume(boolean multitasking) { Log.v(LOG_TAG, "ON RESUME"); super.onResume(multitasking); if (videoView != null) { videoView.resume(); videoView.start(); player.start(); } } }