From e76ecf172fe331c10acb068779fcefa65f5ae492 Mon Sep 17 00:00:00 2001 From: mosazaid Date: Wed, 9 Jun 2021 14:46:03 +0300 Subject: [PATCH] video-straming-android-changes --- android/app/build.gradle | 1 + .../main/kotlin/com/hmg/hmgDr/MainActivity.kt | 32 +- .../hmgDr/ui/fragment/VideoCallFragment.kt | 385 ++++++++++++++++++ android/app/src/main/res/values/strings.xml | 2 + pubspec.lock | 2 +- 5 files changed, 410 insertions(+), 12 deletions(-) create mode 100644 android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt diff --git a/android/app/build.gradle b/android/app/build.gradle index 16960d54..deb42f99 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -70,6 +70,7 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt index 612118c9..b95e7cb9 100644 --- a/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/MainActivity.kt @@ -2,17 +2,19 @@ package com.hmg.hmgDr import android.app.Activity import android.content.Intent +import android.os.Bundle import androidx.annotation.NonNull +import com.google.gson.GsonBuilder import com.hmg.hmgDr.Model.GetSessionStatusModel import com.hmg.hmgDr.Model.SessionStatusModel -import com.hmg.hmgDr.ui.VideoCallActivity -import com.google.gson.GsonBuilder +import io.flutter.embedding.android.FlutterFragment import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugins.GeneratedPluginRegistrant + class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler { private val CHANNEL = "Dr.cloudSolution/videoCall" @@ -20,6 +22,7 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler private var call: MethodCall? = null private val LAUNCH_VIDEO: Int = 1 + private val flutterFragment: FlutterFragment? = null override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine) @@ -27,6 +30,11 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler(this) } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + } + override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { @@ -59,15 +67,17 @@ class MainActivity : FlutterFragmentActivity(), MethodChannel.MethodCallHandler private fun openVideoCall(apiKey: String?, sessionId: String?, token: String?, appLang: String?, baseUrl: String?, sessionStatusModel: GetSessionStatusModel) { // val videoCallActivity = VideoCallActivity() - val intent = Intent(this, VideoCallActivity::class.java) - intent.putExtra("apiKey", apiKey) - intent.putExtra("sessionId", sessionId) - intent.putExtra("token", token) - intent.putExtra("appLang", appLang) - intent.putExtra("baseUrl", baseUrl) - intent.putExtra("sessionStatusModel", sessionStatusModel) - startActivityForResult(intent, LAUNCH_VIDEO) - +// val intent = Intent(this, VideoCallActivity::class.java) +// intent.putExtra("apiKey", apiKey) +// intent.putExtra("sessionId", sessionId) +// intent.putExtra("token", token) +// intent.putExtra("appLang", appLang) +// intent.putExtra("baseUrl", baseUrl) +// intent.putExtra("sessionStatusModel", sessionStatusModel) +// startActivityForResult(intent, LAUNCH_VIDEO) + + val transaction = supportFragmentManager.beginTransaction() +// transaction.add() } diff --git a/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt new file mode 100644 index 00000000..be343b89 --- /dev/null +++ b/android/app/src/main/kotlin/com/hmg/hmgDr/ui/fragment/VideoCallFragment.kt @@ -0,0 +1,385 @@ +package com.hmg.hmgDr.ui.fragment + +import android.Manifest +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.opengl.GLSurfaceView +import android.os.Bundle +import android.os.CountDownTimer +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.fragment.app.Fragment +import com.hmg.hmgDr.Model.ChangeCallStatusRequestModel +import com.hmg.hmgDr.Model.GetSessionStatusModel +import com.hmg.hmgDr.Model.SessionStatusModel +import com.hmg.hmgDr.R +import com.hmg.hmgDr.ui.VideoCallActivity +import com.hmg.hmgDr.ui.VideoCallContract.VideoCallPresenter +import com.hmg.hmgDr.ui.VideoCallContract.VideoCallView +import com.hmg.hmgDr.ui.VideoCallPresenterImpl +import com.opentok.android.* +import com.opentok.android.PublisherKit.PublisherListener +import pub.devrel.easypermissions.AfterPermissionGranted +import pub.devrel.easypermissions.AppSettingsDialog +import pub.devrel.easypermissions.EasyPermissions +import pub.devrel.easypermissions.EasyPermissions.PermissionCallbacks +import java.util.* + +class VideoCallFragment : Fragment(), PermissionCallbacks, Session.SessionListener, PublisherListener, + SubscriberKit.VideoListener, VideoCallView { + + lateinit var videoCallPresenter: VideoCallPresenter + + private var mSession: Session? = null + private var mPublisher: Publisher? = null + private var mSubscriber: Subscriber? = null + + private var mVolHandler: Handler? = null + private var mConnectedHandler: Handler? = null + private var mVolRunnable: Runnable? = null + private var mConnectedRunnable: Runnable? = null + + private var mPublisherViewContainer: FrameLayout? = null + private var mSubscriberViewContainer: RelativeLayout? = null + private var controlPanel: RelativeLayout? = null + + private var apiKey: String? = null + private var sessionId: String? = null + private var token: String? = null + private var appLang: String? = null + private var baseUrl: String? = null + + private var isSwitchCameraClicked = false + private var isCameraClicked = false + private var isSpeckerClicked = false + private var isMicClicked = false + + private var mCallBtn: ImageView? = null + private var mCameraBtn: ImageView? = null + private var mSwitchCameraBtn: ImageView? = null + private var mspeckerBtn: ImageView? = null + private var mMicBtn: ImageView? = null + + private val progressBar: ProgressBar? = null + private val countDownTimer: CountDownTimer? = null + private val progressBarTextView: TextView? = null + private val progressBarLayout: RelativeLayout? = null + + private var isConnected = false + + private var sessionStatusModel: GetSessionStatusModel? = null + + + override fun onCreate(savedInstanceState: Bundle?) { + requireActivity().setTheme(R.style.AppTheme) + super.onCreate(savedInstanceState) + + requestPermissions() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + + val view = inflater.inflate(R.layout.activity_video_call, container, false) + + Objects.requireNonNull(requireActivity().actionBar)!!.hide() + initUI(view) + + return view + } + + override fun onPause() { + super.onPause() + if (mSession == null) { + return + } + mSession!!.onPause() + if (requireActivity().isFinishing) { + disconnectSession() + } + } + + override fun onResume() { + super.onResume() + if (mSession == null) { + return + } + mSession!!.onResume() + } + + override fun onDestroy() { + disconnectSession() + super.onDestroy() + } + + @SuppressLint("ClickableViewAccessibility") + private fun initUI(view: View) { + mPublisherViewContainer = view.findViewById(R.id.local_video_view_container) + mSubscriberViewContainer = view.findViewById(R.id.remote_video_view_container) + + arguments?.run { + apiKey = getString("apiKey") + sessionId = getString("sessionId") + token = getString("token") + appLang = getString("appLang") + baseUrl = getString("baseUrl") + sessionStatusModel = getParcelable("sessionStatusModel") + } + + controlPanel = view.findViewById(R.id.control_panel) + videoCallPresenter = VideoCallPresenterImpl(this, baseUrl) + mCallBtn = view.findViewById(R.id.btn_call) + mCameraBtn = view.findViewById(R.id.btn_camera) + mSwitchCameraBtn = view.findViewById(R.id.btn_switch_camera) + mspeckerBtn = view.findViewById(R.id.btn_specker) + mMicBtn = view.findViewById(R.id.btn_mic) + + // progressBarLayout=findViewById(R.id.progressBar); + // progressBar=findViewById(R.id.progress_bar); +// progressBarTextView=findViewById(R.id.progress_bar_text); +// progressBar.setVisibility(View.GONE); + hiddenButtons() + checkClientConnected() + mSubscriberViewContainer!!.setOnTouchListener { v: View?, event: MotionEvent? -> + controlPanel!!.visibility = View.VISIBLE + mVolHandler!!.removeCallbacks(mVolRunnable!!) + mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) + true + } + if (appLang == "ar") { + progressBarLayout!!.layoutDirection = View.LAYOUT_DIRECTION_RTL + } + } + + private fun checkClientConnected() { + mConnectedHandler = Handler((Looper.getMainLooper())) + mConnectedRunnable = Runnable { + if (!isConnected) { + videoCallPresenter.callClintConnected(sessionStatusModel) + } + } + mConnectedHandler!!.postDelayed(mConnectedRunnable!!, (55 * 1000).toLong()) + } + + private fun hiddenButtons() { + mVolHandler = Handler() + mVolRunnable = Runnable { controlPanel!!.visibility = View.GONE } + mVolHandler!!.postDelayed(mVolRunnable!!, (5 * 1000).toLong()) + } + + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this) + } + + override fun onPermissionsGranted(requestCode: Int, perms: List) { + Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size) + } + + override fun onPermissionsDenied(requestCode: Int, perms: List) { + Log.d(TAG, "onPermissionsDenied:" + requestCode + ":" + perms.size) + if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { + AppSettingsDialog.Builder(this) + .setTitle(getString(R.string.title_settings_dialog)) + .setRationale(getString(R.string.rationale_ask_again)) + .setPositiveButton(getString(R.string.setting)) + .setNegativeButton(getString(R.string.cancel)) + .setRequestCode(RC_SETTINGS_SCREEN_PERM) + .build() + .show() + } + } + + @AfterPermissionGranted(RC_VIDEO_APP_PERM) + private fun requestPermissions() { + val perms = arrayOf(Manifest.permission.INTERNET, Manifest.permission.CAMERA) + if (EasyPermissions.hasPermissions(requireContext(), *perms)) { + try { + mSession = Session.Builder(context, apiKey, sessionId).build() + mSession!!.setSessionListener(this) + mSession!!.connect(token) + } catch (e: Exception) { + e.printStackTrace() + } + } else { + EasyPermissions.requestPermissions(this, getString(R.string.remaining_ar), RC_VIDEO_APP_PERM, *perms) + } + } + + override fun onConnected(session: Session?) { + Log.i(TAG, "Session Connected") + mPublisher = Publisher.Builder(requireContext()).build() + mPublisher!!.setPublisherListener(this) + mPublisherViewContainer!!.addView(mPublisher!!.view) + if (mPublisher!!.getView() is GLSurfaceView) { + (mPublisher!!.getView() as GLSurfaceView).setZOrderOnTop(true) + } + mSession!!.publish(mPublisher) + } + + override fun onDisconnected(session: Session) { + Log.d(TAG, "onDisconnected: disconnected from session " + session.sessionId) + mSession = null + } + + override fun onError(session: Session, opentokError: OpentokError) { + Log.d(TAG, "onError: Error (" + opentokError.message + ") in session " + session.sessionId) + Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() + requireActivity().finish() + } + + override fun onStreamReceived(session: Session, stream: Stream) { + Log.d(TAG, "onStreamReceived: New stream " + stream.streamId + " in session " + session.sessionId) + if (mSubscriber != null) { + isConnected = true + return + } + isConnected = true + subscribeToStream(stream) + videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(3, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) + } + + override fun onStreamDropped(session: Session, stream: Stream) { + Log.d(TAG, "onStreamDropped: Stream " + stream.streamId + " dropped from session " + session.sessionId) + if (mSubscriber == null) { + return + } + if (mSubscriber!!.stream == stream) { + mSubscriberViewContainer!!.removeView(mSubscriber!!.view) + mSubscriber!!.destroy() + mSubscriber = null + } + disconnectSession() + } + + override fun onStreamCreated(publisherKit: PublisherKit?, stream: Stream) { + Log.d(TAG, "onStreamCreated: Own stream " + stream.streamId + " created") + } + + override fun onStreamDestroyed(publisherKit: PublisherKit?, stream: Stream) { + Log.d(TAG, "onStreamDestroyed: Own stream " + stream.streamId + " destroyed") + } + + override fun onError(publisherKit: PublisherKit?, opentokError: OpentokError) { + Log.d(VideoCallFragment.TAG, "onError: Error (" + opentokError.message + ") in publisher") + Toast.makeText(requireContext(), "Session error. See the logcat please.", Toast.LENGTH_LONG).show() + requireActivity().finish() + } + + override fun onVideoDataReceived(subscriberKit: SubscriberKit?) { + mSubscriber!!.setStyle(BaseVideoRenderer.STYLE_VIDEO_SCALE, BaseVideoRenderer.STYLE_VIDEO_FILL) + mSubscriberViewContainer!!.addView(mSubscriber!!.view) + } + + override fun onVideoDisabled(subscriberKit: SubscriberKit?, s: String?) {} + + override fun onVideoEnabled(subscriberKit: SubscriberKit?, s: String?) {} + + override fun onVideoDisableWarning(subscriberKit: SubscriberKit?) {} + + override fun onVideoDisableWarningLifted(subscriberKit: SubscriberKit?) {} + + private fun subscribeToStream(stream: Stream) { + mSubscriber = Subscriber.Builder(requireContext(), stream).build() + mSubscriber!!.setVideoListener(this) + mSession!!.subscribe(mSubscriber) + } + + private fun disconnectSession() { + if (mSession == null) { + requireActivity().setResult(Activity.RESULT_CANCELED) + requireActivity().finish() + return + } + if (mSubscriber != null) { + mSubscriberViewContainer!!.removeView(mSubscriber!!.view) + mSession!!.unsubscribe(mSubscriber) + mSubscriber!!.destroy() + mSubscriber = null + } + if (mPublisher != null) { + mPublisherViewContainer!!.removeView(mPublisher!!.view) + mSession!!.unpublish(mPublisher) + mPublisher!!.destroy() + mPublisher = null + } + mSession!!.disconnect() + countDownTimer?.cancel() + videoCallPresenter.callChangeCallStatus(ChangeCallStatusRequestModel(16, sessionStatusModel!!.doctorId, sessionStatusModel!!.generalid, token, sessionStatusModel!!.vcid)) + requireActivity().finish() + } + + fun onSwitchCameraClicked(view: View?) { + if (mPublisher != null) { + isSwitchCameraClicked = !isSwitchCameraClicked + mPublisher!!.cycleCamera() + val res = if (isSwitchCameraClicked) R.drawable.flip_disapled else R.drawable.flip_enabled + mSwitchCameraBtn!!.setImageResource(res) + } + } + + fun onCameraClicked(view: View?) { + if (mPublisher != null) { + isCameraClicked = !isCameraClicked + mPublisher!!.publishVideo = !isCameraClicked + val res = if (isCameraClicked) R.drawable.video_disanabled else R.drawable.video_enabled + mCameraBtn!!.setImageResource(res) + } + } + + fun onSpeckerClicked(view: View?) { + if (mSubscriber != null) { + isSpeckerClicked = !isSpeckerClicked + mSubscriber!!.subscribeToAudio = !isSpeckerClicked + val res = if (isSpeckerClicked) R.drawable.audio_disabled else R.drawable.audio_enabled + mspeckerBtn!!.setImageResource(res) + } + } + + fun onMicClicked(view: View?) { + if (mPublisher != null) { + isMicClicked = !isMicClicked + mPublisher!!.publishAudio = !isMicClicked + val res = if (isMicClicked) R.drawable.mic_disabled else R.drawable.mic_enabled + mMicBtn!!.setImageResource(res) + } + } + + fun onCallClicked(view: View?) { + disconnectSession() + } + + override fun onCallSuccessful(sessionStatusModel: SessionStatusModel) { + if (sessionStatusModel.sessionStatus == 2 || sessionStatusModel.sessionStatus == 3) { + val returnIntent = Intent() + returnIntent.putExtra("sessionStatusNotRespond", sessionStatusModel) + requireActivity().setResult(Activity.RESULT_OK, returnIntent) + requireActivity().finish() + } + } + + override fun onCallChangeCallStatusSuccessful(sessionStatusModel: SessionStatusModel?) {} + + override fun onFailure() {} + + companion object { + @JvmStatic + fun newInstance(args: Bundle) = + VideoCallFragment().apply { + arguments = args + } + + private val TAG = VideoCallActivity::class.java.simpleName + + private const val RC_SETTINGS_SCREEN_PERM = 123 + private const val RC_VIDEO_APP_PERM = 124 + + } +} \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 74349756..bc694f7c 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -4,5 +4,7 @@ الوقت المتبقي بالثانيه: Settings Cancel + + Hello blank fragment \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 77df9848..49ff6372 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -587,7 +587,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3-nullsafety.1" + version: "0.6.2" json_annotation: dependency: transitive description: