mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
improve providers managament & supports remote events
This commit is contained in:
parent
1cd6429666
commit
de17716109
@ -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)
|
||||||
|
|
||||||
|
@ -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"
|
||||||
@ -13,400 +14,440 @@ import setSampleRate from "./helpers/setSampleRate"
|
|||||||
import AudioPlayerStorage from "./player.storage"
|
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",
|
static namespace = "player"
|
||||||
"settings"
|
static bgColor = "aquamarine"
|
||||||
]
|
static textColor = "black"
|
||||||
static namespace = "player"
|
|
||||||
static bgColor = "aquamarine"
|
// player config
|
||||||
static textColor = "black"
|
static defaultSampleRate = 48000
|
||||||
|
static gradualFadeMs = 150
|
||||||
// player config
|
static maxManifestPrecompute = 3
|
||||||
static defaultSampleRate = 48000
|
|
||||||
static gradualFadeMs = 150
|
state = new PlayerState(this)
|
||||||
static maxManifestPrecompute = 3
|
ui = new PlayerUI(this)
|
||||||
|
serviceProviders = new ServiceProviders()
|
||||||
state = new PlayerState(this)
|
nativeControls = new MediaSession()
|
||||||
ui = new PlayerUI(this)
|
audioContext = new AudioContext({
|
||||||
serviceProviders = new ServiceProviders()
|
sampleRate:
|
||||||
nativeControls = new MediaSession()
|
AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate,
|
||||||
audioContext = new AudioContext({
|
latencyHint: "playback",
|
||||||
sampleRate: AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate,
|
})
|
||||||
latencyHint: "playback"
|
|
||||||
})
|
audioProcessors = new PlayerProcessors(this)
|
||||||
|
|
||||||
audioProcessors = new PlayerProcessors(this)
|
queue = new QueueManager({
|
||||||
|
loadFunction: this.createInstance,
|
||||||
queue = new QueueManager({
|
})
|
||||||
loadFunction: this.createInstance
|
|
||||||
})
|
currentTrackInstance = null
|
||||||
|
|
||||||
currentTrackInstance = null
|
public = {
|
||||||
|
start: this.start,
|
||||||
public = {
|
close: this.close,
|
||||||
start: this.start,
|
queue: this.bindableReadOnlyProxy({
|
||||||
close: this.close,
|
items: () => {
|
||||||
playback: this.bindableReadOnlyProxy({
|
return this.queue.nextItems
|
||||||
toggle: this.togglePlayback,
|
},
|
||||||
play: this.resumePlayback,
|
add: this.addToQueue,
|
||||||
pause: this.pausePlayback,
|
}),
|
||||||
stop: this.stopPlayback,
|
playback: this.bindableReadOnlyProxy({
|
||||||
previous: this.previous,
|
toggle: this.togglePlayback,
|
||||||
next: this.next,
|
play: this.resumePlayback,
|
||||||
mode: this.playbackMode,
|
pause: this.pausePlayback,
|
||||||
}),
|
stop: this.stopPlayback,
|
||||||
controls: this.bindableReadOnlyProxy({
|
previous: this.previous,
|
||||||
duration: this.duration,
|
next: this.next,
|
||||||
volume: this.volume,
|
mode: this.playbackMode,
|
||||||
mute: this.mute,
|
}),
|
||||||
seek: this.seek,
|
controls: this.bindableReadOnlyProxy({
|
||||||
setSampleRate: setSampleRate,
|
duration: this.duration,
|
||||||
}),
|
volume: this.volume,
|
||||||
track: () => {
|
mute: this.mute,
|
||||||
return this.queue.currentItem
|
seek: this.seek,
|
||||||
},
|
setSampleRate: setSampleRate,
|
||||||
eventBus: () => {
|
}),
|
||||||
return this.eventBus
|
track: () => {
|
||||||
},
|
return this.queue.currentItem
|
||||||
state: this.state,
|
},
|
||||||
ui: this.ui.public,
|
eventBus: () => {
|
||||||
audioContext: this.audioContext,
|
return this.eventBus
|
||||||
gradualFadeMs: Player.gradualFadeMs,
|
},
|
||||||
}
|
state: this.state,
|
||||||
|
ui: this.ui.public,
|
||||||
async afterInitialize() {
|
audioContext: this.audioContext,
|
||||||
if (app.isMobile) {
|
gradualFadeMs: Player.gradualFadeMs,
|
||||||
this.state.volume = 1
|
}
|
||||||
}
|
|
||||||
|
async afterInitialize() {
|
||||||
await this.nativeControls.initialize()
|
if (app.isMobile) {
|
||||||
await this.audioProcessors.initialize()
|
this.state.volume = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
await this.nativeControls.initialize()
|
||||||
// Instance managing methods
|
await this.audioProcessors.initialize()
|
||||||
//
|
}
|
||||||
async abortPreloads() {
|
|
||||||
for await (const instance of this.queue.nextItems) {
|
//
|
||||||
if (instance.abortController?.abort) {
|
// Instance managing methods
|
||||||
instance.abortController.abort()
|
//
|
||||||
}
|
async abortPreloads() {
|
||||||
}
|
for await (const instance of this.queue.nextItems) {
|
||||||
}
|
if (instance.abortController?.abort) {
|
||||||
|
instance.abortController.abort()
|
||||||
async createInstance(manifest) {
|
}
|
||||||
return new TrackInstance(this, manifest)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
async createInstance(manifest) {
|
||||||
// Playback methods
|
return new TrackInstance(this, manifest)
|
||||||
//
|
}
|
||||||
async play(instance, params = {}) {
|
|
||||||
if (!instance) {
|
//
|
||||||
throw new Error("Audio instance is required")
|
// Playback methods
|
||||||
}
|
//
|
||||||
|
async play(instance, params = {}) {
|
||||||
// resume audio context if needed
|
if (!instance) {
|
||||||
if (this.audioContext.state === "suspended") {
|
throw new Error("Audio instance is required")
|
||||||
this.audioContext.resume()
|
}
|
||||||
}
|
|
||||||
|
this.console.log("Initializing instance", instance)
|
||||||
// initialize instance if is not
|
|
||||||
if (this.queue.currentItem._initialized === false) {
|
// resume audio context if needed
|
||||||
this.queue.currentItem = await instance.initialize()
|
if (this.audioContext.state === "suspended") {
|
||||||
}
|
this.audioContext.resume()
|
||||||
|
}
|
||||||
// update manifest
|
|
||||||
this.state.track_manifest = this.queue.currentItem.manifest
|
// initialize instance if is not
|
||||||
|
if (this.queue.currentItem._initialized === false) {
|
||||||
// attach processors
|
this.queue.currentItem = await instance.initialize()
|
||||||
this.queue.currentItem = await this.audioProcessors.attachProcessorsToInstance(this.queue.currentItem)
|
}
|
||||||
|
|
||||||
// reconstruct audio src if is not set
|
this.console.log("Instance", this.queue.currentItem)
|
||||||
if (this.queue.currentItem.audio.src !== this.queue.currentItem.manifest.source) {
|
|
||||||
this.queue.currentItem.audio.src = this.queue.currentItem.manifest.source
|
// update manifest
|
||||||
}
|
this.state.track_manifest = this.queue.currentItem.manifest
|
||||||
|
|
||||||
// set audio properties
|
// attach processors
|
||||||
this.queue.currentItem.audio.currentTime = params.time ?? 0
|
this.queue.currentItem =
|
||||||
this.queue.currentItem.audio.muted = this.state.muted
|
await this.audioProcessors.attachProcessorsToInstance(
|
||||||
this.queue.currentItem.audio.loop = this.state.playback_mode === "repeat"
|
this.queue.currentItem,
|
||||||
this.queue.currentItem.gainNode.gain.value = this.state.volume
|
)
|
||||||
|
|
||||||
// play
|
// set audio properties
|
||||||
await this.queue.currentItem.audio.play()
|
this.queue.currentItem.audio.currentTime = params.time ?? 0
|
||||||
|
this.queue.currentItem.audio.muted = this.state.muted
|
||||||
this.console.debug(`Playing track >`, this.queue.currentItem)
|
this.queue.currentItem.audio.loop =
|
||||||
|
this.state.playback_mode === "repeat"
|
||||||
// update native controls
|
this.queue.currentItem.gainNode.gain.value = this.state.volume
|
||||||
this.nativeControls.update(this.queue.currentItem.manifest)
|
|
||||||
|
// play
|
||||||
return this.queue.currentItem
|
await this.queue.currentItem.audio.play()
|
||||||
}
|
|
||||||
|
this.console.log(`Playing track >`, this.queue.currentItem)
|
||||||
async start(manifest, { time, startIndex = 0 } = {}) {
|
|
||||||
this.ui.attachPlayerComponent()
|
// update native controls
|
||||||
|
this.nativeControls.update(this.queue.currentItem.manifest)
|
||||||
if (this.queue.currentItem) {
|
|
||||||
await this.queue.currentItem.stop()
|
return this.queue.currentItem
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.abortPreloads()
|
async start(manifest, { time, startIndex = 0 } = {}) {
|
||||||
await this.queue.flush()
|
this.ui.attachPlayerComponent()
|
||||||
|
|
||||||
this.state.loading = true
|
if (this.queue.currentItem) {
|
||||||
|
await this.queue.currentItem.stop()
|
||||||
let playlist = Array.isArray(manifest) ? manifest : [manifest]
|
}
|
||||||
|
|
||||||
if (playlist.length === 0) {
|
await this.abortPreloads()
|
||||||
this.console.warn(`Playlist is empty, aborting...`)
|
await this.queue.flush()
|
||||||
return false
|
|
||||||
}
|
this.state.loading = true
|
||||||
|
|
||||||
if (playlist.some((item) => typeof item === "string")) {
|
let playlist = Array.isArray(manifest) ? manifest : [manifest]
|
||||||
playlist = await this.serviceProviders.resolveMany(playlist)
|
|
||||||
}
|
if (playlist.length === 0) {
|
||||||
|
this.console.warn(`Playlist is empty, aborting...`)
|
||||||
for await (const [index, _manifest] of playlist.entries()) {
|
return false
|
||||||
let instance = await this.createInstance(_manifest)
|
}
|
||||||
|
|
||||||
this.queue.add(instance)
|
if (playlist.some((item) => typeof item === "string")) {
|
||||||
}
|
playlist = await this.serviceProviders.resolveMany(playlist)
|
||||||
|
}
|
||||||
const item = this.queue.set(startIndex)
|
|
||||||
|
for await (const [index, _manifest] of playlist.entries()) {
|
||||||
this.play(item, {
|
let instance = await this.createInstance(_manifest)
|
||||||
time: time ?? 0
|
|
||||||
})
|
this.queue.add(instance)
|
||||||
|
}
|
||||||
return manifest
|
|
||||||
}
|
const item = this.queue.set(startIndex)
|
||||||
|
|
||||||
next() {
|
this.play(item, {
|
||||||
if (this.queue.currentItem) {
|
time: time ?? 0,
|
||||||
this.queue.currentItem.stop()
|
})
|
||||||
}
|
|
||||||
|
// send the event to the server
|
||||||
//const isRandom = this.state.playback_mode === "shuffle"
|
if (item.manifest._id && item.manifest.service === "default") {
|
||||||
const item = this.queue.next()
|
new RemoteEvent("player.play", {
|
||||||
|
identifier: "unique", // this must be unique to prevent duplicate events and ensure only have unique track events
|
||||||
if (!item) {
|
track_id: item.manifest._id,
|
||||||
return this.stopPlayback()
|
service: item.manifest.service,
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
if (this.queue.currentItem) {
|
||||||
|
this.queue.currentItem.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
//const isRandom = this.state.playback_mode === "shuffle"
|
||||||
|
const item = this.queue.next()
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return this.stopPlayback()
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.play(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
previous() {
|
||||||
|
if (this.queue.currentItem) {
|
||||||
|
this.queue.currentItem.stop()
|
||||||
|
}
|
||||||
|
|
||||||
return this.play(item)
|
const item = this.queue.previous()
|
||||||
}
|
|
||||||
|
|
||||||
previous() {
|
return this.play(item)
|
||||||
if (this.queue.currentItem) {
|
}
|
||||||
this.queue.currentItem.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = this.queue.previous()
|
//
|
||||||
|
// Playback Control
|
||||||
|
//
|
||||||
|
async togglePlayback() {
|
||||||
|
if (this.state.playback_status === "paused") {
|
||||||
|
await this.resumePlayback()
|
||||||
|
} else {
|
||||||
|
await this.pausePlayback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pausePlayback() {
|
||||||
|
if (!this.state.playback_status === "paused") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
if (!this.queue.currentItem) {
|
||||||
|
this.console.error("No audio instance")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// set gain exponentially
|
||||||
|
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
|
||||||
|
0.0001,
|
||||||
|
this.audioContext.currentTime + Player.gradualFadeMs / 1000,
|
||||||
|
)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.queue.currentItem.audio.pause()
|
||||||
|
resolve()
|
||||||
|
}, Player.gradualFadeMs)
|
||||||
|
|
||||||
|
this.nativeControls.updateIsPlaying(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async resumePlayback() {
|
||||||
|
if (!this.state.playback_status === "playing") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
if (!this.queue.currentItem) {
|
||||||
|
this.console.error("No audio instance")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure audio elemeto starts from 0 volume
|
||||||
|
this.queue.currentItem.gainNode.gain.value = 0.0001
|
||||||
|
|
||||||
|
this.queue.currentItem.audio.play().then(() => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
// set gain exponentially
|
||||||
|
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
|
||||||
|
this.state.volume,
|
||||||
|
this.audioContext.currentTime + Player.gradualFadeMs / 1000,
|
||||||
|
)
|
||||||
|
|
||||||
|
this.nativeControls.updateIsPlaying(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
playbackMode(mode) {
|
||||||
|
if (typeof mode !== "string") {
|
||||||
|
return this.state.playback_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.playback_mode = mode
|
||||||
|
|
||||||
|
if (this.queue.currentItem) {
|
||||||
|
this.queue.currentItem.audio.loop =
|
||||||
|
this.state.playback_mode === "repeat"
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioPlayerStorage.set("mode", mode)
|
||||||
|
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
stopPlayback() {
|
||||||
|
if (this.queue.currentItem) {
|
||||||
|
this.queue.currentItem.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.flush()
|
||||||
|
|
||||||
|
this.abortPreloads()
|
||||||
|
|
||||||
|
this.state.playback_status = "stopped"
|
||||||
|
this.state.track_manifest = null
|
||||||
|
|
||||||
|
this.queue.currentItem = null
|
||||||
|
this.track_next_instances = []
|
||||||
|
this.track_prev_instances = []
|
||||||
|
|
||||||
|
this.nativeControls.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Audio Control
|
||||||
|
//
|
||||||
|
mute(to) {
|
||||||
|
if (app.isMobile && typeof to !== "boolean") {
|
||||||
|
this.console.warn("Cannot mute on mobile")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to === "toggle") {
|
||||||
|
to = !this.state.muted
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof to === "boolean") {
|
||||||
|
this.state.muted = to
|
||||||
|
this.queue.currentItem.audio.muted = to
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state.muted
|
||||||
|
}
|
||||||
|
|
||||||
|
volume(volume) {
|
||||||
|
if (typeof volume !== "number") {
|
||||||
|
return this.state.volume
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.isMobile) {
|
||||||
|
this.console.warn("Cannot change volume on mobile")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volume > 1) {
|
||||||
|
if (!app.cores.settings.get("player.allowVolumeOver100")) {
|
||||||
|
volume = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (volume < 0) {
|
||||||
|
volume = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.volume = volume
|
||||||
|
|
||||||
|
AudioPlayerStorage.set("volume", volume)
|
||||||
|
|
||||||
|
if (this.queue.currentItem) {
|
||||||
|
if (this.queue.currentItem.gainNode) {
|
||||||
|
this.queue.currentItem.gainNode.gain.value = this.state.volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.state.volume
|
||||||
|
}
|
||||||
|
|
||||||
return this.play(item)
|
seek(time) {
|
||||||
}
|
if (!this.queue.currentItem || !this.queue.currentItem.audio) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
//
|
// if time not provided, return current time
|
||||||
// Playback Control
|
if (typeof time === "undefined") {
|
||||||
//
|
return this.queue.currentItem.audio.currentTime
|
||||||
async togglePlayback() {
|
}
|
||||||
if (this.state.playback_status === "paused") {
|
|
||||||
await this.resumePlayback()
|
|
||||||
} else {
|
|
||||||
await this.pausePlayback()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pausePlayback() {
|
// if time is provided, seek to that time
|
||||||
if (!this.state.playback_status === "paused") {
|
if (typeof time === "number") {
|
||||||
return true
|
this.console.log(
|
||||||
}
|
`Seeking to ${time} | Duration: ${this.queue.currentItem.audio.duration}`,
|
||||||
|
)
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
this.queue.currentItem.audio.currentTime = time
|
||||||
if (!this.queue.currentItem) {
|
|
||||||
this.console.error("No audio instance")
|
return time
|
||||||
return null
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set gain exponentially
|
duration() {
|
||||||
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
|
if (!this.queue.currentItem || !this.queue.currentItem.audio) {
|
||||||
0.0001,
|
return false
|
||||||
this.audioContext.currentTime + (Player.gradualFadeMs / 1000)
|
}
|
||||||
)
|
|
||||||
|
return this.queue.currentItem.audio.duration
|
||||||
setTimeout(() => {
|
}
|
||||||
this.queue.currentItem.audio.pause()
|
|
||||||
resolve()
|
loop(to) {
|
||||||
}, Player.gradualFadeMs)
|
if (typeof to !== "boolean") {
|
||||||
|
this.console.warn("Loop must be a boolean")
|
||||||
this.nativeControls.updateIsPlaying(false)
|
return false
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
this.state.loop = to ?? !this.state.loop
|
||||||
async resumePlayback() {
|
|
||||||
if (!this.state.playback_status === "playing") {
|
if (this.queue.currentItem.audio) {
|
||||||
return true
|
this.queue.currentItem.audio.loop = this.state.loop
|
||||||
}
|
}
|
||||||
|
|
||||||
return await new Promise((resolve, reject) => {
|
return this.state.loop
|
||||||
if (!this.queue.currentItem) {
|
}
|
||||||
this.console.error("No audio instance")
|
|
||||||
return null
|
close() {
|
||||||
}
|
this.stopPlayback()
|
||||||
|
this.ui.detachPlayerComponent()
|
||||||
// ensure audio elemeto starts from 0 volume
|
}
|
||||||
this.queue.currentItem.gainNode.gain.value = 0.0001
|
|
||||||
|
registerService(serviceInteface) {
|
||||||
this.queue.currentItem.audio.play().then(() => {
|
this.serviceProviders.register(serviceInteface)
|
||||||
resolve()
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
// set gain exponentially
|
|
||||||
this.queue.currentItem.gainNode.gain.linearRampToValueAtTime(
|
|
||||||
this.state.volume,
|
|
||||||
this.audioContext.currentTime + (Player.gradualFadeMs / 1000)
|
|
||||||
)
|
|
||||||
|
|
||||||
this.nativeControls.updateIsPlaying(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
playbackMode(mode) {
|
|
||||||
if (typeof mode !== "string") {
|
|
||||||
return this.state.playback_mode
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.playback_mode = mode
|
|
||||||
|
|
||||||
if (this.queue.currentItem) {
|
|
||||||
this.queue.currentItem.audio.loop = this.state.playback_mode === "repeat"
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioPlayerStorage.set("mode", mode)
|
|
||||||
|
|
||||||
return mode
|
|
||||||
}
|
|
||||||
|
|
||||||
stopPlayback() {
|
|
||||||
if (this.queue.currentItem) {
|
|
||||||
this.queue.currentItem.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.queue.flush()
|
|
||||||
|
|
||||||
this.abortPreloads()
|
|
||||||
|
|
||||||
this.state.playback_status = "stopped"
|
|
||||||
this.state.track_manifest = null
|
|
||||||
|
|
||||||
this.queue.currentItem = null
|
|
||||||
this.track_next_instances = []
|
|
||||||
this.track_prev_instances = []
|
|
||||||
|
|
||||||
this.nativeControls.destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Audio Control
|
|
||||||
//
|
|
||||||
mute(to) {
|
|
||||||
if (app.isMobile && typeof to !== "boolean") {
|
|
||||||
this.console.warn("Cannot mute on mobile")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to === "toggle") {
|
|
||||||
to = !this.state.muted
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof to === "boolean") {
|
|
||||||
this.state.muted = to
|
|
||||||
this.queue.currentItem.audio.muted = to
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.state.muted
|
|
||||||
}
|
|
||||||
|
|
||||||
volume(volume) {
|
|
||||||
if (typeof volume !== "number") {
|
|
||||||
return this.state.volume
|
|
||||||
}
|
|
||||||
|
|
||||||
if (app.isMobile) {
|
|
||||||
this.console.warn("Cannot change volume on mobile")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (volume > 1) {
|
|
||||||
if (!app.cores.settings.get("player.allowVolumeOver100")) {
|
|
||||||
volume = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (volume < 0) {
|
|
||||||
volume = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.volume = volume
|
|
||||||
|
|
||||||
AudioPlayerStorage.set("volume", volume)
|
|
||||||
|
|
||||||
if (this.queue.currentItem) {
|
|
||||||
if (this.queue.currentItem.gainNode) {
|
|
||||||
this.queue.currentItem.gainNode.gain.value = this.state.volume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.state.volume
|
|
||||||
}
|
|
||||||
|
|
||||||
seek(time) {
|
|
||||||
if (!this.queue.currentItem || !this.queue.currentItem.audio) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// if time not provided, return current time
|
|
||||||
if (typeof time === "undefined") {
|
|
||||||
return this.queue.currentItem.audio.currentTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// if time is provided, seek to that time
|
|
||||||
if (typeof time === "number") {
|
|
||||||
this.console.log(`Seeking to ${time} | Duration: ${this.queue.currentItem.audio.duration}`)
|
|
||||||
|
|
||||||
this.queue.currentItem.audio.currentTime = time
|
|
||||||
|
|
||||||
return time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
duration() {
|
|
||||||
if (!this.queue.currentItem || !this.queue.currentItem.audio) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.queue.currentItem.audio.duration
|
|
||||||
}
|
|
||||||
|
|
||||||
loop(to) {
|
|
||||||
if (typeof to !== "boolean") {
|
|
||||||
this.console.warn("Loop must be a boolean")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.loop = to ?? !this.state.loop
|
|
||||||
|
|
||||||
if (this.queue.currentItem.audio) {
|
|
||||||
this.queue.currentItem.audio.loop = this.state.loop
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.state.loop
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.stopPlayback()
|
|
||||||
this.ui.detachPlayerComponent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
42
packages/app/src/cores/player/providers/comtymusic.js
Normal file
42
packages/app/src/cores/player/providers/comtymusic.js
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user