package com.rnfloatingvideowidget; import android.app.Service; import android.content.Intent; import android.graphics.Color; import android.graphics.PixelFormat; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.Button; import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.view.Display; import android.widget.Toast; import android.os.Handler; import androidx.annotation.Nullable; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; import com.devbrackets.android.exomedia.listener.OnVideoSizeChangedListener; import com.devbrackets.android.exomedia.ui.widget.VideoView; import com.devbrackets.android.exomedia.listener.OnErrorListener; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableArray; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.rnfloatingvideowidget.R; import java.util.Date; import java.util.Map; import java.util.Random; import android.graphics.Point; public class FloatingVideoWidgetShowService extends Service { private Handler timeoutHandler = new Handler(); private static ReadableMap playingVideo = null; // The video currently playing private static ReadableArray videoPlaylist = null; // List of videos private static int index = 0; // Index of playing video in videoPlaylist private static ReadableMap initData = null; private GestureDetector gestureDetector; private int videoWidth = 250; // Default width of floating video player private int videoHeight = 180; // Default Height of floating video player WindowManager windowManager; View floatingWindow, floatingView, playerWrapper, overlayView; VideoView videoView; ImageButton increaseSize, decreaseSize, playVideo, pauseVideo; WindowManager.LayoutParams params; ReactContext reactContext = null; public FloatingVideoWidgetShowService() {} @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.getAction() != null) { switch (intent.getAction()) { case "ACTION_CLOSE_WIDGET": { long seek = videoView.getCurrentPosition(); videoView.setKeepScreenOn(false); stopSelf(); WritableMap args = new Arguments().createMap(); args.putInt("index", index); args.putInt("seek", (int) seek); args.putString("type", "close"); sendEvent(reactContext, "onClose", args); onDestroy(); break; } case "ACTION_PLAY": { onResume(floatingWindow); break; } case "ACTION_PAUSE": { onPause(floatingWindow); break; } case "ACTION_PREV": { onPrev(floatingWindow); break; } case "ACTION_NEXT": { onNext(floatingWindow); break; } case "ACTION_SET_VIDEO": { ReadableMap data = Arguments.fromBundle(intent.getBundleExtra("DATA")); initData = data; playingVideo = data.getMap("video"); videoPlaylist = data.getArray("videos"); index = data.getInt("index"); int Seek = data.getInt("seek"); Uri myUri = Uri.parse(playingVideo.getString("url")); videoView.setVideoURI(myUri); videoView.seekTo(Seek); videoView.start(); videoView.setKeepScreenOn(true); WritableMap args = Arguments.createMap(); args.putString("state", "isOpened"); args.putString("url", playingVideo.getString("url")); sendEvent(reactContext, "onOpen", args); break; } } } return START_STICKY; } @Override public void onCreate() { super.onCreate(); final ReactInstanceManager reactInstanceManager = getReactNativeHost().getReactInstanceManager(); ReactContext getReactContext = reactInstanceManager.getCurrentReactContext(); gestureDetector = new GestureDetector(this, new SingleTapConfirm()); reactContext = getReactContext; floatingWindow = LayoutInflater.from(this).inflate(R.layout.floating_widget_layout, null); // Define the layout flag according to android version. int LAYOUT_FLAG; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE; } // Setting layout params for floating video params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, LAYOUT_FLAG, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); windowManager = (WindowManager) getSystemService(WINDOW_SERVICE); assert windowManager != null; windowManager.addView(floatingWindow, params); // Define all the video view and its components floatingView = floatingWindow.findViewById(R.id.Layout_Expended); playerWrapper = floatingWindow.findViewById(R.id.view_wrapper); overlayView = floatingWindow.findViewById(R.id.overlay_view); videoView = (VideoView) floatingWindow.findViewById(R.id.videoView); increaseSize = (ImageButton) floatingWindow.findViewById(R.id.increase_size); decreaseSize = (ImageButton) floatingWindow.findViewById(R.id.decrease_size); playVideo = (ImageButton) floatingWindow.findViewById(R.id.app_video_play); pauseVideo = (ImageButton) floatingWindow.findViewById(R.id.app_video_pause); // Setting the on error Listener videoView.setOnErrorListener(new OnErrorListener() { @Override public boolean onError(Exception e) { long seek = videoView.getCurrentPosition(); WritableMap args = new Arguments().createMap(); args.putInt("index", index); args.putInt("seek", (int) seek); args.putString("url", playingVideo.getString("url")); args.putString("type", "error"); sendEvent(reactContext, "onError", args); Toast.makeText(reactContext, "An Error occured, please try again", Toast.LENGTH_LONG).show(); return false; } }); // Changes the video size when new video is loaded videoView.setOnVideoSizedChangedListener(new OnVideoSizeChangedListener() { @Override public void onVideoSizeChanged(int intrinsicWidth, int intrinsicHeight, float pixelWidthHeightRatio) { final float scale = reactContext.getResources().getDisplayMetrics().density; videoWidth = intrinsicWidth; videoHeight = intrinsicHeight; RelativeLayout relativeLayout = (RelativeLayout) floatingWindow.findViewById(R.id.view_wrapper); double aspectRatio = (double) videoWidth / (double) videoHeight; if (videoHeight > videoWidth) { int height = (int) (200 * scale + 0.5f); double width = height * aspectRatio; relativeLayout.getLayoutParams().width = (int) width; relativeLayout.getLayoutParams().height = height; } else { int width = (int) (250 * scale + 0.5f); double height = width / aspectRatio; relativeLayout.getLayoutParams().width = width; relativeLayout.getLayoutParams().height = (int) height; } } }); floatingWindow.findViewById(R.id.app_video_crop).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { long seek = videoView.getCurrentPosition(); videoView.setKeepScreenOn(false); stopSelf(); WritableMap args = new Arguments().createMap(); args.putInt("index", index); args.putInt("seek", (int) seek); args.putString("url", playingVideo.getString("url")); args.putString("type", "close"); sendEvent(reactContext, "onClose", args); onDestroy(); } }); floatingWindow.findViewById(R.id.Layout_Expended).setOnTouchListener(new View.OnTouchListener() { int X_Axis, Y_Axis; float TouchX, TouchY; @Override public boolean onTouch(View v, MotionEvent event) { if (gestureDetector.onTouchEvent(event)) { if (overlayView.getVisibility() == View.VISIBLE) { overlayView.setVisibility(View.GONE); timeoutHandler.removeCallbacksAndMessages(null); } else { overlayView.setVisibility(View.VISIBLE); timeoutHandler.postDelayed(new Runnable() { public void run() { overlayView.setVisibility(View.GONE); } }, 5000); } } else { int touches = event.getPointerCount(); if (touches > 1) { } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: X_Axis = params.x; Y_Axis = params.y; TouchX = event.getRawX(); TouchY = event.getRawY(); return true; case MotionEvent.ACTION_UP: floatingView.setVisibility(View.VISIBLE); return true; case MotionEvent.ACTION_MOVE: params.x = X_Axis + (int) (event.getRawX() - TouchX); params.y = Y_Axis + (int) (event.getRawY() - TouchY); windowManager.updateViewLayout(floatingWindow, params); return true; } } return false; } }); } private class SingleTapConfirm extends SimpleOnGestureListener { @Override public boolean onSingleTapUp(MotionEvent event) { return true; } } private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params); } public void increaseWindowSize(View view) { final float scale = reactContext.getResources().getDisplayMetrics().density; RelativeLayout relativeLayout = (RelativeLayout) floatingWindow.findViewById(R.id.view_wrapper); Display display = windowManager.getDefaultDisplay(); Point size = new Point(); display.getSize(size); int densityX = size.x; // default height width of screen double aspectRatio = (double) videoWidth / (double) videoHeight; if (videoHeight > videoWidth) { int height = (int) (400 * scale + 0.5f); double width = height * aspectRatio; relativeLayout.getLayoutParams().width = (int) width; relativeLayout.getLayoutParams().height = height; } else { int width = densityX; double height = width / aspectRatio; relativeLayout.getLayoutParams().width = densityX; relativeLayout.getLayoutParams().height = (int) height; } increaseSize.setVisibility(View.GONE); decreaseSize.setVisibility(View.VISIBLE); } public void returnToApp(View view) { long seek = videoView.getCurrentPosition(); Intent intent = getPackageManager().getLaunchIntentForPackage(reactContext.getPackageName()); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); videoView.setKeepScreenOn(false); stopSelf(); WritableMap args = new Arguments().createMap(); args.putInt("index", index); args.putInt("seek", (int) seek); args.putString("type", "close"); args.putString("url", playingVideo.getString("url")); sendEvent(reactContext, "onClose", args); onDestroy(); } public void decreaseWindowSize(View view) { final float scale = reactContext.getResources().getDisplayMetrics().density; RelativeLayout relativeLayout = (RelativeLayout) floatingWindow.findViewById(R.id.view_wrapper); double aspectRatio = (double) videoWidth / (double) videoHeight; if (videoHeight > videoWidth) { int height = (int) (200 * scale + 0.5f); double width = height * aspectRatio; relativeLayout.getLayoutParams().width = (int) width; relativeLayout.getLayoutParams().height = height; } else { int width = (int) (250 * scale + 0.5f); double height = width / aspectRatio; relativeLayout.getLayoutParams().width = width; relativeLayout.getLayoutParams().height = (int) height; } increaseSize.setVisibility(View.VISIBLE); decreaseSize.setVisibility(View.GONE); } public void onPause(View view) { long seek = videoView.getCurrentPosition(); playVideo.setVisibility(ImageButton.VISIBLE); pauseVideo.setVisibility(ImageButton.GONE); videoView.pause(); WritableMap args = Arguments.createMap(); args.putInt("index", index); args.putInt("seek", (int) seek); args.putString("type", "paused"); args.putString("url", playingVideo.getString("url")); sendEvent(reactContext, "onPause", args); } public void onResume(View view) { long seek = videoView.getCurrentPosition(); playVideo.setVisibility(ImageButton.GONE); pauseVideo.setVisibility(ImageButton.VISIBLE); videoView.start(); WritableMap args = Arguments.createMap(); args.putInt("index", index); args.putInt("seek", (int) seek); args.putString("type", "resume"); args.putString("url", playingVideo.getString("url")); sendEvent(reactContext, "onPlay", args); } public void onNext(View view) { int next = index + 1; if (index == videoPlaylist.size() - 1) { next = 0; } index = next; ReadableMap video = videoPlaylist.getMap(next); playingVideo = video; Uri myUri = Uri.parse(video.getString("url")); videoView.setVideoURI(myUri); videoView.seekTo(0); videoView.start(); playVideo.setVisibility(ImageButton.GONE); pauseVideo.setVisibility(ImageButton.VISIBLE); WritableMap args = Arguments.createMap(); args.putInt("index", index); args.putString("type", "next"); args.putString("url", playingVideo.getString("url")); sendEvent(reactContext, "onNext", args); } public void onPrev(View view) { int next = index - 1; if (index == 0) { videoView.seekTo(0); return; } index = next; ReadableMap video = videoPlaylist.getMap(next); playingVideo = video; Uri myUri = Uri.parse(video.getString("url")); videoView.setVideoURI(myUri); videoView.seekTo(0); videoView.start(); playVideo.setVisibility(ImageButton.GONE); pauseVideo.setVisibility(ImageButton.VISIBLE); WritableMap args = Arguments.createMap(); args.putInt("index", index); args.putString("type", "prev"); args.putString("url", playingVideo.getString("url")); sendEvent(reactContext, "onPrev", args); } @Override public void onDestroy() { super.onDestroy(); if (floatingWindow != null) windowManager.removeView(floatingWindow); } protected ReactNativeHost getReactNativeHost() { return ((ReactApplication) getApplication()).getReactNativeHost(); } }