diff --git a/app/src/main/java/org/akanework/gramophone/logic/GramophonePlaybackService.kt b/app/src/main/java/org/akanework/gramophone/logic/GramophonePlaybackService.kt index 36a0f5284e..d0a522132e 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/GramophonePlaybackService.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/GramophonePlaybackService.kt @@ -218,6 +218,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis private lateinit var lastPlayedManager: LastPlayedManager private lateinit var prefs: SharedPreferences private var lastSentHighlightedLyric: String? = null + private var lastSentNotificationLyric: String? = null private lateinit var afFormatTracker: AfFormatTracker private lateinit var rgAp: ReplayGainAudioProcessor private var rgMode = 0 // 0 = disabled, 1 = track, 2 = album, 3 = smart @@ -405,6 +406,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis onSharedPreferenceChanged(prefs, null) // read initial values val player = EndedWorkaroundPlayer( this, + prefs, exoPlayer = ExoPlayer.Builder( this, GramophoneRenderFactory( @@ -452,6 +454,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis .build(), { lyrics }, queueBoard = qb, + getNotificationLyric = { lastSentNotificationLyric } ) player.exoPlayer.addAnalyticsListener(EventLogger()) player.exoPlayer.addAnalyticsListener(afFormatTracker) @@ -817,6 +820,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis scope.cancel() endedWorkaroundPlayer!!.stop() handler.removeCallbacks(timer) + handler.removeCallbacks(sendLyrics) mediaSession!!.setOptOutOfMediaButtonPlaybackResumption(controller!!.currentTimeline.isEmpty) proxy?.let { it.adapter.closeProfileProxy(BluetoothProfile.A2DP, it.a2dp) @@ -902,6 +906,10 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { var restart = false + if (key == null || key == "notification_lyrics" || key == "status_bar_lyrics") { + scheduleSendingLyrics(false) + endedWorkaroundPlayer?.updateLyricNow() + } if (key == null || key == "rg_mode") { rgMode = prefs.getStringStrict("rg_mode", "0")!!.toInt() restart = !computeRgMode(true) @@ -1582,6 +1590,8 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis //lyrics = null //scheduleSendingLyrics(true) } + lastSentNotificationLyric = null + lastSentHighlightedLyric = null // reshuffle queue when shuffle AND repeat all are enabled val player = endedWorkaroundPlayer @@ -1760,6 +1770,20 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis scheduleSendingLyrics(false) } + private fun getActiveNotificationLyric(): String? { + val isNotificationLyricsEnabled = prefs.getBooleanStrict("notification_lyrics", false) + if (!isNotificationLyricsEnabled) return null + if (controller?.playbackState == Player.STATE_ENDED || controller?.playbackState == Player.STATE_IDLE) return null + + val cPos = (controller?.contentPosition ?: 0).toULong() + val lines = syncedLyrics?.text?.filter { + it.start <= cPos && !it.isTranslated + } + val currentLine = lines?.maxByOrNull { it.start } ?: return null + if (currentLine.text.isBlank()) return null + return currentLine.text + } + private fun scheduleSendingLyrics(new: Boolean) { handler.removeCallbacks(sendLyrics) sendLyricNow(new || !updatedLyricAtLeastOnce) @@ -1768,8 +1792,9 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis endedWorkaroundPlayer?.updateLyricNow() } val isStatusBarLyricsEnabled = prefs.getBooleanStrict("status_bar_lyrics", false) + val isNotificationLyricsEnabled = prefs.getBooleanStrict("notification_lyrics", false) val hnw = !LyricWidgetProvider.hasWidget(this) - if (controller?.isPlaying != true || (!isStatusBarLyricsEnabled && hnw)) return + if (controller?.isPlaying != true || (!isStatusBarLyricsEnabled && !isNotificationLyricsEnabled && hnw)) return val cPos = (controller?.contentPosition ?: 0).toULong() val nextUpdate = syncedLyrics?.text?.flatMap { line -> if (hnw && line.start <= cPos) listOf() else if (hnw) listOf(line.start) else @@ -1795,10 +1820,16 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis syncedLyrics?.text?.get(it)?.text } else null - if (lastSentHighlightedLyric != highlightedLyric) { + val notificationLyric = getActiveNotificationLyric() + if (lastSentHighlightedLyric != highlightedLyric || lastSentNotificationLyric != notificationLyric) { + val notifLyricChanged = lastSentNotificationLyric != notificationLyric lastSentHighlightedLyric = highlightedLyric + lastSentNotificationLyric = notificationLyric handler.post { endedWorkaroundPlayer?.let { + if (notifLyricChanged) { + it.updateLyricNow() + } // This will access the media notification controller's getters. But because // controller callback ordering is undefined and in practice our service // controller sometimes gets called first, this would cause us to access a stale diff --git a/app/src/main/java/org/akanework/gramophone/logic/ui/MeiZuLyricsMediaNotificationProvider.kt b/app/src/main/java/org/akanework/gramophone/logic/ui/MeiZuLyricsMediaNotificationProvider.kt index 05fa7f7a37..f21eae4ed4 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/ui/MeiZuLyricsMediaNotificationProvider.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/ui/MeiZuLyricsMediaNotificationProvider.kt @@ -44,8 +44,8 @@ private class InnerMeiZuLyricsMediaNotificationProvider( actionFactory: MediaNotification.ActionFactory ): IntArray { val ticker = tickerProvider() - val title = mediaSession.player.mediaMetadata.title.toString() - val artist = mediaSession.player.mediaMetadata.artist.toString() + val title = mediaSession.player.mediaMetadata.title?.toString() ?: "" + val artist = mediaSession.player.mediaMetadata.artist?.toString() ?: "" val bundle = IsLandHelp.isLandMusicShare( addpic = Bundle(), @@ -68,7 +68,7 @@ private class InnerMeiZuLyricsMediaNotificationProvider( class MeiZuLyricsMediaNotificationProvider( context: MediaSessionService, - private val tickerProvider: () -> CharSequence?, + private val tickerProvider: () -> CharSequence? ) : MediaNotification.Provider { private val inner = InnerMeiZuLyricsMediaNotificationProvider(context, tickerProvider).apply { setSmallIcon(R.drawable.ic_gramophone_monochrome) diff --git a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/EndedWorkaroundPlayer.kt b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/EndedWorkaroundPlayer.kt index 313980420a..822d4d42b1 100644 --- a/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/EndedWorkaroundPlayer.kt +++ b/app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/EndedWorkaroundPlayer.kt @@ -27,6 +27,7 @@ import androidx.media3.common.PlaybackParameters import androidx.media3.common.Player import androidx.media3.common.util.Log import androidx.media3.exoplayer.ExoPlayer +import android.content.SharedPreferences import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture import org.akanework.gramophone.BuildConfig @@ -38,6 +39,7 @@ import org.akanework.gramophone.logic.utils.CircularShuffleOrder import org.akanework.gramophone.logic.utils.Flags import org.akanework.gramophone.logic.utils.SemanticLyrics import org.json.JSONObject +import org.akanework.gramophone.logic.getBooleanStrict import uk.akane.libphonograph.items.EXTRA_HD_ARTWORK_URI import uk.akane.libphonograph.items.hdArtworkUri import java.util.Objects @@ -50,9 +52,11 @@ import java.util.Objects */ class EndedWorkaroundPlayer( val context: Context, + private val prefs: SharedPreferences, exoPlayer: ExoPlayer, private val getLyric: () -> SemanticLyrics?, - val queueBoard: QueueBoard + val queueBoard: QueueBoard, + private val getNotificationLyric: () -> CharSequence? = { null } ) : ForwardingSimpleBasePlayer(exoPlayer), Player.Listener { @@ -97,7 +101,8 @@ class EndedWorkaroundPlayer( } fun updateLyricNow() { - if (context.packageName == "com.tencent.qqmusic") { + val isNotificationLyricsEnabled = prefs.getBooleanStrict("notification_lyrics", false) + if (context.packageName == "com.tencent.qqmusic" || isNotificationLyricsEnabled) { invalidateState() } } @@ -119,6 +124,31 @@ class EndedWorkaroundPlayer( ) .build() } + if (superState.playWhenReady && superState.playbackState != STATE_ENDED && superState.playbackState != STATE_IDLE) { + val notifLyric = getNotificationLyric() + if (!notifLyric.isNullOrBlank()) { + val origTitle = superState.currentMetadata.title?.toString() ?: "" + val origArtist = superState.currentMetadata.artist?.toString() ?: "" + val subtitle = if (origArtist.isNotBlank() && origTitle.isNotBlank()) { + "$origArtist - $origTitle" + } else { + origArtist.ifBlank { origTitle } + } + val metadataWithLyric = superState.currentMetadata.buildUpon() + .setTitle(notifLyric) + .setArtist(subtitle) + .setDisplayTitle(notifLyric) + .setSubtitle(subtitle) + .build() + superState = superState.buildUpon() + .setPlaylist( + superState.timeline, + superState.currentTracks, + metadataWithLyric + ) + .build() + } + } if (context.packageName == "com.tencent.qqmusic") { // Oplus uses package name whitelist for their lockscreen lyric feature // (don't use BuildConfig in order to allow late patching of package name, after build) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index aeead4c25a..b04076273b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -155,6 +155,8 @@ 应用将在启动时显示第一个标签页。分隔线后的标签页将被隐藏。 状态栏歌词 启用魅族状态栏歌词(仅适用于部分设备) + 通知栏歌词 + 在媒体通知中显示当前播放歌词 启用新歌词界面 显示当前播放媒体歌词的小部件 滚动到专辑 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 10ab605577..00f135e288 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -392,6 +392,10 @@ Status bar lyrics Enable MeiZu method of displaying lyrics in status bar (only available on some devices) + + Notification lyrics + + Display lyrics as the notification title and artist - title as subtitle Widget that shows lyrics of currently playing media diff --git a/app/src/main/res/xml/settings_lyric.xml b/app/src/main/res/xml/settings_lyric.xml index db2fb01340..3763e84e54 100644 --- a/app/src/main/res/xml/settings_lyric.xml +++ b/app/src/main/res/xml/settings_lyric.xml @@ -55,6 +55,15 @@ android:widgetLayout="@layout/preference_switch_widget" app:iconSpaceReserved="false" /> + +