@roitium/expo-orpheus 0.9.4 → 0.10.1
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/.commitlintrc.js +3 -0
- package/.env +1 -0
- package/.release-it.json +19 -0
- package/CHANGELOG.md +17 -0
- package/README.md +10 -30
- package/android/build.gradle +7 -2
- package/android/src/main/java/expo/modules/orpheus/OrpheusMusicService.kt +5 -1
- package/docs/API-Events.md +35 -0
- package/docs/API-Methods.md +107 -0
- package/docs/API-Types.md +89 -0
- package/docs/Home.md +22 -0
- package/docs/RELEASING.md +34 -0
- package/expo-module.config.json +4 -1
- package/ios/BilibiliApi.swift +241 -0
- package/ios/ExpoOrpheus.podspec +29 -0
- package/ios/ExpoOrpheusModule.swift +228 -0
- package/ios/GeneralStorage.swift +98 -0
- package/ios/OrpheusDownloadManager.swift +360 -0
- package/ios/OrpheusModels.swift +122 -0
- package/ios/OrpheusPlayerManager.swift +783 -0
- package/ios/WbiUtil.swift +105 -0
- package/lefthook.yml +4 -0
- package/mise.toml +2 -0
- package/package.json +11 -5
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = 'ExpoOrpheus'
|
|
7
|
+
s.version = package['version']
|
|
8
|
+
s.summary = package['description']
|
|
9
|
+
s.description = package['description']
|
|
10
|
+
s.license = package['license']
|
|
11
|
+
s.author = package['author']
|
|
12
|
+
s.homepage = package['homepage']
|
|
13
|
+
s.platforms = {
|
|
14
|
+
:ios => '15.1',
|
|
15
|
+
:tvos => '15.1'
|
|
16
|
+
}
|
|
17
|
+
s.swift_version = '5.9'
|
|
18
|
+
s.source = { git: 'https://github.com/bbplayer-app/orpheus.git' }
|
|
19
|
+
s.static_framework = true
|
|
20
|
+
|
|
21
|
+
s.dependency 'ExpoModulesCore'
|
|
22
|
+
|
|
23
|
+
# Swift/Objective-C compatibility
|
|
24
|
+
s.pod_target_xcconfig = {
|
|
25
|
+
'DEFINES_MODULE' => 'YES'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
s.source_files = "**/*.{h,m,swift}"
|
|
29
|
+
end
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
public class ExpoOrpheusModule: Module {
|
|
4
|
+
|
|
5
|
+
private func setupEventListeners() {
|
|
6
|
+
let manager = OrpheusPlayerManager.shared
|
|
7
|
+
|
|
8
|
+
manager.onPlaybackStateChanged = { [weak self] state in
|
|
9
|
+
self?.sendEvent("onPlaybackStateChanged", ["state": state.rawValue])
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
manager.onTrackStarted = { [weak self] trackId, reason in
|
|
13
|
+
self?.sendEvent("onTrackStarted", ["trackId": trackId, "reason": reason.rawValue])
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
manager.onPositionUpdate = { [weak self] position, duration, buffered in
|
|
17
|
+
self?.sendEvent("onPositionUpdate", [
|
|
18
|
+
"position": position,
|
|
19
|
+
"duration": duration,
|
|
20
|
+
"buffered": buffered
|
|
21
|
+
])
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
OrpheusDownloadManager.shared.onDownloadUpdated = { [weak self] task in
|
|
25
|
+
self?.sendEvent("onDownloadUpdated", [
|
|
26
|
+
"id": task.id,
|
|
27
|
+
"state": task.state.rawValue,
|
|
28
|
+
"percentDownloaded": task.percentDownloaded,
|
|
29
|
+
"bytesDownloaded": task.bytesDownloaded,
|
|
30
|
+
"contentLength": task.contentLength
|
|
31
|
+
])
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
manager.onPlayerError = { [weak self] errorMsg in
|
|
35
|
+
self?.sendEvent("onPlayerError", ["error": errorMsg])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
manager.onIsPlayingChanged = { [weak self] isPlaying in
|
|
39
|
+
self?.sendEvent("onIsPlayingChanged", ["status": isPlaying])
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public func definition() -> ModuleDefinition {
|
|
44
|
+
Name("Orpheus")
|
|
45
|
+
|
|
46
|
+
// Events
|
|
47
|
+
Events(
|
|
48
|
+
"onPlaybackStateChanged",
|
|
49
|
+
"onTrackStarted",
|
|
50
|
+
"onTrackFinished",
|
|
51
|
+
"onPlayerError",
|
|
52
|
+
"onPositionUpdate",
|
|
53
|
+
"onIsPlayingChanged",
|
|
54
|
+
"onDownloadUpdated",
|
|
55
|
+
"onPlaybackSpeedChanged"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
OnCreate {
|
|
59
|
+
self.setupEventListeners()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// MARK: - Preferences
|
|
63
|
+
|
|
64
|
+
Property("restorePlaybackPositionEnabled")
|
|
65
|
+
.get { GeneralStorage.shared.isRestoreEnabled }
|
|
66
|
+
.set { GeneralStorage.shared.isRestoreEnabled = $0 }
|
|
67
|
+
|
|
68
|
+
Property("loudnessNormalizationEnabled")
|
|
69
|
+
.get { GeneralStorage.shared.isLoudnessNormalizationEnabled }
|
|
70
|
+
.set { GeneralStorage.shared.isLoudnessNormalizationEnabled = $0 }
|
|
71
|
+
|
|
72
|
+
Property("autoplayOnStartEnabled")
|
|
73
|
+
.get { GeneralStorage.shared.isAutoplayOnStartEnabled }
|
|
74
|
+
.set { GeneralStorage.shared.isAutoplayOnStartEnabled = $0 }
|
|
75
|
+
|
|
76
|
+
// MARK: - Getters
|
|
77
|
+
|
|
78
|
+
AsyncFunction("getPosition") { () -> Double in
|
|
79
|
+
return OrpheusPlayerManager.shared.getPosition()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
AsyncFunction("getDuration") { () -> Double in
|
|
83
|
+
return OrpheusPlayerManager.shared.getDuration()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
AsyncFunction("getBuffered") { () -> Double in
|
|
87
|
+
return OrpheusPlayerManager.shared.getBufferedPosition()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
AsyncFunction("getIsPlaying") { () -> Bool in
|
|
91
|
+
return OrpheusPlayerManager.shared.isPlaying()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
AsyncFunction("getCurrentIndex") { () -> Int in
|
|
95
|
+
return OrpheusPlayerManager.shared.getCurrentIndex()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
AsyncFunction("getCurrentTrack") { () -> Track? in
|
|
99
|
+
return OrpheusPlayerManager.shared.getCurrentTrack()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
AsyncFunction("getQueue") { () -> [Track] in
|
|
103
|
+
return OrpheusPlayerManager.shared.getQueue()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
AsyncFunction("getIndexTrack") { (index: Int) -> Track? in
|
|
107
|
+
return OrpheusPlayerManager.shared.getTrack(at: index)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
AsyncFunction("getPlaybackSpeed") { () -> Double in
|
|
111
|
+
return Double(OrpheusPlayerManager.shared.getPlaybackSpeed())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
AsyncFunction("getRepeatMode") { () -> Int in
|
|
115
|
+
return OrpheusPlayerManager.shared.repeatMode.rawValue
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
AsyncFunction("getShuffleMode") { () -> Bool in
|
|
119
|
+
return OrpheusPlayerManager.shared.shuffleMode
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// MARK: - Controls
|
|
123
|
+
|
|
124
|
+
AsyncFunction("play") {
|
|
125
|
+
OrpheusPlayerManager.shared.play()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
AsyncFunction("pause") {
|
|
129
|
+
OrpheusPlayerManager.shared.pause()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
AsyncFunction("skipToNext") {
|
|
133
|
+
OrpheusPlayerManager.shared.playNext()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
AsyncFunction("skipToPrevious") {
|
|
137
|
+
OrpheusPlayerManager.shared.skipToPrevious()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
AsyncFunction("seekTo") { (seconds: Double) in
|
|
141
|
+
OrpheusPlayerManager.shared.seek(to: seconds)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
AsyncFunction("skipTo") { (index: Int) in
|
|
145
|
+
OrpheusPlayerManager.shared.skipTo(index: index)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
AsyncFunction("addToEnd") { (tracks: [Track], startFromId: String?, clearQueue: Bool) in
|
|
149
|
+
OrpheusPlayerManager.shared.addToEnd(tracks: tracks, startFromId: startFromId, clearQueue: clearQueue)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
AsyncFunction("playNext") { (track: Track) in
|
|
153
|
+
OrpheusPlayerManager.shared.addToNext(track: track)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
AsyncFunction("removeTrack") { (index: Int) in
|
|
157
|
+
OrpheusPlayerManager.shared.removeTrack(at: index)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
AsyncFunction("clear") {
|
|
161
|
+
OrpheusPlayerManager.shared.clearQueue()
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
AsyncFunction("setPlaybackSpeed") { (speed: Double) in
|
|
165
|
+
OrpheusPlayerManager.shared.setPlaybackSpeed(Float(speed))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
Function("setBilibiliCookie") { (cookie: String) in
|
|
169
|
+
BilibiliApi.shared.setCookie(cookie)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Function("setShuffleMode") { (enabled: Bool) in
|
|
173
|
+
OrpheusPlayerManager.shared.setExecuteShuffleMode(enabled)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
Function("setRepeatMode") { (mode: Int) in
|
|
177
|
+
if let repeatMode = RepeatMode(rawValue: mode) {
|
|
178
|
+
OrpheusPlayerManager.shared.setExecuteRepeatMode(repeatMode)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
Function("setSleepTimer") { (durationMs: Double) in
|
|
183
|
+
OrpheusPlayerManager.shared.setSleepTimer(durationMs: durationMs)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
Function("getSleepTimerEndTime") { () -> Double? in
|
|
187
|
+
return OrpheusPlayerManager.shared.getSleepTimerEndTime()
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
Function("cancelSleepTimer") {
|
|
191
|
+
OrpheusPlayerManager.shared.cancelSleepTimer()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// MARK: - Downloads
|
|
195
|
+
|
|
196
|
+
Function("downloadTrack") { (track: Track) in
|
|
197
|
+
OrpheusDownloadManager.shared.downloadTrack(track: track)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
Function("multiDownload") { (tracks: [Track]) in
|
|
201
|
+
OrpheusDownloadManager.shared.multiDownload(tracks: tracks)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
Function("removeDownload") { (id: String) in
|
|
205
|
+
OrpheusDownloadManager.shared.removeDownload(id: id)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
Function("removeAllDownloads") {
|
|
209
|
+
OrpheusDownloadManager.shared.removeAllDownloads()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Function("getDownloads") { () -> [DownloadTask] in
|
|
213
|
+
return OrpheusDownloadManager.shared.getDownloads()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
Function("getDownloadStatusByIds") { (ids: [String]) -> [String: Int] in
|
|
217
|
+
return OrpheusDownloadManager.shared.getDownloadStatusByIds(ids: ids)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Function("clearUncompletedDownloadTasks") {
|
|
221
|
+
OrpheusDownloadManager.shared.clearUncompletedTasks()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
Function("getUncompletedDownloadTasks") { () -> [DownloadTask] in
|
|
225
|
+
return OrpheusDownloadManager.shared.getUncompletedTasks()
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
class GeneralStorage {
|
|
4
|
+
static let shared = GeneralStorage()
|
|
5
|
+
|
|
6
|
+
private let defaults = UserDefaults.standard
|
|
7
|
+
|
|
8
|
+
private let KEY_SAVED_QUEUE = "saved_queue_json_list"
|
|
9
|
+
private let KEY_SAVED_INDEX = "saved_index"
|
|
10
|
+
private let KEY_SAVED_POSITION = "saved_position"
|
|
11
|
+
private let KEY_SAVED_REPEAT_MODE = "saved_repeat_mode"
|
|
12
|
+
private let KEY_SAVED_SHUFFLE_MODE = "saved_shuffle_mode"
|
|
13
|
+
|
|
14
|
+
private let KEY_RESTORE_ENABLED = "restorePlaybackPositionEnabled"
|
|
15
|
+
private let KEY_LOUDNESS_ENABLED = "loudnessNormalizationEnabled"
|
|
16
|
+
private let KEY_AUTOPLAY_ENABLED = "autoplayOnStartEnabled"
|
|
17
|
+
private let KEY_DESKTOP_LYRICS_SHOWN = "isDesktopLyricsShown" // Not really used in iOS but following pattern
|
|
18
|
+
private let KEY_DESKTOP_LYRICS_LOCKED = "isDesktopLyricsLocked"
|
|
19
|
+
|
|
20
|
+
// MARK: - Preferences
|
|
21
|
+
|
|
22
|
+
var isRestoreEnabled: Bool {
|
|
23
|
+
get { return defaults.bool(forKey: KEY_RESTORE_ENABLED) }
|
|
24
|
+
set { defaults.set(newValue, forKey: KEY_RESTORE_ENABLED) }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var isLoudnessNormalizationEnabled: Bool {
|
|
28
|
+
get { return defaults.bool(forKey: KEY_LOUDNESS_ENABLED) }
|
|
29
|
+
set { defaults.set(newValue, forKey: KEY_LOUDNESS_ENABLED) }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var isAutoplayOnStartEnabled: Bool {
|
|
33
|
+
get { return defaults.bool(forKey: KEY_AUTOPLAY_ENABLED) }
|
|
34
|
+
set { defaults.set(newValue, forKey: KEY_AUTOPLAY_ENABLED) }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// MARK: - Playback State
|
|
38
|
+
|
|
39
|
+
func saveQueue(_ queue: [Track]) {
|
|
40
|
+
do {
|
|
41
|
+
let jsonList = try queue.compactMap { track -> String? in
|
|
42
|
+
let dict = track.dictionaryRepresentation
|
|
43
|
+
let data = try JSONSerialization.data(withJSONObject: dict, options: [])
|
|
44
|
+
return String(data: data, encoding: .utf8)
|
|
45
|
+
}
|
|
46
|
+
defaults.set(jsonList, forKey: KEY_SAVED_QUEUE)
|
|
47
|
+
} catch {
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func getSavedQueue() -> [Track] {
|
|
53
|
+
guard let jsonList = defaults.stringArray(forKey: KEY_SAVED_QUEUE) else { return [] }
|
|
54
|
+
|
|
55
|
+
var restoredQueue: [Track] = []
|
|
56
|
+
for jsonStr in jsonList {
|
|
57
|
+
if let data = jsonStr.data(using: .utf8),
|
|
58
|
+
let dict = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
|
59
|
+
let track = Track(dictionary: dict) {
|
|
60
|
+
restoredQueue.append(track)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return restoredQueue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
func savePosition(index: Int, positionSec: Double) {
|
|
67
|
+
defaults.set(index, forKey: KEY_SAVED_INDEX)
|
|
68
|
+
|
|
69
|
+
if !positionSec.isNaN && !positionSec.isInfinite {
|
|
70
|
+
let positionMs = Int64(positionSec * 1000)
|
|
71
|
+
defaults.set(positionMs, forKey: KEY_SAVED_POSITION)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
func getSavedIndex() -> Int {
|
|
76
|
+
return defaults.integer(forKey: KEY_SAVED_INDEX)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
func getSavedPosition() -> Double {
|
|
80
|
+
return defaults.double(forKey: KEY_SAVED_POSITION) / 1000.0
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func saveRepeatMode(_ mode: Int) {
|
|
84
|
+
defaults.set(mode, forKey: KEY_SAVED_REPEAT_MODE)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
func getSavedRepeatMode() -> Int {
|
|
88
|
+
return defaults.integer(forKey: KEY_SAVED_REPEAT_MODE)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func saveShuffleMode(_ enabled: Bool) {
|
|
92
|
+
defaults.set(enabled, forKey: KEY_SAVED_SHUFFLE_MODE)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
func getSavedShuffleMode() -> Bool {
|
|
96
|
+
return defaults.bool(forKey: KEY_SAVED_SHUFFLE_MODE)
|
|
97
|
+
}
|
|
98
|
+
}
|