Musicoo/app/src/main/java/melody/offline/music/activity/MoBaseActivity.kt
2024-07-04 13:50:54 +08:00

525 lines
22 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package melody.offline.music.activity
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.renderscript.Allocation
import android.renderscript.Element
import android.renderscript.RenderScript
import android.renderscript.ScriptIntrinsicBlur
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.offline.Download
import androidx.media3.exoplayer.offline.DownloadRequest
import androidx.media3.exoplayer.offline.DownloadService
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.ironsource.be
import com.ironsource.vi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import melody.offline.music.App
import melody.offline.music.R
import melody.offline.music.adapter.NewPlayListAdapter
import melody.offline.music.ads.AdPlacement
import melody.offline.music.ads.LolAdWrapper
import melody.offline.music.bean.FavoriteBean
import melody.offline.music.bean.OfflineBean
import melody.offline.music.bean.Playlist
import melody.offline.music.bean.PlaylistItem
import melody.offline.music.http.getAppVersionCode
import melody.offline.music.http.getCountryCode
import melody.offline.music.innertube.Innertube
import melody.offline.music.media.MediaControllerManager
import melody.offline.music.service.MyDownloadService
import melody.offline.music.service.ViewModelMain
import melody.offline.music.sp.AppStore
import melody.offline.music.util.AnalysisUtil
import melody.offline.music.util.DownloadUtil
import melody.offline.music.util.FileSizeConverter
import melody.offline.music.util.LogTag
import melody.offline.music.view.MusicPlayerView
import melody.offline.music.view.RatingDialog
import org.json.JSONObject
import java.util.concurrent.TimeUnit
@OptIn(UnstableApi::class)
abstract class MoBaseActivity : AppCompatActivity(), MusicPlayerView.PlaySkipForwardListener,
CoroutineScope by MainScope(), LifecycleOwner {
private var playerListener: Player.Listener? = null
enum class Event {
ActivityStart, ActivityStop, ActivityOnResume, AutomaticallySwitchSongs,
}
protected val TAG = LogTag.VO_ACT_LOG
protected val appStore by lazy { AppStore(this) }
protected val events = Channel<Event>(Channel.UNLIMITED)
protected val meController = MediaControllerManager.getController()
protected abstract suspend fun main()
private var defer: suspend () -> Unit = {}
private var deferRunning = false
private lateinit var musicPlayerView: MusicPlayerView
fun defer(operation: suspend () -> Unit) {
this.defer = operation
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
musicPlayerView = MusicPlayerView(this, meController, this)
initPlayerListener()
launch {
main()
}
}
override fun onResume() {
super.onResume()
events.trySend(Event.ActivityOnResume)
}
override fun onStart() {
super.onStart()
events.trySend(Event.ActivityStart)
}
override fun onStop() {
super.onStop()
events.trySend(Event.ActivityStop)
}
override fun finish() {
if (deferRunning) {
return
}
deferRunning = true
launch {
try {
defer()
} finally {
withContext(NonCancellable) {
super.finish()
}
}
}
}
fun addMusicPlayerViewToLayout(layoutId: LinearLayout) {
if (meController != null && meController.mediaItemCount > 0 && meController.duration > 0) {
if (layoutId.childCount <= 0) {//没有添加view才进行添加
layoutId.addView(musicPlayerView)
}
musicPlayerView.updateInfoUi(meController.currentMediaItem)
musicPlayerView.updateSetProgress(meController)
musicPlayerView.updateProgressState(meController)
layoutId.visibility = View.VISIBLE
} else {
layoutId.visibility = View.GONE
}
}
private fun initPlayerListener() {
if (this !is MoPlayDetailsActivity && this !is LaunchActivity) {
if (playerListener == null) {
LogTag.LogD(TAG, "MoBaseActivity initPlayerListener")
meController?.addListener(getPlayerListener())
}
}
}
override fun onDestroy() {
super.onDestroy()
if (meController != null && playerListener != null) {
meController.removeListener(playerListener!!)
}
}
private fun getPlayerListener(): Player.Listener {
if (playerListener == null) {
playerListener = object : Player.Listener {
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo, newPosition: Player.PositionInfo, reason: Int
) {
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
if (meController != null) {
musicPlayerView.updateInfoUi(meController.currentMediaItem)
musicPlayerView.updateSetProgress(meController)
musicPlayerView.updateProgressState(meController)
}
events.trySend(Event.AutomaticallySwitchSongs)
}
}
override fun onPlaybackStateChanged(playbackState: Int) {
val meController = MediaControllerManager.getController()
if (meController != null) {
musicPlayerView.updateProgressState(meController)
when (playbackState) {
Player.STATE_IDLE -> {
LogTag.LogD(TAG, "base STATE_IDLE")
}
Player.STATE_BUFFERING -> {
LogTag.LogD(TAG, "base STATE_BUFFERING")
}
Player.STATE_READY -> {
LogTag.LogD(TAG, "base STATE_READY")
musicPlayerView.updateSetProgress(meController)
}
else -> {}
}
}
}
override fun onPlayWhenReadyChanged(
playWhenReady: Boolean, reason: Int
) {
LogTag.LogD(TAG, "base onPlayWhenReadyChanged $playWhenReady")
musicPlayerView.updatePlayState(playWhenReady)
val meController = MediaControllerManager.getController()
if (meController != null) {
musicPlayerView.updateProgressState(meController)
}
}
}
}
return playerListener!!
}
fun applyGaussianBlur(inputBitmap: Bitmap, radius: Float, context: Context): Bitmap {
val rsContext = RenderScript.create(context)
val outputBitmap =
Bitmap.createBitmap(inputBitmap.width, inputBitmap.height, inputBitmap.config)
val blurScript = ScriptIntrinsicBlur.create(rsContext, Element.U8_4(rsContext))
val tmpIn = Allocation.createFromBitmap(rsContext, inputBitmap)
val tmpOut = Allocation.createFromBitmap(rsContext, outputBitmap)
blurScript.setRadius(radius)
blurScript.setInput(tmpIn)
blurScript.forEach(tmpOut)
tmpOut.copyTo(outputBitmap)
rsContext.finish()
return outputBitmap
}
fun showSongDescriptionDialog(description: String) {
val inflater = LayoutInflater.from(this)
val dialogView = inflater.inflate(R.layout.dialog_description, null)
val title = dialogView.findViewById<TextView>(R.id.dialog_title)
title.text = getString(R.string.description)
val content = dialogView.findViewById<TextView>(R.id.dialog_content)
content.text = description
val close = dialogView.findViewById<RelativeLayout>(R.id.closeBtn)
val dialogBuilder = AlertDialog.Builder(this).setView(dialogView)
val dialog = dialogBuilder.create()
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.show()
close.setOnClickListener {
dialog.dismiss()
}
}
fun extractTextBeforeNewline(text: String): String {
// 用换行符分割文本,取第一个部分
return text.split("\n\n")[0]
}
suspend fun insertOfflineData(mediaItem: MediaItem) {
val currentDownload = DownloadUtil.getCurrentIdDownload(mediaItem.mediaId)
val favoriteBean = App.appFavoriteDBManager.getFavoriteBeanByID(mediaItem.mediaId)
val bean = OfflineBean(
videoId = mediaItem.mediaId,
title = mediaItem.mediaMetadata.title.toString(),
name = mediaItem.mediaMetadata.artist.toString(),
thumbnail = mediaItem.mediaMetadata.artworkUri.toString(),
isOffline = true,
isFavorite = favoriteBean?.isFavorite ?: false,
bytesDownloaded = currentDownload?.bytesDownloaded,
size = FileSizeConverter.formatSize(currentDownload?.bytesDownloaded ?: 0)
)
LogTag.LogD(TAG, "insertOfflineBean bean->${bean}")
App.appOfflineDBManager.insertOfflineBean(bean)
}
suspend fun insertOfflineData(favoriteBean: FavoriteBean) {
val currentDownload = DownloadUtil.getCurrentIdDownload(favoriteBean.videoId)
if (currentDownload != null) {
val bytesDownloaded = currentDownload.bytesDownloaded
val size = FileSizeConverter(currentDownload.bytesDownloaded).formattedSize()
val bean = OfflineBean(
videoId = favoriteBean.videoId,
title = favoriteBean.title,
name = favoriteBean.name,
thumbnail = favoriteBean.thumbnail,
isOffline = true,
isFavorite = favoriteBean.isFavorite,
bytesDownloaded = bytesDownloaded,
size = size
)
App.appOfflineDBManager.insertOfflineBean(bean)
} else {
val bean = OfflineBean(
videoId = favoriteBean.videoId,
title = favoriteBean.title,
name = favoriteBean.name,
thumbnail = favoriteBean.thumbnail,
isOffline = true,
isFavorite = favoriteBean.isFavorite,
)
App.appOfflineDBManager.insertOfflineBean(bean)
}
}
suspend fun insertFavoriteData(mediaItem: MediaItem) {
val bean = FavoriteBean(
videoId = mediaItem.mediaId,
title = mediaItem.mediaMetadata.title.toString(),
name = mediaItem.mediaMetadata.artist.toString(),
thumbnail = mediaItem.mediaMetadata.artworkUri.toString(),
isFavorite = true
)
LogTag.LogD(TAG, "insertFavoriteBean bean->${bean}")
App.appFavoriteDBManager.insertFavoriteBean(bean)
}
suspend fun insertFavoriteData(offlineBean: OfflineBean) {
val bean = FavoriteBean(
videoId = offlineBean.videoId,
title = offlineBean.title,
name = offlineBean.name,
thumbnail = offlineBean.thumbnail,
isFavorite = true
)
LogTag.LogD(TAG, "insertFavoriteBean bean->${bean}")
App.appFavoriteDBManager.insertFavoriteBean(bean)
}
fun withPermission(): Boolean {
//先判断当前配置的开关是否为true为false的话就直接进入A
val jsonString = appStore.shouldEnterMusicPageJson//得到配置的json
LogTag.LogD(TAG, "withPermission jsonString->${jsonString}")
val json = JSONObject(jsonString)
val versionCode = json.optInt("versionCode")//得到配置的code
val enter = json.optBoolean("enter")//得到配置的开关
val currentVersionCode = getAppVersionCode(this)//得到当前应用的code
//如果配置的code 等于 当前app的code则把配置赋值给shouldEnterMusicPage
if (versionCode.toLong() == currentVersionCode) {
appStore.shouldEnterMusicPage = enter
} else {
appStore.shouldEnterMusicPage = true
}
LogTag.LogD(TAG, "withPermission shouldEnterMusicPage->${appStore.shouldEnterMusicPage}")
if (!appStore.shouldEnterMusicPage) {
return false
}
// 不允许的国家代码
val restrictedCountries = setOf(
// "CN",
"HK", "TW", "JP", "KR", "GB", "CH", "BE", "MO", "SG"
)
// 检查是否包含当前的国家代码
LogTag.LogD(TAG, "withPermission ipCountryCode->${appStore.ipCountryCode}")
if (appStore.ipCountryCode in restrictedCountries) {
return false
}
// 如果不在受限国家代码中,则继续其他检查
return withIso()
}
private fun withIso(): Boolean {
//460 || 461 China (People's Republic of)
//454 "Hong Kong, China"
//466 "Taiwan, China"
//440 || 441 Japan
//450 Korea (Republic of)
//234 || 235 United Kingdom of Great Britain and Northern Ireland
//228 Switzerland (Confederation of)
//206 Belgium
//455 "Macao, China"
//525 Singapore (Republic of)
val restrictedCountryCodes = setOf(
// "460",
// "461",
"454", "466", "440", "441", "450", "234", "235", "228", "206", "455", "525"
)
val currentCountryCode = getCountryCode(this)
LogTag.LogD(TAG, "withPermission currentCountryCode->${currentCountryCode}")
return currentCountryCode !in restrictedCountryCodes
}
suspend fun showAddPlaylistBottomDialog(favoriteBean: FavoriteBean) {
val bottomAddPlaylistSheetDialog = BottomSheetDialog(this)
val view = layoutInflater.inflate(R.layout.add_playlist_layout, null)
bottomAddPlaylistSheetDialog.setContentView(view)
val newPlayListBtn = view.findViewById<LinearLayout>(R.id.newPlayListBtn)
val rv = view.findViewById<RecyclerView>(R.id.newPlayListRv)
newPlayListBtn.setOnClickListener {
bottomAddPlaylistSheetDialog.dismiss()
showNewPlaylistBottomDialog(favoriteBean)
}
// 设置对话框背景为透明以显示圆角
bottomAddPlaylistSheetDialog.window?.setBackgroundDrawableResource(android.R.color.transparent)
bottomAddPlaylistSheetDialog.window?.navigationBarColor =
ContextCompat.getColor(this, R.color.main_bg_color)
bottomAddPlaylistSheetDialog.show()
val playlist = (App.appPlaylistDBManager.getAllPlaylists())
val adapter = NewPlayListAdapter(this, playlist)
adapter.setOnItemClickListener(object : NewPlayListAdapter.OnItemClickListener {
override fun onItemClick(position: Int) {
launch {
val playlistItem =
App.appPlaylistDBManager.getPlaylistItems(playlist[position].id)
val isAny = playlistItem.any { it.title == favoriteBean.title }
if (isAny) {//如何这首歌曲已经存在歌单则不添加
withContext(Dispatchers.Main) {
Toast.makeText(
this@MoBaseActivity,
getString(R.string.song_exists_playlist_hint),
Toast.LENGTH_LONG
).show()
}
} else {
val isOffline =
App.appOfflineDBManager.getOfflineBeanByID(favoriteBean.videoId) != null
val isFavorite =
App.appFavoriteDBManager.getFavoriteBeanByID(favoriteBean.videoId) != null
App.appPlaylistDBManager.insertOrUpdatePlaylistItem(
PlaylistItem(
playlistId = playlist[position].id,
videoId = favoriteBean.videoId,
title = favoriteBean.title,
name = favoriteBean.name,
thumbnail = favoriteBean.thumbnail,
isOffline = isOffline,
isFavorite = isFavorite
)
)
withContext(Dispatchers.Main) {
bottomAddPlaylistSheetDialog.dismiss()
Toast.makeText(
this@MoBaseActivity,
getString(R.string.added_playlist_success_Hint),
Toast.LENGTH_LONG
).show()
}
}
}
}
})
rv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
rv.adapter = adapter
}
private var bottomSheetDialog: BottomSheetDialog? = null
private fun showNewPlaylistBottomDialog(favoriteBean: FavoriteBean) {
bottomSheetDialog = BottomSheetDialog(this)
val view = layoutInflater.inflate(R.layout.new_playlist_layout, null)
bottomSheetDialog?.setContentView(view)
val edit = view.findViewById<EditText>(R.id.playlistEt)
val confirmBtn = view.findViewById<TextView>(R.id.confirmBtn)
confirmBtn.setOnClickListener {
val text = edit.text.toString().trim()
if (text.isNotEmpty()) {
launch {
val playlist = App.appPlaylistDBManager.getPlaylistByTitle(text)
if (playlist != null) {
withContext(Dispatchers.Main) {
Toast.makeText(
this@MoBaseActivity,
getString(R.string.new_playlist_duplicate_name_hint),
Toast.LENGTH_LONG
).show()
}
} else {
val newPlaylist = Playlist(title = text)
App.appPlaylistDBManager.insertOrUpdatePlaylist(newPlaylist)
withContext(Dispatchers.Main) {
if (bottomSheetDialog != null) {
bottomSheetDialog?.dismiss()
}
Toast.makeText(
this@MoBaseActivity,
getString(R.string.created_successfully),
Toast.LENGTH_LONG
).show()
}
val currentPlaylist = App.appPlaylistDBManager.getPlaylistByTitle(text)
if (currentPlaylist != null) {
val isOffline =
App.appOfflineDBManager.getOfflineBeanByID(favoriteBean.videoId) != null//返回非null则为true
val isFavorite =
App.appFavoriteDBManager.getFavoriteBeanByID(favoriteBean.videoId) != null
val playlistItem = PlaylistItem(
playlistId = currentPlaylist.id,
videoId = favoriteBean.videoId,
title = favoriteBean.title,
name = favoriteBean.name,
thumbnail = favoriteBean.thumbnail,
isOffline = isOffline,
isFavorite = isFavorite
)
App.appPlaylistDBManager.insertOrUpdatePlaylistItem(playlistItem)
}
}
}
}
}
// 设置对话框背景为透明以显示圆角
bottomSheetDialog?.window?.setBackgroundDrawableResource(android.R.color.transparent)
bottomSheetDialog?.window?.navigationBarColor =
ContextCompat.getColor(this, R.color.main_bg_color)
bottomSheetDialog?.show()
}
fun showRatingDialog() {
val intervalTime = 1000 * 60 * 5
val dialog = RatingDialog(this)
val installTime = appStore.showRateDialogTime
val currentTime = System.currentTimeMillis()
// 检测是否超过间隔时间
if (currentTime - installTime > intervalTime) {
dialog.show()
//更新为当前时间
appStore.showRateDialogTime = System.currentTimeMillis()
}
}
}