code

code


package com.funday.musicbox

import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.Dialog
import android.content.Context
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.net.Uri
import android.os.*
import android.text.method.ScrollingMovementMethod
import android.util.DisplayMetrics
import android.util.Log
import android.util.SparseArray
import android.view.*
import android.widget.*
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.widget.PopupWindowCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import at.huber.youtubeExtractor.VideoMeta
import at.huber.youtubeExtractor.YouTubeExtractor
import at.huber.youtubeExtractor.YtFile
import com.android.volley.Request
import com.android.volley.Response
import com.android.volley.toolbox.Volley
import com.bumptech.glide.Glide
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.DefaultRenderersFactory
import com.google.android.exoplayer2.ExoPlayerFactory
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.liulishuo.filedownloader.BaseDownloadTask
import com.liulishuo.filedownloader.FileDownloadListener
import com.liulishuo.filedownloader.FileDownloader
import kotlinx.android.synthetic.main.new_ytplayer_activity.*
import kotlinx.android.synthetic.main.popup_view.*
import kotlinx.android.synthetic.main.translate_dialog.*
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.internal.wait
import org.jetbrains.anko.image
import org.jetbrains.anko.toast
import org.json.JSONArray
import org.json.JSONObject
import org.jsoup.Jsoup
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
import kotlin.concurrent.timerTask
import kotlin.math.abs

class NewYTPlayerActivity : AppCompatActivity() {
    var customerID = 411
    var memberID = 69231
    var recordPlayer: MediaPlayer? = null
    var testBool = true
    var isRepeat = false
    var isPlayed = true
    var isRecording = false
    var isMute = false
    var hasCloudFile = false
    var cloudFileUrl = ""
    var playingSentence = 0
    var thisSentence = 0
    var playingTime = 0
    var pauseTime = 0L
    var favoriteSong = false
    var favoriteWord = false
    var playButtonStatus = true//false: pause true: play
    var muteButtonStatus = 0//0: mute voice 1: mute YT 2: normal
    val array = ArrayList<DataVo>()
    private var timer = object : Timer("timer") {}
    private var progressTimer = object : Timer("progressTimer") {}
    private val sdf = SimpleDateFormat("m:ss")
    private var strSong: String? = ""
    private var strSinger: String? = ""
    private var strSongID: String? = ""
    private var APP_VIDEO_ID: String = ""
    private lateinit var player: SimpleExoPlayer
    private lateinit var path: String
    private lateinit var recordDir: String
    private lateinit var mp3Name: String
    private lateinit var fileMusic: File
    private lateinit var linearManager: LinearLayoutManager
    private var mediaRecorder = object : MediaRecorder() {}

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.new_ytplayer_activity)

        //bundle from main page
        val bundle = intent.extras
        strSong = bundle?.getString("song")
        strSinger = bundle?.getString("singer")
        strSongID = bundle?.getString("index")

        setViews()
        setValues()
        getLyrics() //yt start here
        startTimers()
        setListeners()
    }

    public override fun onStart() {
        super.onStart()
        if (ContextCompat.checkSelfPermission(
                this@NewYTPlayerActivity,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            ) != PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(
                this@NewYTPlayerActivity,
                Manifest.permission.RECORD_AUDIO
            ) != PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(
                this@NewYTPlayerActivity,
                Manifest.permission.READ_EXTERNAL_STORAGE
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this@NewYTPlayerActivity,
                arrayOf(
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.RECORD_AUDIO
                ),
                0
            )
        }
        if (testBool) initializePlayer() else restartPlayer()
    }

    public override fun onPause() {
        super.onPause()
    }

    public override fun onStop() {
        super.onStop()
        Log.d("TAG", "onStop")
        pauseTime = player.currentPosition
    }

    override fun onRestart() {
        super.onRestart()
        Log.d("TAG", "onRestart")
//        player = ExoPlayerFactory.newSimpleInstance(
//            this, DefaultRenderersFactory(this),
//            DefaultTrackSelector(), DefaultLoadControl()
//        )
//
//        yt_player.player = player
testBool = false
//        extractYoutubeUrl("https://www.youtube.com/watch?v=$APP_VIDEO_ID")
//        player.playWhenReady = true
//        player.seekTo(playingTime.toLong())
    }

    override fun onDestroy() {
        super.onDestroy()
        closeTimers()
        releasePlayer()
    }

    //todo testing use
    fun buttonTest(v: View) {
    }

    private fun setViews() {//change to high quality pic
        val glide = Glide.with(this@NewYTPlayerActivity)
        glide.load(resources.getDrawable(R.drawable.tutorial, null)).into(btn_tutorial)
        glide.load(resources.getDrawable(R.drawable.back_new, null)).into(btn_back)
        glide.load(resources.getDrawable(R.drawable.star_new, null)).into(btn_favorite)
//        glide.load(resources.getDrawable(R.drawable.play, null)).into(btn_control_play)
//        glide.load(resources.getDrawable(R.drawable.next, null)).into(btn_next)
//        glide.load(resources.getDrawable(R.drawable.previous, null)).into(btn_previous)
//        glide.load(resources.getDrawable(R.drawable.cloud, null)).into(btn_cloud)
//        glide.load(resources.getDrawable(R.drawable.unmute_hq, null)).into(btn_mute)
    }

    private fun setValues() {
        linearManager = LinearLayoutManager(this@NewYTPlayerActivity)
        recyclerView.layoutManager = linearManager

        FileDownloader.setup(this)//init file downloader

        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT//lock orientation

        mp3Name = "$customerID-$memberID-$strSongID"
        recordDir = "${Environment.getDataDirectory().absolutePath}/data/${this.packageName}/record"

        if (!checkDirExists()) {
            File(recordDir).mkdir()
        }
        checkFileExist(recordDir)
    }

    private fun getLyrics() {
        val requestQueue = Volley.newRequestQueue(this@NewYTPlayerActivity)//get lyrics
        val stringRequest = MyStringRequest(
            Request.Method.GET,
            "https://funday.asia/musicbox/SRT.asp?indx=$strSongID",
            Response.Listener { response ->
                val doc = Jsoup.parse(response)
                APP_VIDEO_ID = doc.select("lrc").attr("Yurl")

                // Play youtube video
                extractYoutubeUrl("https://www.youtube.com/watch?v=$APP_VIDEO_ID")
                tv_lyric_title.text = strSong//set title
                //todo add singer's name and song name at first
                /**time: 0
                 * enText: singer name
                 * chText: song name
                 * */
                array.add(DataVo("0", strSinger.orEmpty(), strSong.orEmpty(), false))
                doc.select("lrclist").forEach { element ->
                    array.add(
                        DataVo(
                            formatStringToInt(element.attr("lrctime")).toString(),
                            element.attr("en_content").checkAndReplace(),
                            element.attr("ch_content"), false
                        )
                    )
                }
                recyclerView.layoutManager = LinearLayoutManager(this@NewYTPlayerActivity)
                array.add(DataVo("", "", "", false))//for last two null sentence
                array.add(DataVo("", "", "", false))
                recyclerView.adapter = RecyclerAdapter(this@NewYTPlayerActivity, array)
            },
            Response.ErrorListener {

            })
        requestQueue.add(stringRequest)
    }

    private fun setListeners() {
        recyclerView.addOnScrollListener(
            object : RecyclerView.OnScrollListener() {
                override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                    super.onScrolled(recyclerView, dx, dy)
                    Log.d("TAG", "onScrolled")
                }

                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                    super.onScrollStateChanged(recyclerView, newState)
                    Log.d("TAG", "onScrollStateChanged")
                }
            }
        )

        btn_video_control.setOnClickListener {
            isPlayed = !isPlayed
            if (isPlayed) {//video pause
                pauseVideo()
            } else {//video play
                playVideo()
            }
        }

        seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {
                val seekNumber = seekBar!!.progress.toLong()
                player.seekTo(seekNumber)
                for (i in 1 until array.size - 2) {// avoid last 2 array is null
                    try {
                        if (array[i + 1].timeText.toInt() > seekNumber) {//next sentence start time
                            if (playingSentence > i) {//down to up
                                runOnUiThread {
                                    linearManager.scrollToPosition(i - 2)
                                    recyclerView.layoutManager?.scrollToPosition(i - 2)
                                }
                                playingSentence =
                                    i // playing sentence > i < next sentence start time
                            } else {//up to down
                                playingSentence =
                                    i // playing sentence > i < next sentence start time
                                lockOnSentence()
                            }
                            break
                        }
                    } catch (e: NumberFormatException) {
                        playingSentence = array.size - 2 //last sentence
                    }
                }
            }
        })

        btn_repeat.setOnClickListener {
            isRepeat = !isRepeat
            if (isRepeat) {
                thisSentence = playingSentence
                btn_repeat.setCompoundDrawablesWithIntrinsicBounds(
                    null,
                    ContextCompat.getDrawable(this, R.drawable.repeat_org),
                    null,
                    null
                )
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    btn_repeat.setTextColor(resources.getColor(R.color.icon_org, null))
                    btn_repeat.background =
                        resources.getDrawable(R.drawable.repeat_button_prassed, null)
                }
            } else {
                btn_repeat.setCompoundDrawablesWithIntrinsicBounds(
                    null,
                    ContextCompat.getDrawable(this, R.drawable.repeat_wh),
                    null,
                    null
                )
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    btn_repeat.setTextColor(resources.getColor(R.color.progressBarWhite, null))
                    btn_repeat.background =
                        resources.getDrawable(R.drawable.repeat_button_normal, null)
                }
            }
        }

        btn_next.setOnClickListener {
            //Next sentence
            if (array[playingSentence + 1].timeText.trim().isNotEmpty()) {//avoid user next to null sentence
                array[playingSentence].isPlaying = false
                array[playingSentence + 1].isPlaying = true
                player.seekTo(array[playingSentence + 1].timeText.toLong())
                playingSentence++
                lockOnSentence()
            }
        }

        btn_previous.setOnClickListener {
            //Previous sentence
            try {//avoid user back to -1 sentence
                array[playingSentence].isPlaying = false
                array[playingSentence - 1].isPlaying = true
                player.seekTo(array[playingSentence - 1].timeText.toLong())
                playingSentence--
                lockOnSentence()
            } catch (e: ArrayIndexOutOfBoundsException) {
            }
        }

        btn_back.setOnClickListener {
            closeTimers()
            finish()
        }

        btn_favorite.setOnClickListener {
            favoriteSong = !favoriteSong
            changeBtnStatus()
            val requestQueue = Volley.newRequestQueue(this@NewYTPlayerActivity)
            val stringRequest = MyStringRequest(
                Request.Method.GET,
                "https://funday.asia/NewMylessonmobile/${if (favoriteSong) "C" else "D"}/api/musicbox/join?member_id=$memberID&customer_id=$customerID&musicbox_id=$strSongID",
                Response.Listener {
                },
                Response.ErrorListener {
                })
            requestQueue.add(stringRequest)
        }

        btn_control_play.setOnClickListener {
            //play record and video
            playButtonStatus = !playButtonStatus
            if (playButtonStatus) {
                "暫停".toast(this@NewYTPlayerActivity)
                pauseVideo()
                recordPlayer?.pause()
            } else {
                "播放錄音+影片".toast(this@NewYTPlayerActivity)
                playVideo()
                recordPlayer = MediaPlayer.create(
                    this,
                    Uri.parse("${Environment.getDataDirectory().absolutePath}/data/${this.packageName}/record/$mp3Name.mp3")
                )
                playingSentence = 1
                player.seekTo(0)
                recordPlayer?.setVolume(1F, 1F)
                recordPlayer?.setOnPreparedListener { }
                recordPlayer?.start()
            }
        }

        btn_record.setOnClickListener {
            if (player.currentPosition == 0L) {
                "影片還沒開始播".toast(this@NewYTPlayerActivity)
                return@setOnClickListener
            }
            val dir = getRecordDir() ?: return@setOnClickListener
            val name = "$mp3Name.mp3"
            path = File(dir, name).path
            pauseVideo()
            if (recordAudio(path)) {//start record
                record_control_layout.visibility = View.GONE
            }

            //countdown in 4sec to start recording
            val dialog = Dialog(this@NewYTPlayerActivity, R.style.DialogTheme)
            dialog.setContentView(R.layout.countdown_dialog)
            dialog.setCancelable(false)
            dialog.window!!.setBackgroundDrawable(object : ColorDrawable(Color.TRANSPARENT) {})
            object : CountDownTimer(4000, 1000) {
                override fun onFinish() {
                    isRecording = true
                    dialog.dismiss()//dialog dismiss first
                    mediaRecorder.start()
                    player.seekTo(0)//avoid player seek to 0 second and won't play the video
                    playVideo()
                    btn_block.visibility = View.VISIBLE// block user when recording
                }

                override fun onTick(millisUntilFinished: Long) {
                }
            }.start()
            dialog.show()
        }

        btn_stop_recording.setOnClickListener {
            if (mediaRecorder != null) {
                mediaRecorder.stop()
                "停止錄音".toast(this@NewYTPlayerActivity)
                isRecording = false
                pauseVideo()
                player.seekTo(0L)
                record_control_layout.visibility = View.VISIBLE
                btn_record.visibility = View.VISIBLE
                btn_repeat.visibility = View.VISIBLE
                btn_stop_recording.visibility = View.GONE
                btn_block.visibility = View.GONE
            }
        }

        btn_mute.setOnClickListener {
            isMute = !isMute
            when (muteButtonStatus) {//0: mute voice 1: mute YT 2: normal
                0 -> {
                    "錄音 靜音".toast(this@NewYTPlayerActivity)
                    btn_mute.image =
                        ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.mutevoice)
                    recordPlayer?.setVolume(0F, 0F)
                    muteButtonStatus = 1
                }
                1 -> {
                    "影片 靜音".toast(this@NewYTPlayerActivity)
                    btn_mute.image =
                        ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.muteyoutube)
                    player.volume = 0F
                    recordPlayer?.setVolume(1F, 1F)
                    muteButtonStatus = 2
                }
                2 -> {
                    "正常音".toast(this@NewYTPlayerActivity)
                    btn_mute.image =
                        ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.unmute)
                    recordPlayer?.setVolume(1F, 1F)
                    player.volume = 1F
                    muteButtonStatus = 0
                }
            }
        }

        btn_cloud.setOnClickListener {
            val popupWindow = PopupWindow(this)
            val view = LayoutInflater.from(this).inflate(R.layout.popup_view, null)
            popupWindow.contentView = view
            popupWindow.width = ViewGroup.LayoutParams.WRAP_CONTENT
            popupWindow.height = ViewGroup.LayoutParams.WRAP_CONTENT
            popupWindow.isOutsideTouchable = true//touch outside to dismiss popupwindow
            popupWindow.isFocusable = true//isOutsideTouchable seem's no work
            popupWindow.background.alpha = 0 //popup window invisible
            val btnDownload = view.findViewById<ImageButton>(R.id.btn_download)
            val btnUpload = view.findViewById<ImageButton>(R.id.btn_upload)

            val dialog = object : Dialog(this@NewYTPlayerActivity) {}//setting progress dialog
            dialog.setContentView(R.layout.download_dialog)
            dialog.setCancelable(false)
            dialog.window!!.setBackgroundDrawable(object :
                ColorDrawable(Color.TRANSPARENT) {})

            val progressBar = dialog.findViewById<ProgressBar>(R.id.download_progressbar)
            val tvPercent = dialog.findViewById<TextView>(R.id.tv_percent)

            btnDownload.setOnClickListener {
                checkFileExist()
                if (hasCloudFile) {
                    dialog.show()
                    popupWindow.dismiss()

                    FileDownloader.getImpl().create(cloudFileUrl).setPath("$recordDir/$mp3Name.mp3")
                        .setForceReDownload(true).setListener(object : FileDownloadListener() {
                            override fun warn(task: BaseDownloadTask?) {
                                Log.d("TAG", "warn")
                            }

                            override fun completed(task: BaseDownloadTask?) {
                                dialog.dismiss()

                                player.seekTo(0)
                            }

                            override fun pending(
                                task: BaseDownloadTask?,
                                soFarBytes: Int,
                                totalBytes: Int
                            ) {
                                Log.d("TAG", "pending")
                            }

                            override fun error(task: BaseDownloadTask?, error: Throwable?) {
                                error?.printStackTrace()
                            }

                            @SuppressLint("SetTextI18n")
                            override fun progress(
                                task: BaseDownloadTask?,
                                soFarBytes: Int,
                                totalBytes: Int
                            ) {
                                progressBar.progress =
                                    getProgress(soFarBytes.toLong(), totalBytes.toLong())
                                tvPercent.text =
                                    "${getProgress(soFarBytes.toLong(), totalBytes.toLong())}%"
                            }

                            override fun paused(
                                task: BaseDownloadTask?,
                                soFarBytes: Int,
                                totalBytes: Int
                            ) {
                                Log.d("TAG", "paused")
                            }

                        }).start()
                } else {
                    "雲端無檔案".toast(this@NewYTPlayerActivity)
                    popupWindow.dismiss()
                }
            }

            btnUpload.setOnClickListener {
                if (!fileMusic.exists()) {
                    resources.getString(R.string.no_local_file).toast(this@NewYTPlayerActivity)
                    popupWindow.dismiss()
                    return@setOnClickListener
                }
                val pic = dialog.findViewById<ImageView>(R.id.imageView)
                val tvUpload = dialog.findViewById<TextView>(R.id.textView2)
                pic.image =
                    ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.icon_upload)
                tvUpload.text = resources.getString(R.string.uploading)
                popupWindow.dismiss()
                val alertDialog = AlertDialog.Builder(this@NewYTPlayerActivity)
                alertDialog.setTitle(resources.getString(R.string.upload_warning_title))
                    .setMessage(resources.getString(R.string.upload_warning))
                    .setPositiveButton(resources.getString(R.string.commit)) { _, _ ->
                        dialog.show()
                        val requestBody = MultipartBody.Builder().setType(MultipartBody.FORM)
                            .addFormDataPart(
                                "mp3", "$mp3Name.mp3",
                                fileMusic.asRequestBody("application/octet-stream".toMediaTypeOrNull())
                            )
                            .addFormDataPart("customer_id", customerID.toString())
                            .addFormDataPart("member_id", memberID.toString())
                            .addFormDataPart("musicbox_id", strSongID!!)
                            .build()

                        val progressListener = object : CountingRequestBody.Listener {
                            @SuppressLint("SetTextI18n")
                            override fun onRequestProgress(
                                bytesWritten: Long,
                                contentLength: Long
                            ) {
                                runOnUiThread {
                                    progressBar.progress = getProgress(bytesWritten, contentLength)
                                    tvPercent.text = "${getProgress(bytesWritten, contentLength)}%"
                                }
                            }
                        }

                        val client = OkHttpClient.Builder()
                            .addNetworkInterceptor(object : Interceptor {
                                @Throws(IOException::class)
                                override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
                                    val originalRequest = chain.request()
                                    if (originalRequest.body == null) {
                                        return chain.proceed(originalRequest)
                                    }
                                    val progressRequest = originalRequest.newBuilder()
                                        .method(
                                            originalRequest.method,
                                            CountingRequestBody(
                                                originalRequest.body!!,
                                                progressListener
                                            )
                                        )
                                        .build()
                                    return chain.proceed(progressRequest)
                                }
                            })
                            .build()

                        val request = okhttp3.Request.Builder()
                            .url("https://funday.asia/newmylessonmobile/api/FunKTVUpload")
                            .method("POST", requestBody)
                            .build()

                        client.newCall(request).enqueue(object : Callback {
                            override fun onFailure(call: Call, e: IOException) {
                                Log.d("TAG", "失敗")
                            }

                            override fun onResponse(call: Call, response: okhttp3.Response) {
                                Log.d("TAG", "成功: $response")
                                dialog.dismiss()
                            }

                        })
                    }
                    .setNegativeButton(resources.getString(R.string.cancel), null)
                    .show()
            }

            val offX = -abs((popupWindow.contentView.measuredWidth - btn_cloud.width) * 1.6).toInt()
            val offY = -(btn_cloud.height * 2.4).toInt()
            PopupWindowCompat.showAsDropDown(popupWindow, btn_cloud, offX, offY, Gravity.START)
        }
    }

    private fun playVideo() {
        player.playWhenReady = true//play yt
        yt_player_img.image =
            ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.ytplay)
        btn_control_play.image =
            ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.recordpause)
        ytViewAnimate()
//        startTimers()
    }

    private fun pauseVideo() {
        player.playWhenReady = false//pause yt
        yt_player_img.image =
            ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.ytpause)
        btn_control_play.image =
            ContextCompat.getDrawable(this@NewYTPlayerActivity, R.drawable.recordplay)
//        progressTimer.cancel()
//        timer.cancel()
        ytViewAnimate()
    }

    private fun ytViewAnimate() {
        yt_player_img.alpha = 1F//view visible
        yt_player_img.animate().alpha(0F).setDuration(1000).start()//animate to view invisible
    }

    private fun startTimers() {
        progressTimer.schedule(timerTask {
            try {
                playingTime = player.currentPosition.toInt()
                for (element in 0 until array.size - 2) {
                    if (playingTime < array[element + 1].timeText.toTime()) {
                        //element is my playing sentence
                        break
                    }
                }
            } catch (e: Exception) {
            }
        }, 0, 1)

        timer.schedule(
            timerTask {
                try {
                    lockOnSentence()
                    runOnUiThread {
                        seekBar.max = player.duration.toInt()//init seekbar's max value
                        seekBar.progress = playingTime
                        try {
                            tv_end_time.text = sdf.format(player.duration.toInt())
                            tv_playing_time.text =
                                sdf.format(playingTime)
                        } catch (e: IllegalStateException) {
                            Log.d("TAG", "死掉啦")
                        }
                    }
                    if (playingTime > player.duration - 1000 && isRepeat) {//repeat the last sentence
                        player.seekTo((array[thisSentence].timeText).toLong())
                    }
                    if (playingTime > 0 && array[playingSentence + 1].timeText.trim().isNotEmpty()) {//start playing
                        if (playingTime > array[playingSentence + 1].timeText.toInt() && !isRepeat) {//jump to next sentence
                            array[playingSentence].isPlaying = false
                            playingSentence++
                            array[playingSentence].isPlaying = true
                            refreshView()
                        } else {//no jump
                            if (playingSentence == 0) {//default first sentence
                                array[0].isPlaying = true
                                refreshView()
                            }
                        }

                        if (isRepeat) {
                            if (playingTime > Integer.parseInt(array[thisSentence + 1].timeText)) {//todo repeat end time
                                player.seekTo((array[thisSentence].timeText).toLong())//todo repeat start time
                            }
                        }
                        if (isRecording) {
                            runOnUiThread {
                                btn_stop_recording.text =
                                    "● ${sdf.format(playingTime)}"
                            }
                        }
                    }
                } catch (e: Exception) {
                    Thread.currentThread().interrupt()
                }
            }, 0, 1
        )
    }

    private fun checkFileExist(path: String) {
        /**upload file if exists
         * */
        val requestQueue = Volley.newRequestQueue(this@NewYTPlayerActivity)
        val stringRequest = MyStringRequest(
            Request.Method.GET,
            "https://funday.asia/NewMylessonmobile/api/MyMusicbox?member_id=$memberID&customer_id=$customerID&musicbox_id=$strSongID",
            Response.Listener { response ->
                favoriteSong = JSONObject(response).optInt("favorite") == 1
                hasCloudFile = JSONObject(response).optString("filename").isNotEmpty()
                cloudFileUrl = JSONObject(response).optString("filename")
                /**
                 * check local mp3 is exists
                 * */
                fileMusic = File("$path/$mp3Name.mp3")
                record_control_layout.visibility =
                    if (File("$path/$mp3Name.mp3").exists() || hasCloudFile) View.VISIBLE else View.GONE
                /**
                 * this song is favorite
                 * */
                changeBtnStatus()
            },
            Response.ErrorListener {
            })
        requestQueue.add(stringRequest)
    }

    /**only check upload file if exists
     * */
    private fun checkFileExist() {
        val requestQueue = Volley.newRequestQueue(this@NewYTPlayerActivity)
        val stringRequest = MyStringRequest(
            Request.Method.GET,
            "https://funday.asia/NewMylessonmobile/api/MyMusicbox?member_id=$memberID&customer_id=$customerID&musicbox_id=$strSongID",
            Response.Listener { response ->
                hasCloudFile = JSONObject(response).optString("filename").isNotEmpty()
                cloudFileUrl = JSONObject(response).optString("filename")
            },
            Response.ErrorListener {
            })
        requestQueue.add(stringRequest)
    }

    private fun lockOnSentence() {
        if (playingSentence != 0) {//avoid lock on the sentence at the first time enter activity
            runOnUiThread {
                linearManager.scrollToPosition(playingSentence + 2)//focus on current sentence +2
                recyclerView.layoutManager?.scrollToPosition(playingSentence + 2)//make the highlight in center
            }
        }
    }

    private fun refreshView() {
        runOnUiThread {
            recyclerView.adapter?.notifyDataSetChanged()
        }
    }

    private fun getProgress(progress: Long, total: Long): Int {
        return ((progress.toFloat() / total.toFloat()) * 100).toInt()
    }

    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    private fun recordAudio(path: String): Boolean {
        mediaRecorder.reset()
        try {
            mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
            mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
            mediaRecorder.setOutputFile(path)
            mediaRecorder.prepare()
            btn_stop_recording.visibility = View.VISIBLE
            btn_record.visibility = View.GONE
            btn_repeat.visibility = View.GONE
        } catch (e: Exception) {
            e.printStackTrace()
            return false
        }
        return true
    }

    private fun getRecordDir(): File? {
        if (!mediaMounted()) {
            return null
        }
        return File(recordDir)
    }

    private fun mediaMounted(): Boolean {
        val state = Environment.getExternalStorageState()
        return state == Environment.MEDIA_MOUNTED
    }

    /**convert time string to integer
     * */
    private fun formatStringToInt(timeString: String?): Int {
        val min = timeString!!.split(":")[0].toInt()
        val sec = timeString.split(":", ".")[1].toInt()
        val milSec = timeString.split(".")[1].toInt()
        return ((min * 60000) + (sec * 1000) + milSec)
    }

    private fun checkDirExists(): Boolean {
        return File(recordDir).absoluteFile.exists()
    }

    //if is favorite song then change pic
    private fun changeBtnStatus() {
        if (favoriteSong) Glide.with(this@NewYTPlayerActivity).load(
            resources.getDrawable(
                R.drawable.star_clicked_new,
                null
            )
        ).into(btn_favorite) else Glide.with(this@NewYTPlayerActivity).load(
            resources.getDrawable(
                R.drawable.star_new,
                null
            )
        ).into(btn_favorite)
    }

    private fun closeTimers() {
        timer.cancel()
        progressTimer.cancel()
        player.release()
        if (recordPlayer != null && recordPlayer?.isPlaying!!) {
            recordPlayer?.stop()
            recordPlayer?.release()
        }
    }

    private fun initializePlayer() {

        player = ExoPlayerFactory.newSimpleInstance(
            this, DefaultRenderersFactory(this),
            DefaultTrackSelector(), DefaultLoadControl()
        )

        yt_player.player = player

        player.playWhenReady = false
        player.seekTo(0, 0)


//         extractYoutubeUrl("https://www.youtube.com/watch?v=mVREnfGmYXI")
    }

    private fun restartPlayer() {
        player = ExoPlayerFactory.newSimpleInstance(
            this, DefaultRenderersFactory(this),
            DefaultTrackSelector(), DefaultLoadControl()
        )
        yt_player.player = player

        extractYoutubeUrl("https://www.youtube.com/watch?v=$APP_VIDEO_ID")
        player.playWhenReady = true
        Handler().postDelayed({
            player.seekTo(pauseTime)
        }, 3000)

    }

    @SuppressLint("StaticFieldLeak")
    private fun extractYoutubeUrl(youtubeLink: String) {
        object : YouTubeExtractor(this) {
            public override fun onExtractionComplete(
                ytFiles: SparseArray<YtFile>?,
                vMeta: VideoMeta
            ) {
                if (ytFiles != null) {
                    val itag = 18//video quality [18 = 480p] [22 = 720p] [137 = 1080p]
                    val downloadUrl = ytFiles.get(itag).url

                    // play url
                    val uri = Uri.parse(downloadUrl)
                    val mediaSource = buildMediaSource(uri)
                    player.prepare(mediaSource, true, false)

                }
            }
        }.extract(youtubeLink, true, true)

    }

    private fun buildMediaSource(uri: Uri): MediaSource {
        return ExtractorMediaSource.Factory(
            DefaultHttpDataSourceFactory("exoplayer-codelab")
        ).createMediaSource(uri)
    }

    private fun releasePlayer() {
        Log.d("TAG", "releasePlayer了")
        if (yt_player != null) {
            var playbackPosition = player.currentPosition
            var currentWindow = player.currentWindowIndex
            var playWhenReady = player.playWhenReady
            player.release()
        }
    }


    /**recyclerview's adapter
     * */
    inner class RecyclerAdapter(
        private var context: Context,
        private var dataVoList: ArrayList<DataVo>
    ) :
        RecyclerView.Adapter<RecyclerAdapter.ViewHolder>() {
        var clickCount = 0

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
            val cell = LayoutInflater.from(context).inflate(R.layout.lyrics_layout, parent, false)
            val viewHolder = ViewHolder(cell)
            viewHolder.tv_en_lyrics = cell.findViewById(R.id.tv_en_lyric)
            viewHolder.tv_ch_lyrics = cell.findViewById(R.id.tv_ch_lyric)
            viewHolder.layout = cell.findViewById(R.id.lyrics_layout)
            return viewHolder
        }

        override fun getItemCount(): Int {
            return dataVoList.size
        }

        @RequiresApi(Build.VERSION_CODES.M)
        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val data = dataVoList[position]
            holder.tv_en_lyrics.text = data.enText
            holder.tv_ch_lyrics.text = data.chText
            holder.tv_en_lyrics.customSelectionActionModeCallback = object : ActionMode.Callback {
                override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
                    return false
                }

                /*
                when user long click on a word then get the range of the word
                return true: show up copy pop up windows
                return false: do not show up the pop up window
                **/
                override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                    val word = holder.tv_en_lyrics.text.subSequence(
                        holder.tv_en_lyrics.selectionStart,
                        holder.tv_en_lyrics.selectionEnd
                    ).toString()
                    val requestQueue = Volley.newRequestQueue(this@NewYTPlayerActivity)
                    val stringRequest = @SuppressLint("SetTextI18n")
                    object : MyStringRequest(
                        Request.Method.POST,
                        "https://funday.asia/newmylessonmobile/api/dr.eye?keyword=$word",
                        Response.Listener { response ->
                            val stringBuilder = StringBuilder()

                            val transDialog =
                                Dialog(this@NewYTPlayerActivity, R.style.TranslateDialogTheme)
                            transDialog.setContentView(R.layout.translate_dialog)
                            transDialog.window!!.setBackgroundDrawable(object :
                                ColorDrawable(Color.TRANSPARENT) {})
                            transDialog.show()

                            val btnDismiss = transDialog.findViewById<Button>(R.id.btn_dismiss)
                            btnDismiss.setOnClickListener {
                                transDialog.dismiss()
                            }

                            val tvTranslateTitle =
                                transDialog.findViewById<TextView>(R.id.tv_translate_dialog_title)
                            val tvTranslateShort =
                                transDialog.findViewById<TextView>(R.id.tv_translate_dialog_short_trans)
                            val tvAllTrans =
                                transDialog.findViewById<TextView>(R.id.tv_translate_dialog_all_trans)
                            val imgTranslateLike =
                                transDialog.findViewById<ImageView>(R.id.img_translate_dialog_like)

                            tvTranslateTitle?.text = word
                            tvTranslateShort?.text =
                                (JSONObject(response).get("baseform") as JSONObject).get("attr")
                                    .toString()//字義
                            stringBuilder.append(
                                "${((JSONObject(response).get("baseform") as JSONObject).get(
                                    "text"
                                ) as JSONArray).foreach()}\n${checkJsonHasValues(response)}"
                            )
                            checkWordHasInclude(word, imgTranslateLike)

                            imgTranslateLike.setOnClickListener {
                                favoriteWord = !favoriteWord
                                val url =
                                    "https://funday.asia/NewMylessonmobile/${if (favoriteWord) "C" else "D"}/api/vocabulary/join?member_id=$memberID&customer_id=$customerID&Enkeyword=$word&Chkeyword=$stringBuilder"
                                val requestRequest =
                                    Volley.newRequestQueue(this@NewYTPlayerActivity)
                                val stringRequest = MyStringRequest(
                                    Request.Method.GET,
                                    url,
                                    Response.Listener {
                                        changeFavoriteWordIcon(imgTranslateLike!!)
                                    },
                                    Response.ErrorListener {
                                        "Error啦".toast(this@NewYTPlayerActivity)
                                    })
                                requestRequest.add(stringRequest)
                            }

                            tvAllTrans?.movementMethod =
                                ScrollingMovementMethod.getInstance()
                            tvAllTrans?.text = stringBuilder.toString()
                        },
                        Response.ErrorListener { error ->
                            Log.d("TAG", "Error: $error")
                        }) {
                        override fun getHeaders(): MutableMap<String, String> {
                            val header = mutableMapOf<String, String>()
                            header["Fundaykey"] = word.md5()
                            return header
                        }
                    }
                    requestQueue.add(stringRequest)
                    return false
                }

                override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                    TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
                }

                override fun onDestroyActionMode(mode: ActionMode?) {
                    TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
                }
            }
            if (data.isPlaying) {
                setFocus(holder)
            } else {
                setNotFocus(holder)
                holder.layout.setOnClickListener {
                    //double click on holder
                    clickCount++
                    object : CountDownTimer(2000, 1000) {
                        override fun onFinish() {
                            clickCount = 0
                        }

                        override fun onTick(millisUntilFinished: Long) {
                        }
                    }.start()
                    if (clickCount == 2) {//double click on recycler view's item
                        try {
                            player.seekTo((data.timeText).toLong())
                            dataVoList[position].isPlaying = true
                            setFocus(holder)
                            playingSentence = position//playing sentence back to position sentence
                            recyclerView.adapter?.notifyItemChanged(position)
                            for (count in 0 until dataVoList.size) {
                                if (count == position) continue
                                dataVoList[count].isPlaying = false
                                setNotFocus(holder)
                                recyclerView.adapter?.notifyItemChanged(count)
                            }
                        } catch (e: NumberFormatException) {
                        }
                    }
                }
            }
        }

        private fun changeFavoriteWordIcon(button: ImageView) {
            if (favoriteWord) Glide.with(this@NewYTPlayerActivity).load(
                resources.getDrawable(
                    R.drawable.star_clicked_new,
                    null
                )
            ).into(button) else Glide.with(this@NewYTPlayerActivity).load(
                resources.getDrawable(
                    R.drawable.star_new,
                    null
                )
            ).into(button)
        }

        private fun checkWordHasInclude(word: String, favoriteWordIcon: ImageView?) {
            val url =
                "https://funday.asia/NewMylessonmobile/api/vocabulary?member_id=$memberID&customer_id=$customerID&Enkeyword=$word"
            val requestRequest = Volley.newRequestQueue(this@NewYTPlayerActivity)
            val stringRequest =
                MyStringRequest(Request.Method.GET, url, Response.Listener { response ->
                    val json = JSONObject(response)
                    favoriteWord = json.opt("En_word") != ""

                    changeFavoriteWordIcon(favoriteWordIcon!!)
                }, Response.ErrorListener {

                })
            requestRequest.add(stringRequest)
        }

        @RequiresApi(Build.VERSION_CODES.KITKAT)
        private fun checkJsonHasValues(response: String?): String {
            val stringBuilder = java.lang.StringBuilder()
            val json = JSONObject(response)
            if (json.has("interpretation")) {
                stringBuilder.append("${(json.get("interpretation") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("interpretation") as JSONObject).get("text") as JSONArray).foreach2())
            }

            if (json.has("reference")) {
                stringBuilder.append("${(json.get("reference") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("reference") as JSONObject).get("text") as JSONArray).foreach())
            }

            if (json.has("phrase")) {
                stringBuilder.append("${(json.get("phrase") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("phrase") as JSONObject).get("text") as JSONArray).foreach2())
            }

            if (json.has("derivative")) {
                stringBuilder.append("${(json.get("derivative") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("derivative") as JSONObject).get("text") as JSONArray).foreach())
            }

            if (json.has("synonym")) {
                stringBuilder.append("${(json.get("synonym") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("synonym") as JSONObject).get("text") as JSONArray).foreach())
            }

            if (json.has("antonym")) {
                stringBuilder.append("${(json.get("antonym") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("antonym") as JSONObject).get("text") as JSONArray).foreach())
            }

            if (json.has("synonymref")) {
                stringBuilder.append("${(json.get("synonymref") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("synonymref") as JSONObject).get("text") as JSONArray).foreach())
            }

            if (json.has("antonymref")) {
                stringBuilder.append("${(json.get("antonymref") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("antonymref") as JSONObject).get("text") as JSONArray).foreach())
            }

            if (json.has("additional")) {
                stringBuilder.append("${(json.get("additional") as JSONObject).get("attr")}\n")
                stringBuilder.append(((json.get("additional") as JSONObject).get("text") as JSONArray).foreach())
            }
            return stringBuilder.toString()
        }

        @RequiresApi(Build.VERSION_CODES.M)
        private fun setNotFocus(holder: ViewHolder) {
            holder.layout.setBackgroundColor(
                context.resources.getColor(
                    R.color.background_gray,
                    null
                )
            )
            holder.tv_en_lyrics.setTextColor(
                context.resources.getColor(
                    R.color.progressBarWhite,
                    null
                )
            )
            holder.tv_ch_lyrics.setTextColor(
                context.resources.getColor(
                    R.color.progressBarWhite,
                    null
                )
            )
        }

        @RequiresApi(Build.VERSION_CODES.M)
        private fun setFocus(holder: ViewHolder) {
            holder.layout.setBackgroundColor(context.resources.getColor(R.color.isPlaying, null))
            holder.tv_en_lyrics.setTextColor(context.resources.getColor(R.color.black, null))
            holder.tv_ch_lyrics.setTextColor(context.resources.getColor(R.color.black, null))
        }

        inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
            lateinit var tv_ch_lyrics: TextView
            lateinit var tv_en_lyrics: TextView
            lateinit var layout: ConstraintLayout
        }
    }
}


Report Page