package com.jnesis.capacitor.brightcoveplayer import AnswersAdapter import android.content.Context.MODE_PRIVATE import android.content.pm.ActivityInfo import android.content.res.Configuration import android.content.res.Resources import android.graphics.Color import android.graphics.Point import android.net.Uri import android.os.Bundle import android.os.Handler import android.os.Looper import android.util.Log import android.util.Pair import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.animation.TranslateAnimation import android.widget.ImageButton import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.brightcove.player.appcompat.BrightcovePlayerFragment import com.brightcove.player.event.Event import com.brightcove.player.event.EventEmitter import com.brightcove.player.event.EventType import com.brightcove.player.mediacontroller.BrightcoveMediaController import com.brightcove.player.mediacontroller.ShowHideController import com.brightcove.player.model.CuePoint import com.brightcove.player.model.Video import com.brightcove.player.view.BrightcoveExoPlayerVideoView import com.brightcove.player.captioning.BrightcoveCaptionFormat import com.getcapacitor.JSObject import com.getcapacitor.PluginCall import com.google.gson.Gson import com.jnesis.capacitor.brightcoveplayer.adapters.MultipleAnswersAdapter import com.jnesis.capacitor.brightcoveplayer.capacitorbrightcoveplayer.R import com.jnesis.capacitor.brightcoveplayer.events.EventBus import com.jnesis.capacitor.brightcoveplayer.events.VideoPlayerBeforeCloseEvent import com.jnesis.capacitor.brightcoveplayer.events.VideoPlayerCloseEvent import com.jnesis.capacitor.brightcoveplayer.events.VideoPlayerProgressEvent import com.jnesis.capacitor.brightcoveplayer.exception.PluginException import com.jnesis.capacitor.brightcoveplayer.manager.CatalogManager import com.jnesis.capacitor.brightcoveplayer.model.VideoPlaybackState import com.jnesis.capacitor.brightcoveplayer.model.studentPopUpQuestions.PopUpQuestions import com.jnesis.capacitor.brightcoveplayer.utils.OnSwipeTouchListener import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import org.json.JSONArray import java.util.* import java.util.concurrent.TimeUnit class BrightcoveVideoPlayerFragment() : BrightcovePlayerFragment() { private lateinit var fragmentView: View private var unsubscribe: PublishSubject = PublishSubject.create() var videoId: String? = null var selectedSubtitle: String = "" lateinit var pluginCall: PluginCall; private var completedFired = false var myTimer: Timer? = null var doThis: TimerTask? = null var questionsTimer: Timer? = null var questionsTask: TimerTask? = null var questionsList: MutableList = mutableListOf() var currentQuestion: Int = 0 var eventEmitter: EventEmitter? = null var listOfQuestionTimeinSec = mutableListOf() lateinit var questionHint : RelativeLayout lateinit var questionsLayout : ConstraintLayout lateinit var questionText : TextView lateinit var answerResult : TextView lateinit var knewCorrectAnswer : TextView lateinit var submitAnswer : TextView lateinit var questionHintText : TextView lateinit var answersRecycler : RecyclerView lateinit var closeButton : ImageButton var isQuestionShown : Boolean = false var appLanguage : String = "en" constructor(pluginCall: PluginCall) : this() { this.pluginCall = pluginCall; } companion object { val DEFAULT_SEEK_TIME = TimeUnit.SECONDS.toMillis(15).toInt() } //private val FONT_AWESOME = "fontawesome-webfont.ttf" //This TTF font is included in the Brightcove SDK. override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { fragmentView = inflater.inflate(R.layout.fragment_brightcove_video_player, container, false) addTouchListenerOnLayout(fragmentView) baseVideoView = fragmentView.findViewById(R.id.brightcove_video_view) eventEmitter = baseVideoView.eventEmitter initViews() this.initMediaController() super.onCreateView(inflater, container, savedInstanceState) if (CatalogManager.brightcoveCatalog == null) { return fragmentView } this.videoId = pluginCall.getString("fileId")!! // val animatedText = pluginCall.getString("animatedText") // val phoneNumberAnimation = fragmentView.findViewById(R.id.txt_animation) // phoneNumberAnimation.text = animatedText // animateText(phoneNumberAnimation) retrieveQuestionsData() setupCuePoints() val callback = { video: Video?, _: Boolean -> try { playVideo(video) pluginCall.resolve(JSObject() .put("name", video?.name) .put("duration", video?.durationLong) ) } catch (t: Throwable) { pluginCall.reject(t.localizedMessage, PluginException.ErrorCode.TECHNICAL_ERROR.name , Exception(t)) } } try { if (pluginCall.getBoolean("local", false)!!) { CatalogManager.tryToLoadLocallyOrFetchRemotely(this.videoId!!, pluginCall, callback) } else { CatalogManager.fetchRemoteMedia(this.videoId!!, pluginCall, callback) } } catch (t: Throwable) { pluginCall.reject(t.localizedMessage, PluginException.ErrorCode.TECHNICAL_ERROR.name , Exception(t)) } return fragmentView } override fun onResume() { super.onResume() stopTimers() scheduleTimer(0) } private fun animateText(phoneNumberAnimation: TextView) { val display = activity?.windowManager?.defaultDisplay val size = Point() display?.getSize(size) val width: Int = size.x val height: Int = size.y var lastX = 0.0f var lastY = 0.0f myTimer = Timer() doThis = object : TimerTask() { override fun run() { var currentX = lastX var currentY = lastY val r = Random() val translationX = r.nextInt(width.toInt()) val translationY = r.nextInt(height.toInt()) var negX = translationX.toFloat() * -1 var negY = translationY.toFloat() * -1 var goToX = translationX.toFloat() var goToY = translationY.toFloat() if (lastX > 0) goToX = negX if (lastY > 0) goToY = negY val anim = TranslateAnimation(currentX, goToX, currentY, goToY) anim.duration = 10000 newThread(phoneNumberAnimation, anim) lastX = goToX lastY = goToY } } myTimer!!.scheduleAtFixedRate(doThis, 0, 10000) } private fun newThread(phoneNumberAnimation: TextView, anim: TranslateAnimation) { requireActivity().runOnUiThread(Runnable { phoneNumberAnimation.startAnimation(anim) }) } private fun initMediaController() { baseVideoView.setMediaController(BrightcoveMediaController(baseVideoView, R.layout.default_media_controller)) // Configure rewind and fast-forward buttons val properties: MutableMap = HashMap() properties[Event.SEEK_DEFAULT_LONG] = DEFAULT_SEEK_TIME properties[Event.SEEK_RELATIVE_ENABLED] = false baseVideoView.eventEmitter.emit(EventType.SEEK_CONTROLLER_CONFIGURATION, properties) // Fullscreen auto baseVideoView.eventEmitter.emit(EventType.ENTER_FULL_SCREEN) baseVideoView.eventEmitter.on(EventType.EXIT_FULL_SCREEN) { closeVideo(false) } // Close button baseVideoView.findViewById(R.id.button_close).apply { setOnClickListener { closeVideo(false) } visibility = View.INVISIBLE baseVideoView.eventEmitter.on(ShowHideController.DID_SHOW_MEDIA_CONTROLS) { visibility = View.VISIBLE } baseVideoView.eventEmitter.on(ShowHideController.DID_HIDE_MEDIA_CONTROLS) { visibility = View.INVISIBLE } } } private fun initViews(){ questionsLayout = fragmentView.findViewById(R.id.single_question_layout) questionText = fragmentView.findViewById(R.id.single_question) answerResult = fragmentView.findViewById(R.id.answer_result) knewCorrectAnswer = fragmentView.findViewById(R.id.answer_corrected) submitAnswer = fragmentView.findViewById(R.id.submit_answer) answersRecycler = fragmentView.findViewById(R.id.answers_list) answersRecycler.layoutManager = LinearLayoutManager(fragmentView.context) questionHint = fragmentView.findViewById(R.id.question_hint) closeButton = baseVideoView.findViewById(R.id.button_close) questionHintText = baseVideoView.findViewById(R.id.question_hint_text) } fun scheduleTimer(delay : Long){ questionsTimer = Timer() questionsTask = object : TimerTask() { override fun run() { if (listOfQuestionTimeinSec.isNotEmpty() && baseVideoView.currentPosition != 0) { if(questionHint.visibility == View.VISIBLE && baseVideoView.currentPosition.div(1000) >= 4) { Handler(Looper.getMainLooper()).post { questionHint.visibility = View.GONE } } listOfQuestionTimeinSec.forEachIndexed { index, _ -> if(baseVideoView.currentPosition.div(1000) == listOfQuestionTimeinSec[index]) { currentQuestion = index showPopUpQuestion(index) return@forEachIndexed } // when(baseVideoView.currentPosition.div(1000)) // { // //Range of video position +- 1 seconds // in listOfQuestionTimeinSec[index]..listOfQuestionTimeinSec[index]-> { // currentQuestion = index // showPopUpQuestion(index); // return@forEachIndexed // } // } } } } } questionsTimer!!.scheduleAtFixedRate(questionsTask, delay, 1000) } private fun retrieveQuestionsData() { val preference = context?.getSharedPreferences("com.appenza.VOD_NativeStorage", MODE_PRIVATE) val PREFS_NAME = preference?.getString("NativeStorageSharedPreferencesName", "NativeStorage") val questions = context?.getSharedPreferences(PREFS_NAME, MODE_PRIVATE) ?.getString(videoId, "") appLanguage = context?.getSharedPreferences(PREFS_NAME, MODE_PRIVATE) ?.getString("appLanguage", "").toString() if (appLanguage.isNotEmpty()) appLanguage = appLanguage.substring(1, appLanguage.length - 1) // changeLanguage(appLanguage.replace("^\"|\"$", "")) if (!questions.isNullOrEmpty() && questions != "[]") { if(appLanguage == "ar"){ changeLanguage("ar") } else{ questionHint.gravity = Gravity.START } questionHintText.text = if(appLanguage == "en") getString(R.string.questions_available_hint) else getString(R.string.questions_available_hint_ar) questionHint.visibility = View.VISIBLE val jsonArray = JSONArray(questions) for (i in 0 until jsonArray.length()) { var question = Gson().fromJson(jsonArray[i].toString(), PopUpQuestions::class.java) questionsList.add(question) listOfQuestionTimeinSec.add(question.minutes?.times(60)?.plus(question.seconds!!)!!) } for(i in 0 until questionsList.size){ questionsList[i].questionBankAnswers?.forEach { it.popupQuestionId = questionsList[i].popupQuestionId } } scheduleTimer(0) } } fun changeLanguage(language: String) { if(Locale.getDefault().language != language){ val locale = Locale(language) Locale.setDefault(locale) val config = resources.configuration config.setLocale(locale) config.setLayoutDirection(locale) activity?.createConfigurationContext(config) onConfigurationChanged(config) } } private fun showPopUpQuestion(index: Int) { isQuestionShown = true Handler(Looper.getMainLooper()).post { showHideControllers() baseVideoView.pause() setSingleCorrectQuestion(questionsList[index]) questionsTimer?.cancel() } } private fun setSingleCorrectQuestion(popUpQuestions: PopUpQuestions) { questionText.text = popUpQuestions.questionBankBody if(popUpQuestions.questionBankType != 1){ val adapter = popUpQuestions.questionBankAnswers?.let { AnswersAdapter(it, answerChecked = false ) } answersRecycler.adapter = adapter adapter?.onItemClick = { item -> popUpQuestions.questionBankAnswers?.let { checkCorrectAnswer(it.indexOf(item)) adapter?.onItemClick = null answersRecycler.isClickable = false } } } else { var adapter = popUpQuestions.questionBankAnswers?.let { MultipleAnswersAdapter(it, answerChecked = false ) } answersRecycler.adapter = adapter adapter?.onItemClick = { item -> popUpQuestions.questionBankAnswers?.let { // adapter?.multipleSelectedAnswers?.forEach { it1 -> checkCorrectAnswer(it1) } // adapter?.onItemClick = null // answersRecycler.isClickable = false } } submitAnswer.visibility = View.VISIBLE submitAnswer.text = if(appLanguage == "en") getString(R.string.confirm_answer) else getString(R.string.confirm_answer_ar) submitAnswer.setOnClickListener { var answersCorrect : Boolean = false; for(item in adapter?.multipleSelectedAnswers!!){ if(questionsList[currentQuestion].questionBankAnswers?.get(item)?.isCorrect == true && questionsList[currentQuestion].questionBankAnswers?.filter { popUpQuestionAnswers -> popUpQuestionAnswers.isCorrect == true }?.size == adapter?.multipleSelectedAnswers?.size ){ answersCorrect = true; } else{ answersCorrect = false break; } } adapter = questionsList[currentQuestion].questionBankAnswers?.let { MultipleAnswersAdapter(it, answerChecked = true ) } answersRecycler.adapter = adapter if(answersCorrect) correctAnswer() else wrongAnswer() knewAnswer() adapter?.onItemClick = null answersRecycler.isClickable = false submitAnswer.visibility = View.GONE } } answerResult.visibility = View.GONE knewCorrectAnswer.visibility = View.GONE answersRecycler.visibility = View.VISIBLE questionsLayout.visibility = View.VISIBLE if(popUpQuestions.questionBankType == 1){ // submitAnswer.visibility = View.VISIBLE // submitAnswer.setOnClickListener { //// adapter?.multipleSelectedAnswers?.let { it1 -> checkCorrectAnswer(it1.first()) } // adapter?.multipleSelectedAnswers?.forEach { it1 -> checkCorrectAnswer(it1) } // adapter?.onItemClick = null // answersRecycler.isClickable = false // submitAnswer.visibility = View.GONE // } } } fun checkCorrectAnswer(checkedStudentAnswer : Int){ val adapter = questionsList[currentQuestion].questionBankAnswers?.let { AnswersAdapter(it, answerChecked = true ) } answersRecycler.adapter = adapter if(questionsList[currentQuestion].questionBankAnswers!![checkedStudentAnswer].isCorrect!!) { correctAnswer() } else{ wrongAnswer() } knewAnswer() saveAnswerData(checkedStudentAnswer) } private fun correctAnswer(){ answerResult.text = if(appLanguage == "en") getString(R.string.correct_answer) else getString(R.string.correct_answer_ar) answerResult.setTextColor(Color.parseColor("#21C503")) answerResult.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.right,0) knewCorrectAnswer.text = if(appLanguage == "en") getString(R.string.continue_questions) else getString(R.string.continue_questions_ar) } private fun wrongAnswer(){ answerResult.text = if(appLanguage == "en") getString(R.string.wrong_answer) else getString(R.string.wrong_answer_ar) answerResult.setTextColor(Color.parseColor("#F9671B")) answerResult.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.wrong,0) knewCorrectAnswer.text = if(appLanguage == "en") getString(R.string.knew_correct_answer) else getString(R.string.knew_correct_answer_ar) answerResult.visibility = View.VISIBLE knewCorrectAnswer.visibility = View.VISIBLE } private fun knewAnswer(){ answerResult.visibility = View.VISIBLE knewCorrectAnswer.visibility = View.VISIBLE knewCorrectAnswer.setOnClickListener { questionsLayout.visibility = View.GONE baseVideoView.start() scheduleTimer(5000) isQuestionShown = false showHideControllers() } } private fun saveAnswerData(checkedStudentAnswer: Int) { val preference = context?.getSharedPreferences("com.appenza.VOD_NativeStorage", MODE_PRIVATE) val PREFS_NAME = preference?.getString("NativeStorageSharedPreferencesName", "NativeStorage") val sharedPref = activity?.getSharedPreferences("com.appenza.VOD_NativeStorage", MODE_PRIVATE) ?: return with (sharedPref.edit()) { putString("studentPopUpAnswers", Gson().toJson(questionsList[currentQuestion].questionBankAnswers?.get(checkedStudentAnswer)) ) apply() } } private fun showHideControllers(){ baseVideoView.brightcoveMediaController.brightcoveSeekBar.isEnabled = !isQuestionShown baseVideoView.brightcoveMediaController.brightcoveSeekBar.isClickable = !isQuestionShown baseVideoView.brightcoveMediaController.brightcoveSeekBar.isActivated = !isQuestionShown baseVideoView.brightcoveMediaController.brightcoveControlBar.isEnabled = !isQuestionShown baseVideoView.brightcoveMediaController.brightcoveControlBar.isClickable = !isQuestionShown baseVideoView.brightcoveMediaController.brightcoveControlBar.isActivated = !isQuestionShown baseVideoView.brightcoveMediaController.mediaControlRegistry.buttonControllers.forEach { it.button.isClickable = !isQuestionShown it.button.isActivated = !isQuestionShown it.button.isEnabled = !isQuestionShown } // closeButton.apply { // visibility = if(isQuestionShown) View.INVISIBLE else View.VISIBLE // } } private fun setupCuePoints() { val properties: Map = HashMap() val details: MutableMap = HashMap() if(listOfQuestionTimeinSec.isNotEmpty()) { listOfQuestionTimeinSec.forEachIndexed { index, element -> run { var cuePoint = CuePoint( listOfQuestionTimeinSec[index].times(1000).minus(1000), CuePoint.CuePointType.CODE, properties ) details[Event.CUE_POINT] = cuePoint eventEmitter?.emit(EventType.CUE_POINT, details) cuePoint = CuePoint( listOfQuestionTimeinSec[index].times(1000).plus(1000), CuePoint.CuePointType.CODE, properties ) details[Event.CUE_POINT] = cuePoint eventEmitter?.emit(EventType.SET_CUE_POINT, details) } return@forEachIndexed } } } private fun stopTimers() { questionsTimer?.cancel() myTimer?.cancel() } private fun onDestroyedEvents() { baseVideoView.eventEmitter.on(EventType.FRAGMENT_DESTROYED) { stopTimers(); } baseVideoView.eventEmitter.on(EventType.FRAGMENT_STOPPED) { stopTimers(); } baseVideoView.eventEmitter.on(EventType.FRAGMENT_DETACHED) { stopTimers(); } baseVideoView.eventEmitter.on(EventType.FRAGMENT_DESTROYED_VIEW) { stopTimers(); } } private fun playVideo(video: Video?) { val subtitle = pluginCall.getString("subtitle","")!! if(subtitle.isNotEmpty()) { baseVideoView.eventEmitter.once(EventType.CAPTIONS_LANGUAGES) { baseVideoView.setClosedCaptioningEnabled(true) baseVideoView.setSubtitleLocale(subtitle) this.selectedSubtitle = subtitle } } baseVideoView.add(video) baseVideoView.start() baseVideoView.eventEmitter.on(EventType.VIDEO_DURATION_CHANGED) { val duration = video?.durationLong val ret = JSObject() ret.put("name", video?.name) ret.put("duration", duration) val position = pluginCall.getInt("position", -1)!! if (position in 1 until duration!!) { baseVideoView.seekTo(position.toLong()) } pluginCall.resolve(ret) } baseVideoView.eventEmitter.on(EventType.PLAY) { initPositionTimer() } onDestroyedEvents(); baseVideoView.eventEmitter.on(EventType.COMPLETED) { //closeVideo(true) completedFired = true } baseVideoView.eventEmitter.on(EventType.SELECT_CLOSED_CAPTION_TRACK) { e -> val captionFormat = e.properties[Event.CAPTION_FORMAT] as BrightcoveCaptionFormat? if (captionFormat != null) { this.selectedSubtitle = captionFormat.language() as String } } baseVideoView.eventEmitter.on(EventType.TOGGLE_CLOSED_CAPTIONS) { e -> val captionsToggledOn = e.properties[Event.BOOLEAN] as Boolean if (!captionsToggledOn) { this.selectedSubtitle = "" } } baseVideoView.eventEmitter.once(EventType.CAPTIONS_LANGUAGES) { baseVideoView.setClosedCaptioningEnabled(true) val defaultSubtitleLanguage = CatalogManager.defaultSubtitleLanguage if (defaultSubtitleLanguage != null) { selectCaption(baseVideoView.currentVideo, defaultSubtitleLanguage) } } } private fun selectCaption(video: Video, language: String) { val pair: Pair? = getCaptionsForLanguageCode(video, language) if (pair != null && !pair.first.equals(Uri.EMPTY)) { // BrightcoveCaptionFormat.BRIGHTCOVE_SCHEME indicates that is not a URL we need to load with the LoadCaptionsService, but instead we'll be enabled through a different component. if (!pair.first.toString().startsWith(BrightcoveCaptionFormat.BRIGHTCOVE_SCHEME)) { baseVideoView.closedCaptioningController.loadCaptionsService.loadCaptions( pair.first, pair.second.type() ) } val properties: MutableMap = HashMap() properties[Event.CAPTION_FORMAT] = pair.second properties[Event.CAPTION_URI] = pair.first baseVideoView.eventEmitter.emit(EventType.SELECT_CLOSED_CAPTION_TRACK, properties) } } private fun getCaptionsForLanguageCode( video: Video?, languageCode: String ): Pair? { val payload = video?.properties?.get(Video.Fields.CAPTION_SOURCES) if (payload is List<*>) { @Suppress("UNCHECKED_CAST") val pairs = payload as List> for (pair in pairs) { if (pair.second.language().equals(languageCode)) { return pair } } } return null } private fun initPositionTimer() { Observable .interval(1000, TimeUnit.MILLISECONDS) .takeUntil(unsubscribe) .filter { baseVideoView.isPlaying } .subscribe { emitPositionEvent() } } private fun emitPositionEvent() { EventBus.publish( VideoPlayerProgressEvent( JSObject(Gson().toJson( VideoPlaybackState(baseVideoView.currentPositionLong, baseVideoView.durationLong)) ) ) ) } fun closeVideo(completed: Boolean) { questionsTimer?.cancel() myTimer?.cancel() this.videoId = null var position = baseVideoView.currentPositionLong val duration = baseVideoView.duration val completedVl = position >= duration - 5000 || completedFired if (completedFired) { position = duration.toLong() } val subtitle = this.selectedSubtitle emitPositionEvent() unsubscribe.onNext(Any()) unsubscribe.onComplete() EventBus.publish(VideoPlayerBeforeCloseEvent()) requireActivity().supportFragmentManager.popBackStack() requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT EventBus.publish(VideoPlayerCloseEvent(JSObject(Gson().toJson(mapOf("completed" to completedVl, "currentMillis" to position, "subtitle" to subtitle))))) } private fun addTouchListenerOnLayout(fragmentView: View) { val fragmentLayout = fragmentView.findViewById(R.id.player_fragment_layout) fragmentLayout.setOnTouchListener(object : OnSwipeTouchListener(requireActivity().applicationContext) { override fun onSwipeDown() { closeVideo(false) } }) } }