improve providers managament & supports remote events

This commit is contained in:
SrGooglo 2025-02-05 02:43:36 +00:00
parent 1cd6429666
commit de17716109
3 changed files with 499 additions and 416 deletions

View File

@ -1,30 +1,9 @@
import MusicModel from "comty.js/models/music" import ComtyMusicServiceInterface from "../providers/comtymusic"
class ComtyMusicService {
static id = "default"
resolve = async (track_id) => {
return await MusicModel.getTrackData(track_id)
}
resolveMany = async (track_ids, options) => {
const response = await MusicModel.getTrackData(track_ids, options)
if (response.list) {
return response
}
return [response]
}
toggleTrackLike = async (manifest, to) => {
return await MusicModel.toggleTrackLike(manifest, to)
}
}
export default class ServiceProviders { export default class ServiceProviders {
providers = [ providers = [
new ComtyMusicService() // add by default here
new ComtyMusicServiceInterface()
] ]
findProvider(providerId) { findProvider(providerId) {
@ -35,7 +14,28 @@ export default class ServiceProviders {
this.providers.push(provider) this.providers.push(provider)
} }
// operations has(providerId) {
return this.providers.some((provider) => provider.constructor.id === providerId)
}
operation = async (operationName, providerId, manifest, args) => {
const provider = await this.findProvider(providerId)
if (!provider) {
console.error(`Failed to resolve manifest, provider [${providerId}] not registered`)
return manifest
}
const operationFn = provider[operationName]
if (typeof operationFn !== "function") {
console.error(`Failed to resolve manifest, provider [${providerId}] operation [${operationName}] not found`)
return manifest
}
return await operationFn(manifest, args)
}
resolve = async (providerId, manifest) => { resolve = async (providerId, manifest) => {
const provider = await this.findProvider(providerId) const provider = await this.findProvider(providerId)

View File

@ -1,7 +1,8 @@
import { Core } from "vessel" import { Core } from "@ragestudio/vessel"
import TrackInstance from "@classes/TrackInstance" import RemoteEvent from "@classes/RemoteEvent"
import QueueManager from "@classes/QueueManager" import QueueManager from "@classes/QueueManager"
import TrackInstance from "./classes/TrackInstance"
import MediaSession from "./classes/MediaSession" import MediaSession from "./classes/MediaSession"
import ServiceProviders from "./classes/Services" import ServiceProviders from "./classes/Services"
import PlayerState from "./classes/PlayerState" import PlayerState from "./classes/PlayerState"
@ -14,10 +15,7 @@ import AudioPlayerStorage from "./player.storage"
export default class Player extends Core { export default class Player extends Core {
// core config // core config
static dependencies = [ static dependencies = ["api", "settings"]
"api",
"settings"
]
static namespace = "player" static namespace = "player"
static bgColor = "aquamarine" static bgColor = "aquamarine"
static textColor = "black" static textColor = "black"
@ -32,14 +30,15 @@ export default class Player extends Core {
serviceProviders = new ServiceProviders() serviceProviders = new ServiceProviders()
nativeControls = new MediaSession() nativeControls = new MediaSession()
audioContext = new AudioContext({ audioContext = new AudioContext({
sampleRate: AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate, sampleRate:
latencyHint: "playback" AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate,
latencyHint: "playback",
}) })
audioProcessors = new PlayerProcessors(this) audioProcessors = new PlayerProcessors(this)
queue = new QueueManager({ queue = new QueueManager({
loadFunction: this.createInstance loadFunction: this.createInstance,
}) })
currentTrackInstance = null currentTrackInstance = null
@ -47,6 +46,12 @@ export default class Player extends Core {
public = { public = {
start: this.start, start: this.start,
close: this.close, close: this.close,
queue: this.bindableReadOnlyProxy({
items: () => {
return this.queue.nextItems
},
add: this.addToQueue,
}),
playback: this.bindableReadOnlyProxy({ playback: this.bindableReadOnlyProxy({
toggle: this.togglePlayback, toggle: this.togglePlayback,
play: this.resumePlayback, play: this.resumePlayback,
@ -107,6 +112,8 @@ export default class Player extends Core {
throw new Error("Audio instance is required") throw new Error("Audio instance is required")
} }
this.console.log("Initializing instance", instance)
// resume audio context if needed // resume audio context if needed
if (this.audioContext.state === "suspended") { if (this.audioContext.state === "suspended") {
this.audioContext.resume() this.audioContext.resume()
@ -117,27 +124,28 @@ export default class Player extends Core {
this.queue.currentItem = await instance.initialize() this.queue.currentItem = await instance.initialize()
} }
this.console.log("Instance", this.queue.currentItem)
// update manifest // update manifest
this.state.track_manifest = this.queue.currentItem.manifest this.state.track_manifest = this.queue.currentItem.manifest
// attach processors // attach processors
this.queue.currentItem = await this.audioProcessors.attachProcessorsToInstance(this.queue.currentItem) this.queue.currentItem =
await this.audioProcessors.attachProcessorsToInstance(
// reconstruct audio src if is not set this.queue.currentItem,
if (this.queue.currentItem.audio.src !== this.queue.currentItem.manifest.source) { )
this.queue.currentItem.audio.src = this.queue.currentItem.manifest.source
}
// set audio properties // set audio properties
this.queue.currentItem.audio.currentTime = params.time ?? 0 this.queue.currentItem.audio.currentTime = params.time ?? 0
this.queue.currentItem.audio.muted = this.state.muted this.queue.currentItem.audio.muted = this.state.muted
this.queue.currentItem.audio.loop = this.state.playback_mode === "repeat" this.queue.currentItem.audio.loop =
this.state.playback_mode === "repeat"
this.queue.currentItem.gainNode.gain.value = this.state.volume this.queue.currentItem.gainNode.gain.value = this.state.volume
// play // play
await this.queue.currentItem.audio.play() await this.queue.currentItem.audio.play()
this.console.debug(`Playing track >`, this.queue.currentItem) this.console.log(`Playing track >`, this.queue.currentItem)
// update native controls // update native controls
this.nativeControls.update(this.queue.currentItem.manifest) this.nativeControls.update(this.queue.currentItem.manifest)
@ -177,12 +185,38 @@ export default class Player extends Core {
const item = this.queue.set(startIndex) const item = this.queue.set(startIndex)
this.play(item, { this.play(item, {
time: time ?? 0 time: time ?? 0,
}) })
// send the event to the server
if (item.manifest._id && item.manifest.service === "default") {
new RemoteEvent("player.play", {
identifier: "unique", // this must be unique to prevent duplicate events and ensure only have unique track events
track_id: item.manifest._id,
service: item.manifest.service,
})
}
return manifest return manifest
} }
// similar to player.start, but add to the queue
// if next is true, it will add to the queue to the top of the queue
async addToQueue(manifest, { next = false }) {
if (typeof manifest === "string") {
manifest = await this.serviceProviders.resolve(manifest)
}
let instance = await this.createInstance(manifest)
this.queue.add(instance, next === true ? "start" : "end")
console.log("Added to queue", {
manifest,
queue: this.queue,
})
}
next() { next() {
if (this.queue.currentItem) { if (this.queue.currentItem) {
this.queue.currentItem.stop() this.queue.currentItem.stop()
@ -233,7 +267,7 @@ export default class Player extends Core {
// set gain exponentially // set gain exponentially
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime( this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
0.0001, 0.0001,
this.audioContext.currentTime + (Player.gradualFadeMs / 1000) this.audioContext.currentTime + Player.gradualFadeMs / 1000,
) )
setTimeout(() => { setTimeout(() => {
@ -266,7 +300,7 @@ export default class Player extends Core {
// set gain exponentially // set gain exponentially
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime( this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
this.state.volume, this.state.volume,
this.audioContext.currentTime + (Player.gradualFadeMs / 1000) this.audioContext.currentTime + Player.gradualFadeMs / 1000,
) )
this.nativeControls.updateIsPlaying(true) this.nativeControls.updateIsPlaying(true)
@ -281,7 +315,8 @@ export default class Player extends Core {
this.state.playback_mode = mode this.state.playback_mode = mode
if (this.queue.currentItem) { if (this.queue.currentItem) {
this.queue.currentItem.audio.loop = this.state.playback_mode === "repeat" this.queue.currentItem.audio.loop =
this.state.playback_mode === "repeat"
} }
AudioPlayerStorage.set("mode", mode) AudioPlayerStorage.set("mode", mode)
@ -374,7 +409,9 @@ export default class Player extends Core {
// if time is provided, seek to that time // if time is provided, seek to that time
if (typeof time === "number") { if (typeof time === "number") {
this.console.log(`Seeking to ${time} | Duration: ${this.queue.currentItem.audio.duration}`) this.console.log(
`Seeking to ${time} | Duration: ${this.queue.currentItem.audio.duration}`,
)
this.queue.currentItem.audio.currentTime = time this.queue.currentItem.audio.currentTime = time
@ -409,4 +446,8 @@ export default class Player extends Core {
this.stopPlayback() this.stopPlayback()
this.ui.detachPlayerComponent() this.ui.detachPlayerComponent()
} }
registerService(serviceInteface) {
this.serviceProviders.register(serviceInteface)
}
} }

View File

@ -0,0 +1,42 @@
import MusicModel from "comty.js/models/music"
export default class ComtyMusicServiceInterface {
static id = "default"
resolve = async (manifest) => {
if (typeof manifest === "string" && manifest.startsWith("https://")) {
return {
source: manifest.source,
service: "default",
}
}
if (typeof manifest === "string") {
manifest = {
_id: manifest,
service: ComtyMusicServiceInterface.id,
}
}
const track = await MusicModel.getTrackData(manifest._id)
return track
}
resolveLyrics = async (manifest, options) => {
return await MusicModel.getTrackLyrics(manifest._id, options)
}
resolveOverride = async (manifest) => {
// not supported yet for comty music service
return {}
}
isItemFavourited = async (manifest, itemType) => {
return await MusicModel.isItemFavourited(itemType, manifest._id)
}
toggleItemFavourite = async (manifest, itemType, to) => {
return await MusicModel.toggleItemFavourite(itemType, manifest._id, to)
}
}