@roitium/expo-orpheus 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/android/build.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/expo/modules/orpheus/ExpoOrpheusModule.kt +47 -0
- package/android/src/main/java/expo/modules/orpheus/FloatingLyricsManager.kt +424 -0
- package/android/src/main/java/expo/modules/orpheus/OrpheusHeadlessTaskService.kt +23 -0
- package/android/src/main/java/expo/modules/orpheus/OrpheusMusicService.kt +55 -5
- package/android/src/main/java/expo/modules/orpheus/models/LyricsModels.kt +12 -0
- package/android/src/main/java/expo/modules/orpheus/utils/GeneralStorage.kt +8 -0
- package/android/src/main/res/drawable/outline_close_24.xml +5 -0
- package/android/src/main/res/drawable/outline_lock_24.xml +5 -0
- package/android/src/main/res/drawable/outline_pause_24.xml +5 -0
- package/android/src/main/res/drawable/outline_play_arrow_24.xml +5 -0
- package/android/src/main/res/drawable/outline_skip_next_24.xml +5 -0
- package/android/src/main/res/drawable/outline_skip_previous_24.xml +5 -0
- package/android/src/main/res/values/strings.xml +7 -0
- package/bilibili--BV1DL4y1V7xH--584235509.json +1 -0
- package/build/ExpoOrpheusModule.d.ts +8 -0
- package/build/ExpoOrpheusModule.d.ts.map +1 -1
- package/build/ExpoOrpheusModule.js.map +1 -1
- package/build/index.d.ts +7 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +5 -0
- package/build/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ExpoOrpheusModule.ts +9 -0
- package/src/index.ts +17 -0
package/README.md
CHANGED
|
@@ -25,6 +25,17 @@ Orpheus 集成了 Media3 的 DownloadManager,抛弃了原先 BBPlayer 中繁
|
|
|
25
25
|
|
|
26
26
|
默认启用,只对未缓存的 b 站音频生效
|
|
27
27
|
|
|
28
|
+
## 桌面歌词
|
|
29
|
+
|
|
30
|
+
相信聪明的你去看一下公开方法名就知道怎么使用了!
|
|
31
|
+
(需要注意的是,在切歌时,会自动清空当前的歌词)
|
|
32
|
+
|
|
33
|
+
## 注意事项
|
|
34
|
+
|
|
35
|
+
该库一些修改比较随意,我怕后续我自己都忘了,所以在这里进行一下记录。
|
|
36
|
+
|
|
37
|
+
1. `onTrackStarted` 事件在 v0.9.0 版本后不再存在。需要使用 `registerOrpheusHeadlessTask` 注册事件并自行判断事件是否为 `onTrackStarted`
|
|
38
|
+
|
|
28
39
|
## 使用
|
|
29
40
|
|
|
30
41
|
虽然该包是公开的,但仍然主要供 BBPlayer 内部使用。可能不会有完整的文档覆盖。我们欢迎你 fork 后自行修改使用。
|
package/android/build.gradle
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
7
7
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
|
8
8
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
|
9
|
+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
|
9
10
|
|
|
10
11
|
<uses-permission android:name="android.permission.INTERNET" />
|
|
11
12
|
|
|
@@ -30,5 +31,6 @@
|
|
|
30
31
|
<category android:name="android.intent.category.DEFAULT" />
|
|
31
32
|
</intent-filter>
|
|
32
33
|
</service>
|
|
34
|
+
<service android:name=".OrpheusHeadlessTaskService" />
|
|
33
35
|
</application>
|
|
34
36
|
</manifest>
|
|
@@ -230,6 +230,15 @@ class ExpoOrpheusModule : Module() {
|
|
|
230
230
|
GeneralStorage.isAutoplayOnStartEnabled()
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
Constant("isDesktopLyricsShown") {
|
|
234
|
+
GeneralStorage.isDesktopLyricsShown()
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
Constant("isDesktopLyricsLocked") {
|
|
238
|
+
GeneralStorage.isDesktopLyricsLocked()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
233
242
|
Function("setAutoplayOnStartEnabled") { enabled: Boolean ->
|
|
234
243
|
GeneralStorage.setAutoplayOnStartEnabled(enabled)
|
|
235
244
|
}
|
|
@@ -592,6 +601,44 @@ class ExpoOrpheusModule : Module() {
|
|
|
592
601
|
}
|
|
593
602
|
return@AsyncFunction result
|
|
594
603
|
}
|
|
604
|
+
|
|
605
|
+
AsyncFunction("checkOverlayPermission") {
|
|
606
|
+
val context = appContext.reactContext ?: return@AsyncFunction false
|
|
607
|
+
android.provider.Settings.canDrawOverlays(context)
|
|
608
|
+
}.runOnQueue(Queues.MAIN)
|
|
609
|
+
|
|
610
|
+
AsyncFunction("requestOverlayPermission") {
|
|
611
|
+
val context = appContext.reactContext ?: return@AsyncFunction false
|
|
612
|
+
if (!android.provider.Settings.canDrawOverlays(context)) {
|
|
613
|
+
val intent = android.content.Intent(
|
|
614
|
+
android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
|
|
615
|
+
"package:${context.packageName}".toUri()
|
|
616
|
+
)
|
|
617
|
+
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
618
|
+
context.startActivity(intent)
|
|
619
|
+
}
|
|
620
|
+
}.runOnQueue(Queues.MAIN)
|
|
621
|
+
|
|
622
|
+
AsyncFunction("showDesktopLyrics") {
|
|
623
|
+
OrpheusMusicService.instance?.floatingLyricsManager?.show()
|
|
624
|
+
}.runOnQueue(Queues.MAIN)
|
|
625
|
+
|
|
626
|
+
AsyncFunction("hideDesktopLyrics") {
|
|
627
|
+
OrpheusMusicService.instance?.floatingLyricsManager?.hide()
|
|
628
|
+
}.runOnQueue(Queues.MAIN)
|
|
629
|
+
|
|
630
|
+
AsyncFunction("setDesktopLyrics") { lyricsJson: String ->
|
|
631
|
+
try {
|
|
632
|
+
val data = gson.fromJson(lyricsJson, expo.modules.orpheus.models.LyricsData::class.java)
|
|
633
|
+
OrpheusMusicService.instance?.floatingLyricsManager?.setLyrics(data.lyrics, data.offset)
|
|
634
|
+
} catch (e: Exception) {
|
|
635
|
+
e.printStackTrace()
|
|
636
|
+
}
|
|
637
|
+
}.runOnQueue(Queues.MAIN)
|
|
638
|
+
|
|
639
|
+
AsyncFunction("setDesktopLyricsLocked") { locked: Boolean ->
|
|
640
|
+
OrpheusMusicService.instance?.floatingLyricsManager?.setLocked(locked)
|
|
641
|
+
}.runOnQueue(Queues.MAIN)
|
|
595
642
|
}
|
|
596
643
|
|
|
597
644
|
private fun getDownloadMap(download: Download): Map<String, Any> {
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
package expo.modules.orpheus
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.PixelFormat
|
|
6
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.os.Handler
|
|
9
|
+
import android.os.Looper
|
|
10
|
+
import android.view.ContextThemeWrapper
|
|
11
|
+
import android.view.Gravity
|
|
12
|
+
import android.view.MotionEvent
|
|
13
|
+
import android.view.View
|
|
14
|
+
import android.view.WindowManager
|
|
15
|
+
import android.widget.FrameLayout
|
|
16
|
+
import android.widget.HorizontalScrollView
|
|
17
|
+
import android.widget.ImageButton
|
|
18
|
+
import android.widget.ImageView
|
|
19
|
+
import android.widget.LinearLayout
|
|
20
|
+
import android.widget.SeekBar
|
|
21
|
+
import android.widget.TextView
|
|
22
|
+
import androidx.core.graphics.toColorInt
|
|
23
|
+
import androidx.media3.common.Player
|
|
24
|
+
import androidx.media3.exoplayer.ExoPlayer
|
|
25
|
+
import expo.modules.orpheus.models.LyricsLine
|
|
26
|
+
import expo.modules.orpheus.utils.GeneralStorage
|
|
27
|
+
import kotlin.math.abs
|
|
28
|
+
|
|
29
|
+
class FloatingLyricsManager(context: Context, private val player: ExoPlayer?) {
|
|
30
|
+
|
|
31
|
+
private val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
|
|
32
|
+
private var floatingView: FrameLayout? = null
|
|
33
|
+
private var lyricsTextView: TextView? = null
|
|
34
|
+
private var settingsPanel: LinearLayout? = null
|
|
35
|
+
private var playPauseButton: ImageButton? = null
|
|
36
|
+
private var params: WindowManager.LayoutParams? = null
|
|
37
|
+
|
|
38
|
+
private val uiContext = ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault)
|
|
39
|
+
|
|
40
|
+
private var lyrics: List<LyricsLine> = emptyList()
|
|
41
|
+
private var offset: Double = 0.0
|
|
42
|
+
private var currentLineIndex = -1
|
|
43
|
+
|
|
44
|
+
private var textSize = 18f
|
|
45
|
+
private var textColor = "#FFFFFF".toColorInt()
|
|
46
|
+
private var isLocked = false
|
|
47
|
+
|
|
48
|
+
private val colors = listOf(
|
|
49
|
+
"#FFFFFF", "#CCCCCC",
|
|
50
|
+
"#FF0000", "#FFC107",
|
|
51
|
+
"#2196F3", "#9C27B0",
|
|
52
|
+
"#000000", "#4CAF50"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
private val playerListener = object : Player.Listener {
|
|
56
|
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
57
|
+
updatePlayPauseButton(isPlaying)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
init {
|
|
62
|
+
isLocked = GeneralStorage.isDesktopLyricsLocked()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fun show() {
|
|
66
|
+
if (floatingView != null) return
|
|
67
|
+
|
|
68
|
+
val type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
69
|
+
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
|
70
|
+
} else {
|
|
71
|
+
WindowManager.LayoutParams.TYPE_PHONE
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
params = WindowManager.LayoutParams(
|
|
75
|
+
WindowManager.LayoutParams.MATCH_PARENT,
|
|
76
|
+
WindowManager.LayoutParams.WRAP_CONTENT,
|
|
77
|
+
type,
|
|
78
|
+
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
|
|
79
|
+
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
|
|
80
|
+
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
|
|
81
|
+
PixelFormat.TRANSLUCENT
|
|
82
|
+
)
|
|
83
|
+
params?.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
84
|
+
params?.y = 200
|
|
85
|
+
|
|
86
|
+
createView()
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
windowManager.addView(floatingView, params)
|
|
90
|
+
player?.addListener(playerListener)
|
|
91
|
+
updatePlayPauseButton(player?.isPlaying == true)
|
|
92
|
+
GeneralStorage.setDesktopLyricsShown(true)
|
|
93
|
+
} catch (e: Exception) {
|
|
94
|
+
e.printStackTrace()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fun hide() {
|
|
99
|
+
floatingView?.let {
|
|
100
|
+
try {
|
|
101
|
+
windowManager.removeView(it)
|
|
102
|
+
} catch (e: Exception) {
|
|
103
|
+
e.printStackTrace()
|
|
104
|
+
}
|
|
105
|
+
player?.removeListener(playerListener)
|
|
106
|
+
floatingView = null
|
|
107
|
+
lyricsTextView = null
|
|
108
|
+
settingsPanel = null
|
|
109
|
+
playPauseButton = null
|
|
110
|
+
GeneralStorage.setDesktopLyricsShown(false)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fun setLyrics(newLyrics: List<LyricsLine>, newOffset: Double = 0.0) {
|
|
115
|
+
lyrics = newLyrics.sortedBy { it.timestamp }
|
|
116
|
+
offset = newOffset
|
|
117
|
+
currentLineIndex = -1
|
|
118
|
+
updateText(null)
|
|
119
|
+
if (lyrics.isEmpty()) {
|
|
120
|
+
Handler(Looper.getMainLooper()).post {
|
|
121
|
+
settingsPanel?.visibility = View.GONE
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fun updateTime(seconds: Double) {
|
|
127
|
+
if (floatingView == null || lyrics.isEmpty()) return
|
|
128
|
+
|
|
129
|
+
val adjustedTime = seconds - offset
|
|
130
|
+
val index = lyrics.indexOfLast { it.timestamp <= adjustedTime }
|
|
131
|
+
if (index != currentLineIndex && index >= 0) {
|
|
132
|
+
currentLineIndex = index
|
|
133
|
+
updateText(lyrics[index])
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fun setLocked(locked: Boolean) {
|
|
138
|
+
isLocked = locked
|
|
139
|
+
GeneralStorage.setDesktopLyricsLocked(locked)
|
|
140
|
+
updateTouchableFlags()
|
|
141
|
+
if (locked) {
|
|
142
|
+
settingsPanel?.visibility = View.GONE
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private fun updateTouchableFlags() {
|
|
147
|
+
val p = params ?: return
|
|
148
|
+
if (isLocked) {
|
|
149
|
+
p.flags = p.flags or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
|
|
150
|
+
} else {
|
|
151
|
+
p.flags = p.flags and WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE.inv()
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
if (floatingView?.isAttachedToWindow == true) {
|
|
155
|
+
windowManager.updateViewLayout(floatingView, p)
|
|
156
|
+
}
|
|
157
|
+
} catch (e: Exception) {
|
|
158
|
+
e.printStackTrace()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fun updateText(line: LyricsLine?) {
|
|
163
|
+
Handler(Looper.getMainLooper()).post {
|
|
164
|
+
if (line == null) {
|
|
165
|
+
lyricsTextView?.text = ""
|
|
166
|
+
} else {
|
|
167
|
+
val text = if (line.translation.isNullOrEmpty()) {
|
|
168
|
+
line.text
|
|
169
|
+
} else {
|
|
170
|
+
"${line.text}\n${line.translation}"
|
|
171
|
+
}
|
|
172
|
+
lyricsTextView?.text = text
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private fun updatePlayPauseButton(isPlaying: Boolean) {
|
|
178
|
+
Handler(Looper.getMainLooper()).post {
|
|
179
|
+
if (isPlaying) {
|
|
180
|
+
playPauseButton?.setImageResource(R.drawable.outline_pause_24)
|
|
181
|
+
} else {
|
|
182
|
+
playPauseButton?.setImageResource(R.drawable.outline_play_arrow_24)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fun createView() {
|
|
188
|
+
val frame = FrameLayout(uiContext)
|
|
189
|
+
|
|
190
|
+
val contentContainer = LinearLayout(uiContext).apply {
|
|
191
|
+
orientation = LinearLayout.VERTICAL
|
|
192
|
+
gravity = Gravity.CENTER_HORIZONTAL
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Lyrics View
|
|
196
|
+
lyricsTextView = TextView(uiContext).apply {
|
|
197
|
+
text = context.getString(R.string.desktop_lyrics)
|
|
198
|
+
textSize = this@FloatingLyricsManager.textSize
|
|
199
|
+
setTextColor(this@FloatingLyricsManager.textColor)
|
|
200
|
+
setShadowLayer(4f, 0f, 0f, Color.BLACK)
|
|
201
|
+
gravity = Gravity.CENTER
|
|
202
|
+
setPadding(20, 10, 20, 10)
|
|
203
|
+
|
|
204
|
+
setOnClickListener {
|
|
205
|
+
toggleSettings()
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Settings Panel
|
|
210
|
+
settingsPanel = createSettingsPanel()
|
|
211
|
+
settingsPanel?.visibility = View.GONE
|
|
212
|
+
|
|
213
|
+
contentContainer.addView(lyricsTextView, LinearLayout.LayoutParams(
|
|
214
|
+
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
215
|
+
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
216
|
+
).apply {
|
|
217
|
+
bottomMargin = 10
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
contentContainer.addView(settingsPanel, LinearLayout.LayoutParams(
|
|
221
|
+
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
222
|
+
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
223
|
+
))
|
|
224
|
+
|
|
225
|
+
frame.addView(contentContainer, FrameLayout.LayoutParams(
|
|
226
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
227
|
+
FrameLayout.LayoutParams.WRAP_CONTENT,
|
|
228
|
+
Gravity.CENTER_HORIZONTAL
|
|
229
|
+
))
|
|
230
|
+
|
|
231
|
+
var initialY = 0
|
|
232
|
+
var initialTouchY = 0f
|
|
233
|
+
var isClick = false
|
|
234
|
+
val touchSlop = 10
|
|
235
|
+
|
|
236
|
+
lyricsTextView?.setOnTouchListener {
|
|
237
|
+
v, event ->
|
|
238
|
+
if (isLocked) return@setOnTouchListener false
|
|
239
|
+
|
|
240
|
+
when (event.action) {
|
|
241
|
+
MotionEvent.ACTION_DOWN -> {
|
|
242
|
+
initialY = params?.y ?: 0
|
|
243
|
+
initialTouchY = event.rawY
|
|
244
|
+
isClick = true
|
|
245
|
+
true
|
|
246
|
+
}
|
|
247
|
+
MotionEvent.ACTION_MOVE -> {
|
|
248
|
+
val dy = (event.rawY - initialTouchY).toInt()
|
|
249
|
+
if (abs(dy) > touchSlop) {
|
|
250
|
+
isClick = false
|
|
251
|
+
params?.y = initialY + dy
|
|
252
|
+
try {
|
|
253
|
+
windowManager.updateViewLayout(floatingView, params)
|
|
254
|
+
} catch (e: Exception) {
|
|
255
|
+
e.printStackTrace()
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
true
|
|
259
|
+
}
|
|
260
|
+
MotionEvent.ACTION_UP -> {
|
|
261
|
+
if (isClick) {
|
|
262
|
+
v.performClick()
|
|
263
|
+
}
|
|
264
|
+
true
|
|
265
|
+
}
|
|
266
|
+
else -> false
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
floatingView = frame
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
private fun createSettingsPanel(): LinearLayout {
|
|
274
|
+
val panel = LinearLayout(uiContext).apply {
|
|
275
|
+
orientation = LinearLayout.VERTICAL
|
|
276
|
+
val gd = GradientDrawable()
|
|
277
|
+
gd.setColor("#DD1A1A1A".toColorInt())
|
|
278
|
+
gd.cornerRadius = 32f
|
|
279
|
+
background = gd
|
|
280
|
+
setPadding(32, 24, 32, 24)
|
|
281
|
+
gravity = Gravity.CENTER_HORIZONTAL
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Row 1: Playback Controls
|
|
285
|
+
val controlsRow = LinearLayout(uiContext).apply {
|
|
286
|
+
orientation = LinearLayout.HORIZONTAL
|
|
287
|
+
gravity = Gravity.CENTER
|
|
288
|
+
setPadding(0, 0, 0, 24)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
val prevBtn = createControlButton(R.drawable.outline_skip_previous_24) { player?.seekToPreviousMediaItem() }
|
|
292
|
+
playPauseButton = createControlButton(R.drawable.outline_play_arrow_24) {
|
|
293
|
+
if (player?.isPlaying == true) player.pause() else player?.play()
|
|
294
|
+
}.apply { textSize = 28f }
|
|
295
|
+
val nextBtn = createControlButton(R.drawable.outline_skip_next_24) { player?.seekToNextMediaItem() }
|
|
296
|
+
|
|
297
|
+
controlsRow.addView(prevBtn)
|
|
298
|
+
controlsRow.addView(View(uiContext), LinearLayout.LayoutParams(40, 1)) // Spacer
|
|
299
|
+
controlsRow.addView(playPauseButton)
|
|
300
|
+
controlsRow.addView(View(uiContext), LinearLayout.LayoutParams(40, 1)) // Spacer
|
|
301
|
+
controlsRow.addView(nextBtn)
|
|
302
|
+
|
|
303
|
+
// Row 2: Size Slider
|
|
304
|
+
val sizeRow = LinearLayout(uiContext).apply {
|
|
305
|
+
orientation = LinearLayout.HORIZONTAL
|
|
306
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
307
|
+
setPadding(0, 0, 0, 16)
|
|
308
|
+
}
|
|
309
|
+
val sizeLabel = TextView(uiContext).apply {
|
|
310
|
+
text = context.getString(R.string.size)
|
|
311
|
+
setTextColor(Color.LTGRAY)
|
|
312
|
+
textSize = 12f
|
|
313
|
+
}
|
|
314
|
+
val sizeSeekBar = SeekBar(uiContext).apply {
|
|
315
|
+
max = 30
|
|
316
|
+
progress = (textSize - 10).toInt()
|
|
317
|
+
setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
|
318
|
+
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
|
|
319
|
+
textSize = (p1 + 10).toFloat()
|
|
320
|
+
lyricsTextView?.textSize = textSize
|
|
321
|
+
}
|
|
322
|
+
override fun onStartTrackingTouch(p0: SeekBar?) {}
|
|
323
|
+
override fun onStopTrackingTouch(p0: SeekBar?) {}
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
sizeRow.addView(sizeLabel)
|
|
327
|
+
sizeRow.addView(sizeSeekBar, LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f).apply {
|
|
328
|
+
marginStart = 16
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
// Row 3: Colors
|
|
332
|
+
val colorsScroll = HorizontalScrollView(uiContext).apply {
|
|
333
|
+
isHorizontalScrollBarEnabled = false
|
|
334
|
+
overScrollMode = View.OVER_SCROLL_NEVER
|
|
335
|
+
setPadding(0, 0, 0, 24)
|
|
336
|
+
}
|
|
337
|
+
val colorContainer = LinearLayout(uiContext).apply {
|
|
338
|
+
orientation = LinearLayout.HORIZONTAL
|
|
339
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
colors.forEach { colorString ->
|
|
343
|
+
val color = colorString.toColorInt()
|
|
344
|
+
val colorView = View(uiContext).apply {
|
|
345
|
+
val size = 60
|
|
346
|
+
layoutParams = LinearLayout.LayoutParams(size, size).apply {
|
|
347
|
+
marginEnd = 16
|
|
348
|
+
}
|
|
349
|
+
val circle = GradientDrawable()
|
|
350
|
+
circle.shape = GradientDrawable.OVAL
|
|
351
|
+
circle.setColor(color)
|
|
352
|
+
circle.setStroke(2, Color.DKGRAY)
|
|
353
|
+
background = circle
|
|
354
|
+
|
|
355
|
+
setOnClickListener {
|
|
356
|
+
textColor = color
|
|
357
|
+
lyricsTextView?.setTextColor(textColor)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
colorContainer.addView(colorView)
|
|
361
|
+
}
|
|
362
|
+
colorsScroll.addView(colorContainer)
|
|
363
|
+
|
|
364
|
+
// Row 4: Actions (Lock & Close)
|
|
365
|
+
val actionsRow = LinearLayout(uiContext).apply {
|
|
366
|
+
orientation = LinearLayout.HORIZONTAL
|
|
367
|
+
gravity = Gravity.CENTER
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
val lockBtn = createActionButton(R.string.lock, R.drawable.outline_lock_24) { setLocked(true) }
|
|
371
|
+
val closeBtn = createActionButton(R.string.close, R.drawable.outline_close_24) { settingsPanel?.visibility = View.GONE }
|
|
372
|
+
|
|
373
|
+
actionsRow.addView(lockBtn)
|
|
374
|
+
actionsRow.addView(View(uiContext), LinearLayout.LayoutParams(32, 1)) // Spacer
|
|
375
|
+
actionsRow.addView(closeBtn)
|
|
376
|
+
|
|
377
|
+
panel.addView(controlsRow)
|
|
378
|
+
panel.addView(sizeRow, LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT))
|
|
379
|
+
panel.addView(colorsScroll)
|
|
380
|
+
panel.addView(actionsRow)
|
|
381
|
+
|
|
382
|
+
return panel
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private fun createControlButton(resId: Int, onClick: () -> Unit): ImageButton {
|
|
386
|
+
return ImageButton(uiContext).apply {
|
|
387
|
+
setImageResource(resId)
|
|
388
|
+
setBackgroundColor(Color.TRANSPARENT)
|
|
389
|
+
setColorFilter(Color.WHITE)
|
|
390
|
+
scaleType = ImageView.ScaleType.FIT_CENTER
|
|
391
|
+
setPadding(16, 16, 16, 16)
|
|
392
|
+
setOnClickListener { onClick() }
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private fun createActionButton(textId: Int, iconId: Int, onClick: () -> Unit): TextView {
|
|
397
|
+
return TextView(uiContext).apply {
|
|
398
|
+
text = context.getString(textId)
|
|
399
|
+
textSize = 14f
|
|
400
|
+
setTextColor(Color.WHITE)
|
|
401
|
+
gravity = Gravity.CENTER
|
|
402
|
+
setPadding(32, 16, 32, 16)
|
|
403
|
+
|
|
404
|
+
setCompoundDrawablesWithIntrinsicBounds(iconId, 0, 0, 0)
|
|
405
|
+
|
|
406
|
+
compoundDrawablePadding = 12
|
|
407
|
+
|
|
408
|
+
background = GradientDrawable().apply {
|
|
409
|
+
setColor("#33FFFFFF".toColorInt())
|
|
410
|
+
cornerRadius = 50f
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
setOnClickListener { onClick() }
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private fun toggleSettings() {
|
|
418
|
+
if (settingsPanel?.visibility == View.VISIBLE) {
|
|
419
|
+
settingsPanel?.visibility = View.GONE
|
|
420
|
+
} else {
|
|
421
|
+
settingsPanel?.visibility = View.VISIBLE
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
package expo.modules.orpheus
|
|
2
|
+
|
|
3
|
+
import android.content.Intent
|
|
4
|
+
import com.facebook.react.HeadlessJsTaskService
|
|
5
|
+
import com.facebook.react.bridge.Arguments
|
|
6
|
+
import com.facebook.react.jstasks.HeadlessJsTaskConfig
|
|
7
|
+
|
|
8
|
+
class OrpheusHeadlessTaskService : HeadlessJsTaskService() {
|
|
9
|
+
|
|
10
|
+
override fun getTaskConfig(intent: Intent?): HeadlessJsTaskConfig? {
|
|
11
|
+
val extras = intent?.extras
|
|
12
|
+
return if (extras != null) {
|
|
13
|
+
HeadlessJsTaskConfig(
|
|
14
|
+
"OrpheusHeadlessTask",
|
|
15
|
+
Arguments.fromBundle(extras),
|
|
16
|
+
5000, // timeout for the task
|
|
17
|
+
true // allowed in foreground
|
|
18
|
+
)
|
|
19
|
+
} else {
|
|
20
|
+
null
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package expo.modules.orpheus
|
|
2
2
|
|
|
3
|
+
import android.R
|
|
3
4
|
import android.app.PendingIntent
|
|
4
5
|
import android.content.Intent
|
|
5
6
|
import android.util.Log
|
|
@@ -37,6 +38,19 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
37
38
|
private var volumeFadeJob: Job? = null
|
|
38
39
|
private var scope = MainScope()
|
|
39
40
|
|
|
41
|
+
lateinit var floatingLyricsManager: FloatingLyricsManager
|
|
42
|
+
private val serviceHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
43
|
+
private val lyricsUpdateRunnable = object : Runnable {
|
|
44
|
+
override fun run() {
|
|
45
|
+
player?.let {
|
|
46
|
+
if (it.isPlaying) {
|
|
47
|
+
floatingLyricsManager.updateTime(it.currentPosition / 1000.0)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
serviceHandler.postDelayed(this, 200)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
40
54
|
companion object {
|
|
41
55
|
var instance: OrpheusMusicService? = null
|
|
42
56
|
private set(value) {
|
|
@@ -84,6 +98,11 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
84
98
|
)
|
|
85
99
|
.build()
|
|
86
100
|
|
|
101
|
+
floatingLyricsManager = FloatingLyricsManager(this, player)
|
|
102
|
+
if (GeneralStorage.isDesktopLyricsShown()) {
|
|
103
|
+
serviceHandler.post { floatingLyricsManager.show() }
|
|
104
|
+
}
|
|
105
|
+
|
|
87
106
|
setupListeners()
|
|
88
107
|
|
|
89
108
|
var launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
|
@@ -124,6 +143,8 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
124
143
|
}
|
|
125
144
|
|
|
126
145
|
override fun onDestroy() {
|
|
146
|
+
serviceHandler.removeCallbacks(lyricsUpdateRunnable)
|
|
147
|
+
floatingLyricsManager.hide()
|
|
127
148
|
scope.cancel()
|
|
128
149
|
instance = null
|
|
129
150
|
|
|
@@ -159,13 +180,10 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
159
180
|
.build()
|
|
160
181
|
}
|
|
161
182
|
|
|
162
|
-
/**
|
|
163
|
-
* 修复 UnsupportedOperationException 的关键!
|
|
164
|
-
* 当系统尝试恢复播放(比如从“最近播放”卡片点击)时触发。
|
|
165
|
-
*/
|
|
166
183
|
override fun onPlaybackResumption(
|
|
167
184
|
mediaSession: MediaSession,
|
|
168
|
-
controller: MediaSession.ControllerInfo
|
|
185
|
+
controller: MediaSession.ControllerInfo,
|
|
186
|
+
isPlayback: Boolean
|
|
169
187
|
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
|
|
170
188
|
return Futures.immediateFuture(
|
|
171
189
|
MediaSession.MediaItemsWithStartPosition(
|
|
@@ -202,16 +220,48 @@ class OrpheusMusicService : MediaLibraryService() {
|
|
|
202
220
|
|
|
203
221
|
player.playWhenReady = GeneralStorage.isAutoplayOnStartEnabled()
|
|
204
222
|
player.prepare()
|
|
223
|
+
|
|
224
|
+
// 软件冷启动时,恢复的歌曲并不会触发 onMediaTransition 事件,我们需要手动补发一个
|
|
225
|
+
if (player.currentMediaItem != null) {
|
|
226
|
+
sendTrackStartEvent(player.currentMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED)
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@OptIn(UnstableApi::class)
|
|
232
|
+
private fun sendTrackStartEvent(mediaItem: androidx.media3.common.MediaItem?, reason: Int) {
|
|
233
|
+
if (mediaItem == null) return
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
val intent = Intent(this, OrpheusHeadlessTaskService::class.java)
|
|
237
|
+
intent.putExtra("eventName", "onTrackStarted")
|
|
238
|
+
intent.putExtra("trackId", mediaItem.mediaId)
|
|
239
|
+
intent.putExtra("reason", reason)
|
|
240
|
+
startService(intent)
|
|
241
|
+
} catch (e: Exception) {
|
|
242
|
+
e.printStackTrace()
|
|
205
243
|
}
|
|
206
244
|
}
|
|
207
245
|
|
|
208
246
|
private fun setupListeners() {
|
|
209
247
|
player?.addListener(object : Player.Listener {
|
|
248
|
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
249
|
+
if (isPlaying) {
|
|
250
|
+
serviceHandler.removeCallbacks(lyricsUpdateRunnable)
|
|
251
|
+
serviceHandler.post(lyricsUpdateRunnable)
|
|
252
|
+
} else {
|
|
253
|
+
serviceHandler.removeCallbacks(lyricsUpdateRunnable)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
210
257
|
@OptIn(UnstableApi::class)
|
|
211
258
|
override fun onMediaItemTransition(
|
|
212
259
|
mediaItem: androidx.media3.common.MediaItem?,
|
|
213
260
|
reason: Int
|
|
214
261
|
) {
|
|
262
|
+
sendTrackStartEvent(mediaItem, reason)
|
|
263
|
+
|
|
264
|
+
floatingLyricsManager.setLyrics(emptyList())
|
|
215
265
|
saveCurrentQueue()
|
|
216
266
|
val uri = mediaItem?.localConfiguration?.uri?.toString() ?: return
|
|
217
267
|
|
|
@@ -21,6 +21,8 @@ object GeneralStorage {
|
|
|
21
21
|
private const val KEY_SAVED_REPEAT_MODE = "saved_repeat_mode"
|
|
22
22
|
private const val KEY_SAVED_SHUFFLE_MODE = "saved_shuffle_mode"
|
|
23
23
|
private const val KEY_AUTOPLAY_ON_START_ENABLED = "config_autoplay_on_start_enabled"
|
|
24
|
+
private const val KEY_DESKTOP_LYRICS_SHOWN = "state_desktop_lyrics_shown"
|
|
25
|
+
private const val KEY_DESKTOP_LYRICS_LOCKED = "state_desktop_lyrics_locked"
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
@Synchronized
|
|
@@ -122,4 +124,10 @@ object GeneralStorage {
|
|
|
122
124
|
fun getSavedPosition() = kv?.decodeLong(KEY_SAVED_POSITION, 0L) ?: 0L
|
|
123
125
|
fun getRepeatMode() = kv?.decodeInt(KEY_SAVED_REPEAT_MODE, 0) ?: 0
|
|
124
126
|
fun getShuffleMode() = kv?.decodeBool(KEY_SAVED_SHUFFLE_MODE, false) ?: false
|
|
127
|
+
|
|
128
|
+
fun isDesktopLyricsShown() = kv?.decodeBool(KEY_DESKTOP_LYRICS_SHOWN, false) ?: false
|
|
129
|
+
fun setDesktopLyricsShown(shown: Boolean) = safeKv.encode(KEY_DESKTOP_LYRICS_SHOWN, shown)
|
|
130
|
+
|
|
131
|
+
fun isDesktopLyricsLocked() = kv?.decodeBool(KEY_DESKTOP_LYRICS_LOCKED, false) ?: false
|
|
132
|
+
fun setDesktopLyricsLocked(locked: Boolean) = safeKv.encode(KEY_DESKTOP_LYRICS_LOCKED, locked)
|
|
125
133
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
|
2
|
+
|
|
3
|
+
<path android:fillColor="@android:color/white" android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
|
|
4
|
+
|
|
5
|
+
</vector>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
|
2
|
+
|
|
3
|
+
<path android:fillColor="@android:color/white" android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,400Q160,367 183.5,343.5Q207,320 240,320L280,320L280,240Q280,157 338.5,98.5Q397,40 480,40Q563,40 621.5,98.5Q680,157 680,240L680,320L720,320Q753,320 776.5,343.5Q800,367 800,400L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,400Q720,400 720,400Q720,400 720,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800ZM480,680Q513,680 536.5,656.5Q560,633 560,600Q560,567 536.5,543.5Q513,520 480,520Q447,520 423.5,543.5Q400,567 400,600Q400,633 423.5,656.5Q447,680 480,680ZM360,320L600,320L600,240Q600,190 565,155Q530,120 480,120Q430,120 395,155Q360,190 360,240L360,320ZM240,800Q240,800 240,800Q240,800 240,800L240,400Q240,400 240,400Q240,400 240,400L240,400Q240,400 240,400Q240,400 240,400L240,800Q240,800 240,800Q240,800 240,800Z"/>
|
|
4
|
+
|
|
5
|
+
</vector>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
|
2
|
+
|
|
3
|
+
<path android:fillColor="@android:color/white" android:pathData="M520,760L520,200L760,200L760,760L520,760ZM200,760L200,200L440,200L440,760L200,760ZM600,680L680,680L680,280L600,280L600,680ZM280,680L360,680L360,280L280,280L280,680ZM280,280L280,280L280,680L280,680L280,280ZM600,280L600,280L600,680L600,680L600,280Z"/>
|
|
4
|
+
|
|
5
|
+
</vector>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
|
2
|
+
|
|
3
|
+
<path android:fillColor="@android:color/white" android:pathData="M320,760L320,200L760,480L320,760ZM400,480L400,480L400,480ZM400,614L610,480L400,346L400,614Z"/>
|
|
4
|
+
|
|
5
|
+
</vector>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
|
2
|
+
|
|
3
|
+
<path android:fillColor="@android:color/white" android:pathData="M660,720L660,240L740,240L740,720L660,720ZM220,720L220,240L580,480L220,720ZM300,480L300,480L300,480ZM300,570L436,480L300,390L300,570Z"/>
|
|
4
|
+
|
|
5
|
+
</vector>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
|
2
|
+
|
|
3
|
+
<path android:fillColor="@android:color/white" android:pathData="M220,720L220,240L300,240L300,720L220,720ZM740,720L380,480L740,240L740,720ZM660,480L660,480L660,480ZM660,570L660,390L524,480L660,570Z"/>
|
|
4
|
+
|
|
5
|
+
</vector>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"tags":{"00":"51.81","01":"47.24","02":"53.70","03":"39.62"},"lyrics":[{"timestamp":0,"text":"作词 : えびかれー伯爵/知代/くりーむぱい太郎"},{"timestamp":1,"text":"作曲 : えびかれー伯爵"},{"timestamp":7.66,"text":"I can't be a superstar","translation":"我成为不了超级明星"},{"timestamp":10.66,"text":"Not happy like in the silver screen","translation":"无法像银幕里那样幸福美满"},{"timestamp":15.05,"text":"But we can get everything here","translation":"但在这里,我们能获得想要的一切"},{"timestamp":37.18,"text":"It's kinda fiction","translation":"这是一篇小说"},{"timestamp":38.56,"text":"It's a made up story","translation":"是虚构的故事"},{"timestamp":40.18,"text":"That would be too good for me, sometimes","translation":"虽然它有时也对我太过友好"},{"timestamp":44.6,"text":"But the beginning is from that point","translation":"但一切都要从那里说起"},{"timestamp":52.07,"text":"Perfect story is waiting on me who failure","translation":"完美的故事正等着失败的我书写"},{"timestamp":59.51,"text":"Even though I'm not honest man","translation":"我并非诚实之人,但也无妨"},{"timestamp":66.7,"text":"It's a breeze","translation":"这事小菜一碟"},{"timestamp":70.4,"text":"I can't be a superstar","translation":"我成为不了超级明星"},{"timestamp":73.43,"text":"Not happy like in the silver screen","translation":"无法像银幕里那样幸福美满"},{"timestamp":77.77,"text":"But we can get everything here","translation":"但在这里,我们能获得想要的一切"},{"timestamp":92.58,"text":"It's Low-key life","translation":"这是低调生活"},{"timestamp":93.94,"text":"It's so small role","translation":"属于边缘配角"},{"timestamp":95.59,"text":"Cursed this terrible doom fades me away","translation":"尽管被诅咒的可怕厄运企图将我扼杀"},{"timestamp":99.92,"text":"But it's a chance to change the life brand new","translation":"但同时它也是令生活焕然一新的良机"},{"timestamp":107.52,"text":"Perfect story is waiting on me who failure","translation":"完美的故事正等着失败的我书写"},{"timestamp":114.92,"text":"Even though I'm not honest man","translation":"我并非诚实之人,但也无妨"},{"timestamp":122.13,"text":"It's a breeze","translation":"这事小菜一碟"},{"timestamp":125.78,"text":"I can't be a superstar","translation":"我成为不了超级明星"},{"timestamp":128.76,"text":"Not happy like in the silver screen","translation":"无法像银幕里那样幸福美满"},{"timestamp":133.18,"text":"But we can get everything here","translation":"但在这里,我们能获得想要的一切"},{"timestamp":155.51,"text":"It's kinda fiction","translation":"这是一篇小说"},{"timestamp":157.39,"text":"It's a made up story","translation":"是虚构的故事"},{"timestamp":159.27,"text":"That would be too good for me, sometimes","translation":"虽然它有时也对我太过友好"},{"timestamp":162.96,"text":"But the beginning is from that point","translation":"但一切都要从那里说起"},{"timestamp":170.01,"text":"It's a breeze","translation":"这事小菜一碟"},{"timestamp":173.8,"text":"I can't be a superstar","translation":"我成为不了超级明星"},{"timestamp":176.75,"text":"Not happy like in the silver screen","translation":"无法像银幕里那样幸福美满"},{"timestamp":181.16,"text":"But we can get everything here","translation":"但在这里,我们能获得想要的一切"},{"timestamp":189.55,"text":"I can't be a superstar","translation":"我成为不了超级明星"},{"timestamp":192.45,"text":"Not happy like in the silver screen","translation":"无法像银幕里那样幸福美满"},{"timestamp":196.89,"text":"But we can get everything here","translation":"但在这里,我们能获得想要的一切"},{"timestamp":211.65,"text":"But we can get everything here","translation":"但在这里,我们能获得想要的一切"}],"rawOriginalLyrics":"[00:00.00] 作词 : えびかれー伯爵/知代/くりーむぱい太郎\n[00:01.00] 作曲 : えびかれー伯爵\n[00:07.66]I can't be a superstar\n[00:10.66]Not happy like in the silver screen\n[00:15.05]But we can get everything here\n[00:22.44]\n[00:37.18]It's kinda fiction\n[00:38.56]It's a made up story\n[00:40.18]That would be too good for me, sometimes\n[00:44.60]But the beginning is from that point\n[00:51.81]\n[00:52.07]Perfect story is waiting on me who failure\n[00:59.51]Even though I'm not honest man\n[01:06.70]It's a breeze\n[01:10.30]\n[01:10.40]I can't be a superstar\n[01:13.43]Not happy like in the silver screen\n[01:17.77]But we can get everything here\n[01:28.93]\n[01:32.58]It's Low-key life\n[01:33.94]It's so small role\n[01:35.59]Cursed this terrible doom fades me away\n[01:39.92]But it's a chance to change the life brand new\n[01:47.24]\n[01:47.52]Perfect story is waiting on me who failure\n[01:54.92]Even though I'm not honest man\n[02:02.13]It's a breeze\n[02:05.68]\n[02:05.78]I can't be a superstar\n[02:08.76]Not happy like in the silver screen\n[02:13.18]But we can get everything here\n[02:24.30]\n[02:35.51]It's kinda fiction\n[02:37.39]It's a made up story\n[02:39.27]That would be too good for me, sometimes\n[02:42.96]But the beginning is from that point\n[02:48.99]\n[02:50.01]It's a breeze\n[02:53.70]\n[02:53.80]I can't be a superstar\n[02:56.75]Not happy like in the silver screen\n[03:01.16]But we can get everything here\n[03:08.61]\n[03:09.55]I can't be a superstar\n[03:12.45]Not happy like in the silver screen\n[03:16.89]But we can get everything here\n[03:28.02]\n[03:31.65]But we can get everything here\n[03:39.62]\n","rawTranslatedLyrics":"\n[00:07.66]我成为不了超级明星\n[00:10.66]无法像银幕里那样幸福美满\n[00:15.05]但在这里,我们能获得想要的一切\n[00:22.44]\n[00:37.18]这是一篇小说\n[00:38.56]是虚构的故事\n[00:40.18]虽然它有时也对我太过友好\n[00:44.60]但一切都要从那里说起\n[00:51.81]\n[00:52.07]完美的故事正等着失败的我书写\n[00:59.51]我并非诚实之人,但也无妨\n[01:06.70]这事小菜一碟\n[01:10.30]\n[01:10.40]我成为不了超级明星\n[01:13.43]无法像银幕里那样幸福美满\n[01:17.77]但在这里,我们能获得想要的一切\n[01:28.93]\n[01:32.58]这是低调生活\n[01:33.94]属于边缘配角\n[01:35.59]尽管被诅咒的可怕厄运企图将我扼杀\n[01:39.92]但同时它也是令生活焕然一新的良机\n[01:47.24]\n[01:47.52]完美的故事正等着失败的我书写\n[01:54.92]我并非诚实之人,但也无妨\n[02:02.13]这事小菜一碟\n[02:05.68]\n[02:05.78]我成为不了超级明星\n[02:08.76]无法像银幕里那样幸福美满\n[02:13.18]但在这里,我们能获得想要的一切\n[02:24.30]\n[02:35.51]这是一篇小说\n[02:37.39]是虚构的故事\n[02:39.27]虽然它有时也对我太过友好\n[02:42.96]但一切都要从那里说起\n[02:48.99]\n[02:50.01]这事小菜一碟\n[02:53.70]\n[02:53.80]我成为不了超级明星\n[02:56.75]无法像银幕里那样幸福美满\n[03:01.16]但在这里,我们能获得想要的一切\n[03:08.61]\n[03:09.55]我成为不了超级明星\n[03:12.45]无法像银幕里那样幸福美满\n[03:16.89]但在这里,我们能获得想要的一切\n[03:28.02]\n[03:31.65]但在这里,我们能获得想要的一切\n[03:39.62]"}
|
|
@@ -59,6 +59,8 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
59
59
|
restorePlaybackPositionEnabled: boolean;
|
|
60
60
|
loudnessNormalizationEnabled: boolean;
|
|
61
61
|
autoplayOnStartEnabled: boolean;
|
|
62
|
+
isDesktopLyricsShown: boolean;
|
|
63
|
+
isDesktopLyricsLocked: boolean;
|
|
62
64
|
/**
|
|
63
65
|
* 获取当前进度(秒)
|
|
64
66
|
*/
|
|
@@ -166,6 +168,12 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
166
168
|
* 获取所有未完成的下载任务
|
|
167
169
|
*/
|
|
168
170
|
getUncompletedDownloadTasks(): Promise<DownloadTask[]>;
|
|
171
|
+
checkOverlayPermission(): Promise<boolean>;
|
|
172
|
+
requestOverlayPermission(): Promise<void>;
|
|
173
|
+
showDesktopLyrics(): Promise<void>;
|
|
174
|
+
hideDesktopLyrics(): Promise<void>;
|
|
175
|
+
setDesktopLyrics(lyricsJson: string): Promise<void>;
|
|
176
|
+
setDesktopLyricsLocked(locked: boolean): Promise<void>;
|
|
169
177
|
}
|
|
170
178
|
export declare enum DownloadState {
|
|
171
179
|
QUEUED = 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoOrpheusModule.d.ts","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtE,oBAAY,aAAa;IACvB,IAAI,IAAI;IACR,SAAS,IAAI;IACb,KAAK,IAAI;IACT,KAAK,IAAI;CACV;AAED,oBAAY,UAAU;IACpB,GAAG,IAAI;IACP,KAAK,IAAI;IACT,KAAK,IAAI;CACV;AAED,oBAAY,gBAAgB;IAC1B,MAAM,IAAI;IACV,IAAI,IAAI;IACR,IAAI,IAAI;IACR,gBAAgB,IAAI;CACrB;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QACT,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAA;CACF;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,sBAAsB,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,cAAc,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,gBAAgB,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3E,eAAe,CAAC,KAAK,EAAE;QACrB,OAAO,EAAE,MAAM,CAAC;QAChB,aAAa,EAAE,MAAM,CAAC;QACtB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,IAAI,CAAC;IACT,aAAa,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9D,gBAAgB,CAAC,KAAK,EAAE;QACtB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,IAAI,CAAC;IACT,kBAAkB,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACrD,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CAC9C,CAAC;AAEF,OAAO,OAAO,aAAc,SAAQ,YAAY,CAAC,aAAa,CAAC;IAE7D,8BAA8B,EAAE,OAAO,CAAC;IACxC,4BAA4B,EAAE,OAAO,CAAC;IACtC,sBAAsB,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ExpoOrpheusModule.d.ts","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExpoOrpheusModule.js","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgB,MAAM,mBAAmB,CAAC;AAEtE,MAAM,CAAN,IAAY,aAKX;AALD,WAAY,aAAa;IACvB,iDAAQ,CAAA;IACR,2DAAa,CAAA;IACb,mDAAS,CAAA;IACT,mDAAS,CAAA;AACX,CAAC,EALW,aAAa,KAAb,aAAa,QAKxB;AAED,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,yCAAO,CAAA;IACP,6CAAS,CAAA;IACT,6CAAS,CAAA;AACX,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAED,MAAM,CAAN,IAAY,gBAKX;AALD,WAAY,gBAAgB;IAC1B,2DAAU,CAAA;IACV,uDAAQ,CAAA;IACR,uDAAQ,CAAA;IACR,+EAAoB,CAAA;AACtB,CAAC,EALW,gBAAgB,KAAhB,gBAAgB,QAK3B;
|
|
1
|
+
{"version":3,"file":"ExpoOrpheusModule.js","sourceRoot":"","sources":["../src/ExpoOrpheusModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgB,MAAM,mBAAmB,CAAC;AAEtE,MAAM,CAAN,IAAY,aAKX;AALD,WAAY,aAAa;IACvB,iDAAQ,CAAA;IACR,2DAAa,CAAA;IACb,mDAAS,CAAA;IACT,mDAAS,CAAA;AACX,CAAC,EALW,aAAa,KAAb,aAAa,QAKxB;AAED,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,yCAAO,CAAA;IACP,6CAAS,CAAA;IACT,6CAAS,CAAA;AACX,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAED,MAAM,CAAN,IAAY,gBAKX;AALD,WAAY,gBAAgB;IAC1B,2DAAU,CAAA;IACV,uDAAQ,CAAA;IACR,uDAAQ,CAAA;IACR,+EAAoB,CAAA;AACtB,CAAC,EALW,gBAAgB,KAAhB,gBAAgB,QAK3B;AAmMD,MAAM,CAAN,IAAY,aAQX;AARD,WAAY,aAAa;IACvB,qDAAU,CAAA;IACV,uDAAW,CAAA;IACX,+DAAe,CAAA;IACf,2DAAa,CAAA;IACb,qDAAU,CAAA;IACV,yDAAY,CAAA;IACZ,6DAAc,CAAA;AAChB,CAAC,EARW,aAAa,KAAb,aAAa,QAQxB;AAWD,MAAM,CAAC,MAAM,OAAO,GAAG,mBAAmB,CAAgB,SAAS,CAAC,CAAC","sourcesContent":["import { requireNativeModule, NativeModule } from \"expo-modules-core\";\n\nexport enum PlaybackState {\n IDLE = 1,\n BUFFERING = 2,\n READY = 3,\n ENDED = 4,\n}\n\nexport enum RepeatMode {\n OFF = 0,\n TRACK = 1,\n QUEUE = 2,\n}\n\nexport enum TransitionReason {\n REPEAT = 0,\n AUTO = 1,\n SEEK = 2,\n PLAYLIST_CHANGED = 3,\n}\n\nexport interface Track {\n id: string;\n url: string;\n title?: string;\n artist?: string;\n artwork?: string;\n duration?: number;\n loudness?: {\n measured_i: number;\n target_i: number;\n }\n}\n\nexport type OrpheusEvents = {\n onPlaybackStateChanged(event: { state: PlaybackState }): void;\n onTrackStarted(event: { trackId: string; reason: TransitionReason }): void;\n onTrackFinished(event: {\n trackId: string;\n finalPosition: number;\n duration: number;\n }): void;\n onPlayerError(event: { code: string; message: string }): void;\n onPositionUpdate(event: {\n position: number;\n duration: number;\n buffered: number;\n }): void;\n onIsPlayingChanged(event: { status: boolean }): void;\n onDownloadUpdated(event: DownloadTask): void;\n};\n\ndeclare class OrpheusModule extends NativeModule<OrpheusEvents> {\n \n restorePlaybackPositionEnabled: boolean;\n loudnessNormalizationEnabled: boolean;\n autoplayOnStartEnabled: boolean;\n isDesktopLyricsShown: boolean;\n isDesktopLyricsLocked: boolean;\n\n /**\n * 获取当前进度(秒)\n */\n getPosition(): Promise<number>;\n\n /**\n * 获取总时长(秒)\n */\n getDuration(): Promise<number>;\n\n /**\n * 获取缓冲进度(秒)\n */\n getBuffered(): Promise<number>;\n\n /**\n * 获取是否正在播放\n */\n getIsPlaying(): Promise<boolean>;\n\n /**\n * 获取当前播放索引\n */\n getCurrentIndex(): Promise<number>;\n\n /**\n * 获取当前播放的 Track 对象\n */\n getCurrentTrack(): Promise<Track | null>;\n\n /**\n * 获取随机模式状态\n */\n getShuffleMode(): Promise<boolean>;\n\n /**\n * 获取指定索引的 Track\n */\n getIndexTrack(index: number): Promise<Track | null>;\n\n getRepeatMode(): Promise<RepeatMode>;\n\n setBilibiliCookie(cookie: string): void;\n \n setRestorePlaybackPositionEnabled(enabled: boolean): void;\n setLoudnessNormalizationEnabled(enabled: boolean): void;\n setAutoplayOnStartEnabled(enabled: boolean): void;\n\n play(): Promise<void>;\n\n pause(): Promise<void>;\n\n clear(): Promise<void>;\n\n skipTo(index: number): Promise<void>;\n\n skipToNext(): Promise<void>;\n\n skipToPrevious(): Promise<void>;\n\n /**\n * 跳转进度\n * @param seconds 秒数\n */\n seekTo(seconds: number): Promise<void>;\n\n setRepeatMode(mode: RepeatMode): Promise<void>;\n\n setShuffleMode(enabled: boolean): Promise<void>;\n\n getQueue(): Promise<Track[]>;\n\n /**\n * 添加到队列末尾,且不去重。\n * @param tracks\n * @param startFromId 可选,添加后立即播放该 ID 的曲目\n * @param clearQueue 可选,是否清空当前队列\n */\n addToEnd(\n tracks: Track[],\n startFromId?: string,\n clearQueue?: boolean\n ): Promise<void>;\n\n /**\n * 播放下一首\n * @param track\n */\n playNext(track: Track): Promise<void>;\n\n removeTrack(index: number): Promise<void>;\n\n /**\n * 设置睡眠定时器\n * @param durationMs 单位毫秒\n */\n setSleepTimer(durationMs: number): Promise<void>;\n\n /**\n * 获取睡眠定时器结束时间\n * @returns 单位毫秒,如果没有设置则返回 null\n */\n getSleepTimerEndTime(): Promise<number | null>;\n\n cancelSleepTimer(): Promise<void>;\n\n /**\n * 下载单首歌曲\n */\n downloadTrack(track: Track): Promise<void>;\n\n /**\n * 移除下载任务\n */\n removeDownload(id: string): Promise<void>;\n\n /**\n * 批量下载歌曲\n */\n multiDownload(tracks: Track[]): Promise<void>;\n\n /**\n * 移除所有下载任务(包括已完成的及源文件)\n */\n removeAllDownloads(): Promise<void>;\n\n /**\n * 获取所有下载任务\n */\n getDownloads(): Promise<DownloadTask[]>;\n\n /**\n * 批量返回指定 ID 的下载状态\n */\n getDownloadStatusByIds(ids: string[]): Promise<Record<string, DownloadState>>;\n\n /**\n * 清除未完成的下载任务\n */\n clearUncompletedDownloadTasks(): Promise<void>;\n\n /**\n * 获取所有未完成的下载任务\n */\n getUncompletedDownloadTasks(): Promise<DownloadTask[]>;\n\n checkOverlayPermission(): Promise<boolean>;\n requestOverlayPermission(): Promise<void>;\n showDesktopLyrics(): Promise<void>;\n hideDesktopLyrics(): Promise<void>;\n setDesktopLyrics(lyricsJson: string): Promise<void>;\n setDesktopLyricsLocked(locked: boolean): Promise<void>;\n}\n\nexport enum DownloadState {\n QUEUED = 0,\n STOPPED = 1,\n DOWNLOADING = 2,\n COMPLETED = 3,\n FAILED = 4,\n REMOVING = 5,\n RESTARTING = 7,\n}\n\nexport interface DownloadTask {\n id: string;\n state: DownloadState;\n percentDownloaded: number;\n bytesDownloaded: number;\n contentLength: number;\n track?: Track;\n}\n\nexport const Orpheus = requireNativeModule<OrpheusModule>(\"Orpheus\");\n"]}
|
package/build/index.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export * from "./ExpoOrpheusModule";
|
|
2
2
|
export * from "./hooks";
|
|
3
|
+
export type OrpheusHeadlessTrackStartedEvent = {
|
|
4
|
+
eventName: "onTrackStarted";
|
|
5
|
+
trackId: string;
|
|
6
|
+
reason: number;
|
|
7
|
+
};
|
|
8
|
+
export type OrpheusHeadlessEvent = OrpheusHeadlessTrackStartedEvent;
|
|
9
|
+
export declare function registerOrpheusHeadlessTask(task: (event: OrpheusHeadlessEvent) => Promise<void>): void;
|
|
3
10
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AAIxB,MAAM,MAAM,gCAAgC,GAAG;IAC7C,SAAS,EAAE,gBAAgB,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG,gCAAgC,CAAC;AAEpE,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,QAGrD"}
|
package/build/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { AppRegistry } from "react-native";
|
|
1
2
|
export * from "./ExpoOrpheusModule";
|
|
2
3
|
export * from "./hooks";
|
|
4
|
+
const ORPHEUS_HEADLESS_TASK = "OrpheusHeadlessTask";
|
|
5
|
+
export function registerOrpheusHeadlessTask(task) {
|
|
6
|
+
AppRegistry.registerHeadlessTask(ORPHEUS_HEADLESS_TASK, () => task);
|
|
7
|
+
}
|
|
3
8
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AAExB,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AAUpD,MAAM,UAAU,2BAA2B,CACzC,IAAoD;IAEpD,WAAW,CAAC,oBAAoB,CAAC,qBAAqB,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC","sourcesContent":["import { AppRegistry } from \"react-native\";\nexport * from \"./ExpoOrpheusModule\";\nexport * from \"./hooks\";\n\nconst ORPHEUS_HEADLESS_TASK = \"OrpheusHeadlessTask\";\n\nexport type OrpheusHeadlessTrackStartedEvent = {\n eventName: \"onTrackStarted\";\n trackId: string;\n reason: number;\n};\n\nexport type OrpheusHeadlessEvent = OrpheusHeadlessTrackStartedEvent;\n\nexport function registerOrpheusHeadlessTask(\n task: (event: OrpheusHeadlessEvent) => Promise<void>\n) {\n AppRegistry.registerHeadlessTask(ORPHEUS_HEADLESS_TASK, () => task);\n}\n"]}
|
package/package.json
CHANGED
package/src/ExpoOrpheusModule.ts
CHANGED
|
@@ -56,6 +56,8 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
56
56
|
restorePlaybackPositionEnabled: boolean;
|
|
57
57
|
loudnessNormalizationEnabled: boolean;
|
|
58
58
|
autoplayOnStartEnabled: boolean;
|
|
59
|
+
isDesktopLyricsShown: boolean;
|
|
60
|
+
isDesktopLyricsLocked: boolean;
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
63
|
* 获取当前进度(秒)
|
|
@@ -202,6 +204,13 @@ declare class OrpheusModule extends NativeModule<OrpheusEvents> {
|
|
|
202
204
|
* 获取所有未完成的下载任务
|
|
203
205
|
*/
|
|
204
206
|
getUncompletedDownloadTasks(): Promise<DownloadTask[]>;
|
|
207
|
+
|
|
208
|
+
checkOverlayPermission(): Promise<boolean>;
|
|
209
|
+
requestOverlayPermission(): Promise<void>;
|
|
210
|
+
showDesktopLyrics(): Promise<void>;
|
|
211
|
+
hideDesktopLyrics(): Promise<void>;
|
|
212
|
+
setDesktopLyrics(lyricsJson: string): Promise<void>;
|
|
213
|
+
setDesktopLyricsLocked(locked: boolean): Promise<void>;
|
|
205
214
|
}
|
|
206
215
|
|
|
207
216
|
export enum DownloadState {
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1,19 @@
|
|
|
1
|
+
import { AppRegistry } from "react-native";
|
|
1
2
|
export * from "./ExpoOrpheusModule";
|
|
2
3
|
export * from "./hooks";
|
|
4
|
+
|
|
5
|
+
const ORPHEUS_HEADLESS_TASK = "OrpheusHeadlessTask";
|
|
6
|
+
|
|
7
|
+
export type OrpheusHeadlessTrackStartedEvent = {
|
|
8
|
+
eventName: "onTrackStarted";
|
|
9
|
+
trackId: string;
|
|
10
|
+
reason: number;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type OrpheusHeadlessEvent = OrpheusHeadlessTrackStartedEvent;
|
|
14
|
+
|
|
15
|
+
export function registerOrpheusHeadlessTask(
|
|
16
|
+
task: (event: OrpheusHeadlessEvent) => Promise<void>
|
|
17
|
+
) {
|
|
18
|
+
AppRegistry.registerHeadlessTask(ORPHEUS_HEADLESS_TASK, () => task);
|
|
19
|
+
}
|