Skip to content

Commit 0fd1c01

Browse files
committed
feat(lyric): 支持词幕
1 parent 52ae6b8 commit 0fd1c01

16 files changed

Lines changed: 994 additions & 4 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ dependencies {
6868
implementation(libs.coil.compose)
6969
implementation("com.github.xiaowine:Lyric-Getter-Api:6.0.0")
7070
implementation("com.github.HChenX:SuperLyricApi:2.4")
71+
implementation("io.github.proify.lyricon:provider:0.1.70")
7172

7273
implementation(platform(libs.androidx.compose.bom))
7374
implementation("androidx.compose.material3:material3") // 明确指定

app/src/main/AndroidManifest.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@
2121
android:theme="@style/Theme.Kanade"
2222
android:enableOnBackInvokedCallback="true"
2323
android:networkSecurityConfig="@xml/network_security_config">
24+
25+
<!-- Lyricon Provider Configuration -->
26+
<meta-data
27+
android:name="lyricon_module"
28+
android:value="true" />
29+
<meta-data
30+
android:name="lyricon_module_author"
31+
android:value="Kanade Team" />
32+
<meta-data
33+
android:name="lyricon_module_description"
34+
android:value="Kanade Music Player" />
35+
<meta-data
36+
android:name="lyricon_module_tags"
37+
android:resource="@array/lyricon_module_tags" />
38+
2439
<activity
2540
android:name=".MainActivity"
2641
android:exported="true"

app/src/main/java/org/parallel_sekai/kanade/MainActivity.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ sealed class Screen(
9595

9696
object SuperLyricApi : Screen("super_lyric_api", R.string.pref_super_lyric, null)
9797

98+
object LyriconApi : Screen("lyricon_api", R.string.pref_lyricon, null)
99+
98100
object MediaNotificationLyrics : Screen("media_notification_lyrics", R.string.title_media_notification_lyrics, null)
99101

100102
object ExcludedFolders : Screen("excluded_folders", R.string.title_excluded_folders, null)
@@ -501,6 +503,7 @@ class MainActivity : ComponentActivity() {
501503
)
502504
},
503505
onNavigateToSuperLyricApi = { navController.navigate(Screen.SuperLyricApi.route) },
506+
onNavigateToLyriconApi = { navController.navigate(Screen.LyriconApi.route) },
504507
onNavigateToMediaNotificationLyrics = {
505508
navController.navigate(
506509
Screen.MediaNotificationLyrics.route,
@@ -526,6 +529,12 @@ class MainActivity : ComponentActivity() {
526529
onNavigateBack = { navController.popBackStack() },
527530
)
528531
}
532+
composable(Screen.LyriconApi.route) {
533+
org.parallel_sekai.kanade.ui.screens.settings.LyriconApiScreen(
534+
viewModel = settingsViewModel,
535+
onNavigateBack = { navController.popBackStack() },
536+
)
537+
}
529538
composable(Screen.MediaNotificationLyrics.route) {
530539
MediaNotificationLyricsScreen(
531540
viewModel = settingsViewModel,
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.parallel_sekai.kanade.data.model
2+
3+
/**
4+
* Settings for Lyricon Provider API integration.
5+
* These settings control how lyrics are formatted and sent to Lyricon-compatible apps.
6+
*/
7+
data class LyriconApiSettings(
8+
/**
9+
* Whether Lyricon API is enabled.
10+
*/
11+
val enabled: Boolean = true,
12+
/**
13+
* Whether to send word-by-word (syllable) lyric data.
14+
*/
15+
val enableWordByWord: Boolean = true,
16+
/**
17+
* Whether to enable scrolling truncate mode.
18+
*/
19+
val scrollingTruncateEnabled: Boolean = false,
20+
/**
21+
* Maximum number of display units (characters/words) to show.
22+
*/
23+
val maxDisplayUnits: Int = 40,
24+
/**
25+
* Whether to enable smart units calculation.
26+
*/
27+
val smartUnitsEnabled: Boolean = true,
28+
/**
29+
* Whether to show timestamp in lyric display.
30+
*/
31+
val showTimestamp: Boolean = false,
32+
/**
33+
* Display states to enable (0=playing, 1=paused, 2=stopped).
34+
*/
35+
val displayStates: Set<Int> = setOf(0, 1, 2),
36+
/**
37+
* Whether to clear lyrics when playback is paused.
38+
*/
39+
val clearOnPause: Boolean = true,
40+
/**
41+
* Whether to display translation in Lyricon.
42+
*/
43+
val displayTranslation: Boolean = true,
44+
/**
45+
* Whether to display romanization in Lyricon.
46+
*/
47+
val displayRoma: Boolean = false,
48+
)

app/src/main/java/org/parallel_sekai/kanade/data/repository/SettingsRepository.kt

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,26 @@ open class SettingsRepository(
114114
private val SUPER_LYRIC_API_CLEAR_ON_PAUSE =
115115
booleanPreferencesKey("super_lyric_api_clear_on_pause")
116116

117+
// Lyricon API keys
118+
private val LYRICON_API_ENABLED = booleanPreferencesKey("lyricon_api_enabled")
119+
private val LYRICON_API_ENABLE_WORD_BY_WORD = booleanPreferencesKey("lyricon_api_enable_word_by_word")
120+
private val LYRICON_API_SCROLLING_TRUNCATE_ENABLED =
121+
booleanPreferencesKey("lyricon_api_scrolling_truncate_enabled")
122+
private val LYRICON_API_MAX_DISPLAY_UNITS =
123+
intPreferencesKey("lyricon_api_max_display_units")
124+
private val LYRICON_API_SMART_UNITS_ENABLED =
125+
booleanPreferencesKey("lyricon_api_smart_units_enabled")
126+
private val LYRICON_API_SHOW_TIMESTAMP =
127+
booleanPreferencesKey("lyricon_api_show_timestamp")
128+
private val LYRICON_API_DISPLAY_STATES =
129+
stringSetPreferencesKey("lyricon_api_display_states")
130+
private val LYRICON_API_CLEAR_ON_PAUSE =
131+
booleanPreferencesKey("lyricon_api_clear_on_pause")
132+
private val LYRICON_API_DISPLAY_TRANSLATION =
133+
booleanPreferencesKey("lyricon_api_display_translation")
134+
private val LYRICON_API_DISPLAY_ROMA =
135+
booleanPreferencesKey("lyricon_api_display_roma")
136+
117137
open val activeScriptIdFlow: Flow<String?> =
118138
context.dataStore.data
119139
.map { preferences -> preferences[ACTIVE_SCRIPT_ID] }
@@ -384,6 +404,27 @@ open class SettingsRepository(
384404
)
385405
}
386406

407+
open val lyriconApiSettingsFlow:
408+
Flow<org.parallel_sekai.kanade.data.model.LyriconApiSettings> =
409+
context.dataStore.data
410+
.map { preferences ->
411+
org.parallel_sekai.kanade.data.model.LyriconApiSettings(
412+
enabled = preferences[LYRICON_API_ENABLED] ?: true,
413+
enableWordByWord = preferences[LYRICON_API_ENABLE_WORD_BY_WORD] ?: true,
414+
scrollingTruncateEnabled =
415+
preferences[LYRICON_API_SCROLLING_TRUNCATE_ENABLED] ?: false,
416+
maxDisplayUnits = preferences[LYRICON_API_MAX_DISPLAY_UNITS] ?: 40,
417+
smartUnitsEnabled = preferences[LYRICON_API_SMART_UNITS_ENABLED] ?: true,
418+
showTimestamp = preferences[LYRICON_API_SHOW_TIMESTAMP] ?: false,
419+
displayStates =
420+
preferences[LYRICON_API_DISPLAY_STATES]?.mapNotNull { it.toIntOrNull() }?.toSet()
421+
?: setOf(0, 1, 2),
422+
clearOnPause = preferences[LYRICON_API_CLEAR_ON_PAUSE] ?: true,
423+
displayTranslation = preferences[LYRICON_API_DISPLAY_TRANSLATION] ?: true,
424+
displayRoma = preferences[LYRICON_API_DISPLAY_ROMA] ?: false,
425+
)
426+
}
427+
387428
open suspend fun updateRepeatMode(mode: Int) {
388429
context.dataStore.edit { preferences ->
389430
preferences[REPEAT_MODE] = mode
@@ -567,4 +608,67 @@ open class SettingsRepository(
567608
preferences[SUPER_LYRIC_API_CLEAR_ON_PAUSE] = enabled
568609
}
569610
}
611+
612+
// Lyricon API update methods
613+
open suspend fun updateLyriconApiEnabled(enabled: Boolean) {
614+
context.dataStore.edit { preferences ->
615+
preferences[LYRICON_API_ENABLED] = enabled
616+
}
617+
}
618+
619+
open suspend fun updateLyriconApiEnableWordByWord(enabled: Boolean) {
620+
context.dataStore.edit { preferences ->
621+
preferences[LYRICON_API_ENABLE_WORD_BY_WORD] = enabled
622+
}
623+
}
624+
625+
open suspend fun updateLyriconApiScrollingTruncateEnabled(enabled: Boolean) {
626+
context.dataStore.edit { preferences ->
627+
preferences[LYRICON_API_SCROLLING_TRUNCATE_ENABLED] = enabled
628+
}
629+
}
630+
631+
open suspend fun updateLyriconApiMaxDisplayUnits(units: Int) {
632+
context.dataStore.edit { preferences ->
633+
preferences[LYRICON_API_MAX_DISPLAY_UNITS] = units.coerceIn(3, 120)
634+
}
635+
}
636+
637+
open suspend fun updateLyriconApiSmartUnitsEnabled(enabled: Boolean) {
638+
context.dataStore.edit { preferences ->
639+
preferences[LYRICON_API_SMART_UNITS_ENABLED] = enabled
640+
}
641+
}
642+
643+
open suspend fun updateLyriconApiShowTimestamp(enabled: Boolean) {
644+
context.dataStore.edit { preferences ->
645+
preferences[LYRICON_API_SHOW_TIMESTAMP] = enabled
646+
}
647+
}
648+
649+
open suspend fun updateLyriconApiDisplayStates(states: Set<Int>) {
650+
context.dataStore.edit { preferences ->
651+
val validStates = states.filter { it in 0..2 }.toSet()
652+
val finalStates = if (validStates.isEmpty()) setOf(0) else validStates
653+
preferences[LYRICON_API_DISPLAY_STATES] = finalStates.map { it.toString() }.toSet()
654+
}
655+
}
656+
657+
open suspend fun updateLyriconApiClearOnPause(enabled: Boolean) {
658+
context.dataStore.edit { preferences ->
659+
preferences[LYRICON_API_CLEAR_ON_PAUSE] = enabled
660+
}
661+
}
662+
663+
open suspend fun updateLyriconApiDisplayTranslation(enabled: Boolean) {
664+
context.dataStore.edit { preferences ->
665+
preferences[LYRICON_API_DISPLAY_TRANSLATION] = enabled
666+
}
667+
}
668+
669+
open suspend fun updateLyriconApiDisplayRoma(enabled: Boolean) {
670+
context.dataStore.edit { preferences ->
671+
preferences[LYRICON_API_DISPLAY_ROMA] = enabled
672+
}
673+
}
570674
}

0 commit comments

Comments
 (0)