Skip to content

Commit 59c4782

Browse files
feat(lyrics): add notification lyrics support (#1005)
* feat(lyrics): add notification lyrics support * fix(lyrics): complete notification lyrics dependencies and decouple widget calls * refactor(notification): remove redundant notification lyric provider and streamline updates * perf(lyrics): pass shared preferences to EndedWorkaroundPlayer to avoid repeated lookups
1 parent 8baa1f4 commit 59c4782

6 files changed

Lines changed: 83 additions & 7 deletions

File tree

app/src/main/java/org/akanework/gramophone/logic/GramophonePlaybackService.kt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
219219
private lateinit var lastPlayedManager: LastPlayedManager
220220
private lateinit var prefs: SharedPreferences
221221
private var lastSentHighlightedLyric: String? = null
222+
private var lastSentNotificationLyric: String? = null
222223
private lateinit var afFormatTracker: AfFormatTracker
223224
private lateinit var rgAp: ReplayGainAudioProcessor
224225
private var rgMode = 0 // 0 = disabled, 1 = track, 2 = album, 3 = smart
@@ -406,6 +407,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
406407
onSharedPreferenceChanged(prefs, null) // read initial values
407408
val player = EndedWorkaroundPlayer(
408409
this,
410+
prefs,
409411
exoPlayer = ExoPlayer.Builder(
410412
this,
411413
GramophoneRenderFactory(
@@ -453,6 +455,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
453455
.build(),
454456
{ lyrics },
455457
queueBoard = qb,
458+
getNotificationLyric = { lastSentNotificationLyric }
456459
)
457460
player.exoPlayer.addAnalyticsListener(EventLogger())
458461
player.exoPlayer.addAnalyticsListener(afFormatTracker)
@@ -818,6 +821,7 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
818821
scope.cancel()
819822
endedWorkaroundPlayer!!.stop()
820823
handler.removeCallbacks(timer)
824+
handler.removeCallbacks(sendLyrics)
821825
mediaSession!!.setOptOutOfMediaButtonPlaybackResumption(controller!!.currentTimeline.isEmpty)
822826
proxy?.let {
823827
it.adapter.closeProfileProxy(BluetoothProfile.A2DP, it.a2dp)
@@ -903,6 +907,10 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
903907

904908
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
905909
var restart = false
910+
if (key == null || key == "notification_lyrics" || key == "status_bar_lyrics") {
911+
scheduleSendingLyrics(false)
912+
endedWorkaroundPlayer?.updateLyricNow()
913+
}
906914
if (key == null || key == "rg_mode") {
907915
rgMode = prefs.getStringStrict("rg_mode", "0")!!.toInt()
908916
restart = !computeRgMode(true)
@@ -1572,6 +1580,8 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
15721580
//lyrics = null
15731581
//scheduleSendingLyrics(true)
15741582
}
1583+
lastSentNotificationLyric = null
1584+
lastSentHighlightedLyric = null
15751585

15761586
// reshuffle queue when shuffle AND repeat all are enabled
15771587
val player = endedWorkaroundPlayer
@@ -1768,6 +1778,20 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
17681778
scheduleSendingLyrics(false)
17691779
}
17701780

1781+
private fun getActiveNotificationLyric(): String? {
1782+
val isNotificationLyricsEnabled = prefs.getBooleanStrict("notification_lyrics", false)
1783+
if (!isNotificationLyricsEnabled) return null
1784+
if (controller?.playbackState == Player.STATE_ENDED || controller?.playbackState == Player.STATE_IDLE) return null
1785+
1786+
val cPos = (controller?.contentPosition ?: 0).toULong()
1787+
val lines = syncedLyrics?.text?.filter {
1788+
it.start <= cPos && !it.isTranslated
1789+
}
1790+
val currentLine = lines?.maxByOrNull { it.start } ?: return null
1791+
if (currentLine.text.isBlank()) return null
1792+
return currentLine.text
1793+
}
1794+
17711795
private fun scheduleSendingLyrics(new: Boolean) {
17721796
handler.removeCallbacks(sendLyrics)
17731797
sendLyricNow(new || !updatedLyricAtLeastOnce)
@@ -1776,8 +1800,9 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
17761800
endedWorkaroundPlayer?.updateLyricNow()
17771801
}
17781802
val isStatusBarLyricsEnabled = prefs.getBooleanStrict("status_bar_lyrics", false)
1803+
val isNotificationLyricsEnabled = prefs.getBooleanStrict("notification_lyrics", false)
17791804
val hnw = !LyricWidgetProvider.hasWidget(this)
1780-
if (controller?.isPlaying != true || (!isStatusBarLyricsEnabled && hnw)) return
1805+
if (controller?.isPlaying != true || (!isStatusBarLyricsEnabled && !isNotificationLyricsEnabled && hnw)) return
17811806
val cPos = (controller?.contentPosition ?: 0).toULong()
17821807
val nextUpdate = syncedLyrics?.text?.flatMap { line ->
17831808
if (hnw && line.start <= cPos) listOf() else if (hnw) listOf(line.start) else
@@ -1803,10 +1828,16 @@ class GramophonePlaybackService : MediaLibraryService(), MediaSessionService.Lis
18031828
syncedLyrics?.text?.get(it)?.text
18041829
}
18051830
else null
1806-
if (lastSentHighlightedLyric != highlightedLyric) {
1831+
val notificationLyric = getActiveNotificationLyric()
1832+
if (lastSentHighlightedLyric != highlightedLyric || lastSentNotificationLyric != notificationLyric) {
1833+
val notifLyricChanged = lastSentNotificationLyric != notificationLyric
18071834
lastSentHighlightedLyric = highlightedLyric
1835+
lastSentNotificationLyric = notificationLyric
18081836
handler.post {
18091837
endedWorkaroundPlayer?.let {
1838+
if (notifLyricChanged) {
1839+
it.updateLyricNow()
1840+
}
18101841
// This will access the media notification controller's getters. But because
18111842
// controller callback ordering is undefined and in practice our service
18121843
// controller sometimes gets called first, this would cause us to access a stale

app/src/main/java/org/akanework/gramophone/logic/ui/MeiZuLyricsMediaNotificationProvider.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ private class InnerMeiZuLyricsMediaNotificationProvider(
4444
actionFactory: MediaNotification.ActionFactory
4545
): IntArray {
4646
val ticker = tickerProvider()
47-
val title = mediaSession.player.mediaMetadata.title.toString()
48-
val artist = mediaSession.player.mediaMetadata.artist.toString()
47+
val title = mediaSession.player.mediaMetadata.title?.toString() ?: ""
48+
val artist = mediaSession.player.mediaMetadata.artist?.toString() ?: ""
4949

5050
val bundle = IsLandHelp.isLandMusicShare(
5151
addpic = Bundle(),
@@ -68,7 +68,7 @@ private class InnerMeiZuLyricsMediaNotificationProvider(
6868

6969
class MeiZuLyricsMediaNotificationProvider(
7070
context: MediaSessionService,
71-
private val tickerProvider: () -> CharSequence?,
71+
private val tickerProvider: () -> CharSequence?
7272
) : MediaNotification.Provider {
7373
private val inner = InnerMeiZuLyricsMediaNotificationProvider(context, tickerProvider).apply {
7474
setSmallIcon(R.drawable.ic_gramophone_monochrome)

app/src/main/java/org/akanework/gramophone/logic/utils/exoplayer/EndedWorkaroundPlayer.kt

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import androidx.media3.common.PlaybackParameters
2727
import androidx.media3.common.Player
2828
import androidx.media3.common.util.Log
2929
import androidx.media3.exoplayer.ExoPlayer
30+
import android.content.SharedPreferences
3031
import com.google.common.util.concurrent.Futures
3132
import com.google.common.util.concurrent.ListenableFuture
3233
import org.akanework.gramophone.BuildConfig
@@ -38,6 +39,7 @@ import org.akanework.gramophone.logic.utils.CircularShuffleOrder
3839
import org.akanework.gramophone.logic.utils.Flags
3940
import org.akanework.gramophone.logic.utils.SemanticLyrics
4041
import org.json.JSONObject
42+
import org.akanework.gramophone.logic.getBooleanStrict
4143
import uk.akane.libphonograph.items.EXTRA_HD_ARTWORK_URI
4244
import uk.akane.libphonograph.items.hdArtworkUri
4345
import java.util.Objects
@@ -50,9 +52,11 @@ import java.util.Objects
5052
*/
5153
class EndedWorkaroundPlayer(
5254
val context: Context,
55+
private val prefs: SharedPreferences,
5356
exoPlayer: ExoPlayer,
5457
private val getLyric: () -> SemanticLyrics?,
55-
val queueBoard: QueueBoard
58+
val queueBoard: QueueBoard,
59+
private val getNotificationLyric: () -> CharSequence? = { null }
5660
) : ForwardingSimpleBasePlayer(exoPlayer),
5761
Player.Listener {
5862

@@ -95,7 +99,8 @@ class EndedWorkaroundPlayer(
9599
}
96100

97101
fun updateLyricNow() {
98-
if (context.packageName == "com.tencent.qqmusic") {
102+
val isNotificationLyricsEnabled = prefs.getBooleanStrict("notification_lyrics", false)
103+
if (context.packageName == "com.tencent.qqmusic" || isNotificationLyricsEnabled) {
99104
invalidateState()
100105
}
101106
}
@@ -117,6 +122,31 @@ class EndedWorkaroundPlayer(
117122
)
118123
.build()
119124
}
125+
if (superState.playWhenReady && superState.playbackState != STATE_ENDED && superState.playbackState != STATE_IDLE) {
126+
val notifLyric = getNotificationLyric()
127+
if (!notifLyric.isNullOrBlank()) {
128+
val origTitle = superState.currentMetadata.title?.toString() ?: ""
129+
val origArtist = superState.currentMetadata.artist?.toString() ?: ""
130+
val subtitle = if (origArtist.isNotBlank() && origTitle.isNotBlank()) {
131+
"$origArtist - $origTitle"
132+
} else {
133+
origArtist.ifBlank { origTitle }
134+
}
135+
val metadataWithLyric = superState.currentMetadata.buildUpon()
136+
.setTitle(notifLyric)
137+
.setArtist(subtitle)
138+
.setDisplayTitle(notifLyric)
139+
.setSubtitle(subtitle)
140+
.build()
141+
superState = superState.buildUpon()
142+
.setPlaylist(
143+
superState.timeline,
144+
superState.currentTracks,
145+
metadataWithLyric
146+
)
147+
.build()
148+
}
149+
}
120150
if (context.packageName == "com.tencent.qqmusic") {
121151
// Oplus uses package name whitelist for their lockscreen lyric feature
122152
// (don't use BuildConfig in order to allow late patching of package name, after build)

app/src/main/res/values-zh-rCN/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@
155155
<string name="tab_order_summary">应用将在启动时显示第一个标签页。分隔线后的标签页将被隐藏。</string>
156156
<string name="settings_status_bar_lyrics_title">状态栏歌词</string>
157157
<string name="settings_status_bar_lyrics_summary">启用魅族状态栏歌词(仅适用于部分设备)</string>
158+
<string name="settings_notification_lyrics_title">通知栏歌词</string>
159+
<string name="settings_notification_lyrics_summary">在媒体通知中显示当前播放歌词</string>
158160
<string name="settings_lyrics_ui">启用新歌词界面</string>
159161
<string name="lyric_widget_description">显示当前播放媒体歌词的小部件</string>
160162
<string name="scroll_to_albums">滚动到专辑</string>

app/src/main/res/values/strings.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@
392392
<string name="settings_status_bar_lyrics_title">Status bar lyrics</string>
393393
<!-- Settings toggle to enable showing lyrics in status bar (requires meizu phone). Subtext -->
394394
<string name="settings_status_bar_lyrics_summary">Enable MeiZu method of displaying lyrics in status bar (only available on some devices)</string>
395+
<!-- Settings toggle to enable showing lyrics in media notification. Title -->
396+
<string name="settings_notification_lyrics_title">Notification lyrics</string>
397+
<!-- Settings toggle to enable showing lyrics in media notification. Subtext -->
398+
<string name="settings_notification_lyrics_summary">Display lyrics as the notification title and artist - title as subtitle</string>
395399
<!-- Description shown about lyric widget in Android launcher / home screen's widget selector on Android 12+ -->
396400
<string name="lyric_widget_description">Widget that shows lyrics of currently playing media</string>
397401
<!-- Context menu item to delete song/album/playlist -->

app/src/main/res/xml/settings_lyric.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@
5555
android:widgetLayout="@layout/preference_switch_widget"
5656
app:iconSpaceReserved="false" />
5757

58+
<SwitchPreferenceCompat
59+
android:defaultValue="false"
60+
android:key="notification_lyrics"
61+
android:layout="@layout/preference_switch"
62+
android:summary="@string/settings_notification_lyrics_summary"
63+
android:title="@string/settings_notification_lyrics_title"
64+
android:widgetLayout="@layout/preference_switch_widget"
65+
app:iconSpaceReserved="false" />
66+
5867
<SwitchPreferenceCompat
5968
android:defaultValue="true"
6069
android:key="trim_lyrics"

0 commit comments

Comments
 (0)