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" />
+
+