package relax.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.LinearLayout import android.widget.RelativeLayout import android.widget.TextView import androidx.annotation.OptIn import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.LifecycleOwner import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.Player import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.offline.Download import androidx.media3.exoplayer.offline.DownloadManager import relax.offline.music.R import relax.offline.music.media.MediaControllerManager import relax.offline.music.sp.AppStore import relax.offline.music.util.LogTag import relax.offline.music.view.MusicPlayerView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import relax.offline.music.bean.OfflineBean import relax.offline.music.innertube.Innertube import relax.offline.music.util.FileSizeConverter @OptIn(UnstableApi::class) abstract class MoBaseActivity : AppCompatActivity(), CoroutineScope by MainScope(), LifecycleOwner { private var playerListener: Player.Listener? = null private var downloadManagerListener: DownloadManager.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(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) 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) { 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_READY -> { musicPlayerView.updateSetProgress(meController) } else -> {} } } } override fun onPlayWhenReadyChanged( playWhenReady: Boolean, reason: Int ) { 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(R.id.dialog_title) title.text = getString(R.string.description) val content = dialogView.findViewById(R.id.dialog_content) content.text = description val close = dialogView.findViewById(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] } fun insertOfflineData(mediaItem: MediaItem) { CoroutineScope(Dispatchers.IO).launch { val bean = OfflineBean( videoId = mediaItem.mediaId, title = mediaItem.mediaMetadata.title.toString(), name = mediaItem.mediaMetadata.artist.toString(), thumbnail = mediaItem.mediaMetadata.artworkUri.toString(), isOffline = true ) LogTag.LogD(Innertube.TAG, "insertOfflineBean bean->${bean}") relax.offline.music.App.appOfflineDBManager.insertOfflineBean(bean) } } }