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
}
}
}