From 7748e5943915c7f47d43b520ef23aabf48ac56dd Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Mon, 7 Aug 2023 15:46:27 +0000 Subject: [PATCH] remove old player core --- .../{playerv2 => player}/mediaSession.js | 0 .../cores/{playerv2 => player}/player.core.js | 21 + .../{playerv2 => player}/player.storage.js | 0 packages/app/src/cores/player/player_dep.js | 1264 ----------------- .../app/src/cores/player/processorNode.js | 172 --- .../processors/bpmNode/index.js | 0 .../player/processors/compressorNode/index.js | 4 +- .../cores/player/processors/eqNode/index.js | 4 +- .../cores/player/processors/gainNode/index.js | 4 +- .../{playerv2 => player}/processors/index.js | 0 .../{playerv2 => player}/processors/node.js | 0 .../player/servicesToManifestResolver.js | 14 +- packages/app/src/cores/player/storage.js | 25 - .../processors/compressorNode/index.js | 55 - .../cores/playerv2/processors/eqNode/index.js | 131 -- .../playerv2/processors/gainNode/index.js | 60 - .../playerv2/servicesToManifestResolver.js | 23 - 17 files changed, 35 insertions(+), 1742 deletions(-) rename packages/app/src/cores/{playerv2 => player}/mediaSession.js (100%) rename packages/app/src/cores/{playerv2 => player}/player.core.js (97%) rename packages/app/src/cores/{playerv2 => player}/player.storage.js (100%) delete mode 100755 packages/app/src/cores/player/player_dep.js delete mode 100644 packages/app/src/cores/player/processorNode.js rename packages/app/src/cores/{playerv2 => player}/processors/bpmNode/index.js (100%) rename packages/app/src/cores/{playerv2 => player}/processors/index.js (100%) rename packages/app/src/cores/{playerv2 => player}/processors/node.js (100%) delete mode 100644 packages/app/src/cores/player/storage.js delete mode 100644 packages/app/src/cores/playerv2/processors/compressorNode/index.js delete mode 100644 packages/app/src/cores/playerv2/processors/eqNode/index.js delete mode 100644 packages/app/src/cores/playerv2/processors/gainNode/index.js delete mode 100644 packages/app/src/cores/playerv2/servicesToManifestResolver.js diff --git a/packages/app/src/cores/playerv2/mediaSession.js b/packages/app/src/cores/player/mediaSession.js similarity index 100% rename from packages/app/src/cores/playerv2/mediaSession.js rename to packages/app/src/cores/player/mediaSession.js diff --git a/packages/app/src/cores/playerv2/player.core.js b/packages/app/src/cores/player/player.core.js similarity index 97% rename from packages/app/src/cores/playerv2/player.core.js rename to packages/app/src/cores/player/player.core.js index f7fb5b2b..a7bcf239 100755 --- a/packages/app/src/cores/playerv2/player.core.js +++ b/packages/app/src/cores/player/player.core.js @@ -109,9 +109,30 @@ export default class Player extends Core { }) } + internalEvents = { + "player.state.update:loading": () => { + app.cores.sync.music.dispatchEvent("music.player.state.update", this.state) + }, + "player.state.update:track_manifest": () => { + app.cores.sync.music.dispatchEvent("music.player.state.update", this.state) + }, + "player.state.update:playback_status": () => { + app.cores.sync.music.dispatchEvent("music.player.state.update", this.state) + }, + "player.seeked": (to) => { + app.cores.sync.music.dispatchEvent("music.player.seek", to) + }, + } + async onInitialize() { + this.native_controls.initialize() + this.initializeAudioProcessors() + for (const [eventName, eventHandler] of Object.entries(this.internalEvents)) { + this.eventBus.on(eventName, eventHandler) + } + Observable.observe(this.state, async (changes) => { try { changes.forEach((change) => { diff --git a/packages/app/src/cores/playerv2/player.storage.js b/packages/app/src/cores/player/player.storage.js similarity index 100% rename from packages/app/src/cores/playerv2/player.storage.js rename to packages/app/src/cores/player/player.storage.js diff --git a/packages/app/src/cores/player/player_dep.js b/packages/app/src/cores/player/player_dep.js deleted file mode 100755 index 0841cc10..00000000 --- a/packages/app/src/cores/player/player_dep.js +++ /dev/null @@ -1,1264 +0,0 @@ -import Core from "evite/src/core" -import { Observable } from "object-observer" -import { FastAverageColor } from "fast-average-color" -import { CapacitorMusicControls } from 'capacitor-music-controls-plugin-v3' - -import PlaylistModel from "comty.js/models/playlists" -import SyncModel from "comty.js/models/sync" - -import EmbbededMediaPlayer from "components/Player/MediaPlayer" -import BackgroundMediaPlayer from "components/Player/BackgroundMediaPlayer" - -import AudioPlayerStorage from "./storage" - -import EqProcessorNode from "./processors/eqNode" -import GainProcessorNode from "./processors/gainNode" -import CompressorProcessorNode from "./processors/compressorNode" - -import servicesToManifestResolver from "./servicesToManifestResolver" - -function useMusicSync(event, data) { - const currentRoomData = app.cores.sync.music.currentRoomData() - - if (!currentRoomData) { - this.console.warn("No room data available") - return false - } - - return app.cores.sync.music.dispatchEvent(event, data) -} - -// this is the time tooks to fade in/out the volume when playing/pausing -const gradualFadeMs = 150 - -const defaultAudioProccessors = [ - EqProcessorNode, - GainProcessorNode, - CompressorProcessorNode, -] - -import MediaSession from "./mediaSession" - -// TODO: Check if source playing is a stream. Also handle if it's a stream resuming after a pause will seek to the last position -export default class Player extends Core { - static dependencies = [ - "api", - "settings" - ] - - static websocketListen = "music" - - static namespace = "player_dep" - - // default statics - static maxBufferLoadQueue = 2 - - static defaultSampleRate = 48000 - - native_controls = new MediaSession() - - currentDomWindow = null - - audioContext = new AudioContext({ - sampleRate: AudioPlayerStorage.get("sample_rate") ?? Player.defaultSampleRate, - latencyHint: "playback" - }) - - bufferLoadQueue = [] - bufferLoadQueueLoading = false - - audioQueueHistory = [] - audioQueue = [] - audioProcessors = [] - - currentAudioInstance = null - - fac = new FastAverageColor() - - state = Observable.from({ - loading: false, - minimized: false, - audioMuted: app.isMobile ? false : (AudioPlayerStorage.get("mute") ?? false), - playbackMode: AudioPlayerStorage.get("mode") ?? "repeat", - audioVolume: app.isMobile ? 1 : (AudioPlayerStorage.get("volume") ?? 0.3), - velocity: AudioPlayerStorage.get("velocity") ?? 1, - - coverColorAnalysis: null, - currentAudioManifest: null, - playbackStatus: "stopped", - livestream: false, - syncMode: false, - syncModeLocked: false, - startingNew: false, - liked: false, - }) - - public = { - audioContext: this.audioContext, - attachPlayerComponent: this.attachPlayerComponent.bind(this), - detachPlayerComponent: this.detachPlayerComponent.bind(this), - toggleMute: this.toggleMute.bind(this), - minimize: this.toggleMinimize.bind(this), - volume: this.volume.bind(this), - start: this.start.bind(this), - startPlaylist: this.startPlaylist.bind(this), - isIdCurrent: function (id) { - this.console.log("isIdCurrent", id, this.state.currentAudioManifest?._id === id) - - return this.state.currentAudioManifest?._id === id - }.bind(this), - isIdPlaying: function (id) { - return this.public.isIdCurrent(id) && this.state.playbackStatus === "playing" - }.bind(this), - attachProcessor: function (name) { - // find the processor by refName - const processor = this.audioProcessors.find((_processor) => { - return _processor.constructor.refName === name - }) - - if (!processor) { - throw new Error("Processor not found") - } - - if (typeof processor._attach !== "function") { - throw new Error("Processor does not support attach") - } - - this.currentAudioInstance = processor._attach(this.currentAudioInstance) - - // attach last one to the destination - //this.currentAudioInstance.attachedProcessors[this.currentAudioInstance.attachedProcessors.length - 1].processor.connect(this.audioContext.destination) - }.bind(this), - dettachProcessor: async function (name) { - // find the processor by refName - const processor = this.currentAudioInstance.attachedProcessors.find((_processor) => { - return _processor.constructor.refName === name - }) - - if (!processor) { - throw new Error("Processor not found") - } - - if (typeof processor._detach !== "function") { - throw new Error("Processor does not support detach") - } - - return this.currentAudioInstance = await processor._detach(this.currentAudioInstance) - }.bind(this), - playback: { - mode: function (mode) { - if (mode) { - this.state.playbackMode = mode - } - - return this.state.playbackMode - }.bind(this), - toggle: function () { - if (!this.currentAudioInstance) { - this.console.error("No audio instance") - return null - } - - if (this.state.syncModeLocked) { - this.console.warn("Sync mode is locked, cannot do this action") - return false - } - - if (this.currentAudioInstance.audioElement.paused) { - this.resumePlayback() - } else { - this.pausePlayback() - } - }.bind(this), - play: this.resumePlayback.bind(this), - pause: this.pausePlayback.bind(this), - next: this.next.bind(this), - previous: this.previous.bind(this), - stop: this.stop.bind(this), - status: function () { - return this.state.playbackStatus - }.bind(this), - }, - getState: function (key) { - if (key) { - return this.state[key] - } - - return this.state - }.bind(this), - toggleCurrentTrackLike: this.toggleCurrentTrackLike.bind(this), - seek: this.seek.bind(this), - duration: this.duration.bind(this), - velocity: this.velocity.bind(this), - close: this.close.bind(this), - toggleSyncMode: this.toggleSyncMode.bind(this), - currentState: this.currentState.bind(this), - setSampleRate: this.setSampleRate.bind(this), - } - - wsEvents = { - "music:self:track:toggle:like": (data) => { - const to = data.action === "liked" - - if (this.state.liked !== to) { - this.state.liked = to - } - } - } - - async initializeAudioProcessors() { - if (this.audioProcessors.length > 0) { - this.console.log("Destroying audio processors") - - this.audioProcessors.forEach((processor) => { - this.console.log(`Destroying audio processor ${processor.constructor.name}`, processor) - processor._destroy() - }) - - this.audioProcessors = [] - } - - for await (const defaultProccessor of defaultAudioProccessors) { - this.audioProcessors.push(new defaultProccessor(this)) - } - - for await (const processor of this.audioProcessors) { - this.console.log(`Initializing audio processor ${processor.constructor.name}`, processor) - - if (typeof processor._init === "function") { - try { - await processor._init(this.audioContext) - } catch (error) { - this.console.error(`Failed to initialize audio processor ${processor.constructor.name} >`, error) - continue - } - } - - // check if processor has exposed public methods - if (processor.exposeToPublic) { - Object.entries(processor.exposeToPublic).forEach(([key, value]) => { - const refName = processor.constructor.refName - - if (typeof this.public[refName] === "undefined") { - // by default create a empty object - this.public[refName] = {} - } - - this.public[refName][key] = value - }) - } - } - } - - observeStateChanges() { - Observable.observe(this.state, (changes) => { - changes.forEach((change) => { - if (change.type === "update") { - switch (change.path[0]) { - case "livestream": { - app.eventBus.emit("player.livestream.update", change.object.livestream) - - break - } - case "trackBPM": { - app.eventBus.emit("player.bpm.update", change.object.trackBPM) - - break - } - case "crossfading": { - app.eventBus.emit("player.crossfading.update", change.object.crossfading) - - break - } - case "loading": { - app.eventBus.emit("player.loading.update", change.object.loading) - - if (this.state.syncMode) { - useMusicSync("music:player:loading", { - loading: change.object.loading, - state: this.currentState() - }) - } - - break - } - case "currentAudioManifest": { - app.eventBus.emit("player.current.update", change.object.currentAudioManifest) - - if (change.object.currentAudioManifest) { - // analyze cover color - - if (change.object.currentAudioManifest.cover || change.object.currentAudioManifest.thumbnail) { - this.fac.getColorAsync(change.object.currentAudioManifest.cover ?? change.object.currentAudioManifest.thumbnail) - .then((color) => { - this.state.coverColorAnalysis = color - }) - .catch((err) => { - this.console.error(err) - }) - } - } - - if (this.state.syncMode) { - useMusicSync("music:player:start", { - manifest: { - ...change.object.currentAudioManifest, - service: "inherit", - }, - state: this.currentState() - }) - } - - break - } - case "coverColorAnalysis": { - app.eventBus.emit("player.coverColorAnalysis.update", change.object.coverColorAnalysis) - - break - } - case "audioMuted": { - AudioPlayerStorage.set("muted", change.object.audioMuted) - - app.eventBus.emit("player.mute.update", change.object.audioMuted) - - break - } - case "audioVolume": { - AudioPlayerStorage.set("volume", change.object.audioVolume) - - app.eventBus.emit("player.volume.update", change.object.audioVolume) - - break - } - case "velocity": { - AudioPlayerStorage.set("velocity", change.object.velocity) - - app.eventBus.emit("player.velocity.update", change.object.velocity) - - break - } - case "playbackMode": { - AudioPlayerStorage.set("mode", change.object.playbackMode) - - this.currentAudioInstance.audioElement.loop = change.object.playbackMode === "repeat" - - app.eventBus.emit("player.mode.update", change.object.playbackMode) - - break - } - case "playbackStatus": { - app.eventBus.emit("player.status.update", change.object.playbackStatus) - - if (this.state.syncMode) { - if (this.state.loading) { - return false - } - - useMusicSync("music:player:status", { - status: change.object.playbackStatus, - time: this.currentAudioInstance.audioElement.currentTime, - duration: this.currentAudioInstance.audioElement.duration, - startingNew: this.state.startingNew, - state: this.currentState(), - }) - } - - break - } - case "minimized": { - if (change.object.minimized) { - app.layout.sidebar.attachBottomItem("player", BackgroundMediaPlayer, { - noContainer: true - }) - } else { - app.layout.sidebar.removeBottomItem("player") - } - - app.eventBus.emit("player.minimized.update", change.object.minimized) - - break - } - case "syncModeLocked": { - app.eventBus.emit("player.syncModeLocked.update", change.object.syncModeLocked) - break - } - case "syncMode": { - app.eventBus.emit("player.syncMode.update", change.object.syncMode) - break - } - case "liked": { - app.eventBus.emit("player.toggle.like", change.object.liked) - break - } - } - } - }) - }) - } - - async onInitialize() { - this.initializeAudioProcessors() - this.observeStateChanges() - this.native_controls.initialize() - } - - async initializeAfterRuntimeInitialize() { - for (const [eventName, eventHandler] of Object.entries(this.wsEvents)) { - app.cores.api.listenEvent(eventName, eventHandler, Player.websocketListen) - } - - if (app.isMobile) { - this.state.audioVolume = 1 - } - } - - // - // UI Methods - // - - async toggleCurrentTrackLike() { - if (!this.currentAudioInstance) { - this.console.error("No track playing") - return false - } - - const currentId = this.currentAudioInstance.manifest._id - - const result = await PlaylistModel.toggleTrackLike(currentId).catch((err) => { - return null - }) - - if (result) { - this.state.liked = result.action === "liked" - } - } - - attachPlayerComponent() { - if (this.currentDomWindow) { - this.console.warn("EmbbededMediaPlayer already attached") - return false - } - - if (!app.layout.floatingStack) { - this.console.error("Floating stack not found") - return false - } - - this.currentDomWindow = app.layout.floatingStack.add("mediaPlayer", EmbbededMediaPlayer) - } - - detachPlayerComponent() { - if (!this.currentDomWindow) { - this.console.warn("EmbbededMediaPlayer not attached") - return false - } - - if (!app.layout.floatingStack) { - this.console.error("Floating stack not found") - return false - } - - app.layout.floatingStack.remove("mediaPlayer") - - this.currentDomWindow = null - } - - // - // Buffer methods - // - - enqueueLoadBuffer(audioElement) { - if (!audioElement) { - this.console.error("Audio element is required") - return false - } - - if (audioElement instanceof Audio) { - this.bufferLoadQueue.push(audioElement) - } - - if (!this.bufferLoadQueueLoading) { - this.bufferLoadQueueLoading = true - - this.loadNextQueueBuffer() - } - } - - async loadNextQueueBuffer() { - if (!this.bufferLoadQueue.length) { - this.bufferLoadQueueLoading = false - - return false - } - - if (this.bufferLoadQueueLoading >= Player.maxBufferLoadQueue) { - return false - } - - const audioElement = this.bufferLoadQueue.shift() - - if (audioElement.signal.aborted) { - this.console.warn("Aborted audio element") - - this.bufferLoadQueueLoading = false - - this.loadNextQueueBuffer() - - return false - } - - this.bufferLoadQueueLoading = true - - const preloadPromise = () => new Promise((resolve, reject) => { - audioElement.addEventListener("canplaythrough", () => { - resolve() - }, { once: true }) - - this.console.log("Preloading audio buffer", audioElement.src) - - audioElement.load() - }) - - await preloadPromise() - - this.bufferLoadQueueLoading = false - - this.loadNextQueueBuffer() - - return true - } - - async abortPreloads() { - for await (const instance of this.audioQueue) { - if (instance.abortController?.abort) { - instance.abortController.abort() - } - } - - // clear load buffer audio queue - this.loadBufferAudioQueue = [] - this.bufferLoadQueueLoading = false - } - - // - // Instance managing methods - // - - async destroyCurrentInstance({ sync = false } = {}) { - if (!this.currentAudioInstance) { - return false - } - - // stop playback - if (this.currentAudioInstance.audioElement) { - this.currentAudioInstance.audioElement.srcObj = null - this.currentAudioInstance.audioElement.src = null - - // if is in sync mode, just seek to last position to stop playback and avoid sync issues - this.currentAudioInstance.audioElement.pause() - } - - this.currentAudioInstance = null - - // reset livestream mode - this.state.livestream = false - } - - async createInstance(manifest) { - if (!manifest) { - this.console.error("Manifest is required") - return false - } - - if (typeof manifest === "string") { - manifest = { - src: manifest, - stream: false, - } - } - - // check if manifest has `manifest` property - if (manifest.service) { - if (manifest.service !== "inherit" && !manifest.source) { - const resolver = servicesToManifestResolver[manifest.service] - - if (!resolver) { - this.console.error(`Service ${manifest.service} is not supported`) - return false - } - - manifest = await resolver(manifest) - } - } - - if (!manifest.src && !manifest.source) { - this.console.error("Manifest source is required") - return false - } - - const source = manifest.src ?? manifest.source - - // if title is not set, use the audio source filename - if (!manifest.title) { - manifest.title = source.split("/").pop() - } - - let instanceObj = { - abortController: new AbortController(), - audioElement: new Audio(source), - media: null, - source: source, - manifest: manifest, - attachedProcessors: [], - } - - instanceObj.audioElement.signal = instanceObj.abortController.signal - instanceObj.audioElement.loop = this.state.playbackMode === "repeat" - instanceObj.audioElement.crossOrigin = "anonymous" - instanceObj.audioElement.preload = "none" - - // handle on end - instanceObj.audioElement.addEventListener("ended", () => { - // if is in sync locked mode, do noting - if (this.state.syncModeLocked) { - return false - } - - this.next() - }) - - instanceObj.audioElement.addEventListener("play", () => { - this.state.playbackStatus = "playing" - - instanceObj.audioElement.loop = this.state.playbackMode === "repeat" - }) - - instanceObj.audioElement.addEventListener("loadeddata", () => { - this.state.loading = false - - this.console.log("Loaded audio data", instanceObj.audioElement.src) - }) - - instanceObj.audioElement.addEventListener("playing", () => { - this.state.loading = false - - this.state.playbackStatus = "playing" - - if (this.state.startingNew) { - this.state.startingNew = false - } - - if (this.waitUpdateTimeout) { - clearTimeout(this.waitUpdateTimeout) - this.waitUpdateTimeout = null - } - }) - - instanceObj.audioElement.addEventListener("pause", () => { - this.state.playbackStatus = "paused" - - if (instanceObj.crossfadeInterval) { - clearInterval(instanceObj.crossfadeInterval) - } - }) - - instanceObj.audioElement.addEventListener("durationchange", (duration) => { - if (instanceObj.audioElement.paused) { - return - } - - app.eventBus.emit("player.duration.update", duration) - }) - - instanceObj.audioElement.addEventListener("waiting", () => { - if (instanceObj.audioElement.paused) { - return - } - - if (this.waitUpdateTimeout) { - clearTimeout(this.waitUpdateTimeout) - this.waitUpdateTimeout = null - } - - // if takes more than 200ms to load, update loading state - this.waitUpdateTimeout = setTimeout(() => { - this.state.loading = true - }, 200) - }) - - instanceObj.audioElement.addEventListener("seeked", () => { - app.eventBus.emit("player.seek.update", instanceObj.audioElement.currentTime) - - if (this.state.syncMode) { - useMusicSync("music:player:seek", { - position: instanceObj.audioElement.currentTime, - state: this.currentState(), - }) - } - }) - - // // detect if the audio is a live stream - // instanceObj.audioElement.addEventListener("loadedmetadata", () => { - // if (instanceObj.audioElement.duration === Infinity) { - // instanceObj.manifest.stream = true - // } - // }) - - //this.enqueueLoadBuffer(instanceObj.audioElement) - - instanceObj.media = this.audioContext.createMediaElementSource(instanceObj.audioElement) - - // storage media data on browser cache to improve performance - instanceObj.media.data = instanceObj.audioElement - - return instanceObj - } - - async attachProcessorsToInstance(instance) { - for await (const [index, processor] of this.audioProcessors.entries()) { - if (typeof processor._attach !== "function") { - this.console.error(`Processor ${processor.constructor.refName} not support attach`) - - continue - } - - instance = await processor._attach(instance, index) - } - - const lastProcessor = instance.attachedProcessors[instance.attachedProcessors.length - 1].processor - - this.console.log("Attached processors", instance.attachedProcessors) - - // now attach to destination - lastProcessor.connect(this.audioContext.destination) - - return instance - } - - // - // Playback methods - // - - async play(instance, params = {}) { - this.state.startingNew = true - - if (typeof instance === "number") { - instance = this.audioQueue[instance] - } - - if (!instance) { - throw new Error("Audio instance is required") - } - - if (this.audioContext.state === "suspended") { - this.audioContext.resume() - } - - if (!this.currentDomWindow) { - this.attachPlayerComponent() - } - - // check if already exists a current instance - // if exists, destroy it - // but before, try to detach the last procesor attched to destination - if (this.currentAudioInstance) { - this.currentAudioInstance = this.currentAudioInstance.attachedProcessors[this.currentAudioInstance.attachedProcessors.length - 1]._destroy(this.currentAudioInstance) - - this.destroyCurrentInstance() - } - - // attach processors - instance = await this.attachProcessorsToInstance(instance) - - // now set the current instance - this.currentAudioInstance = instance - - this.state.currentAudioManifest = instance.manifest - - this.state.liked = instance.manifest.liked - - // set time to 0 - this.currentAudioInstance.audioElement.currentTime = 0 - - if (params.time >= 0) { - this.currentAudioInstance.audioElement.currentTime = params.time - } - - if (params.volume >= 0) { - this.currentAudioInstance.gainNode.gain.value = params.volume - } else { - this.currentAudioInstance.gainNode.gain.value = this.state.audioVolume - } - - instance.audioElement.muted = this.state.audioMuted - - // reconstruct audio src if is not set - if (instance.audioElement.src !== instance.manifest.source) { - instance.audioElement.src = instance.manifest.source - } - - instance.audioElement.load() - - instance.audioElement.play() - - // set navigator metadata - this.native_controls.update(instance.manifest) - - // check if the audio is a live stream when metadata is loaded - instance.audioElement.addEventListener("loadedmetadata", () => { - this.console.log("loadedmetadata", instance.audioElement.duration) - - if (instance.audioElement.duration === Infinity) { - instance.manifest.stream = true - - this.state.livestream = true - } - - // enqueue preload next audio - if (this.audioQueue.length > 1) { - const nextAudio = this.audioQueue[1] - - this.enqueueLoadBuffer(nextAudio.audioElement) - } - }, { once: true }) - } - - async startPlaylist(playlist, startIndex = 0, { sync = false } = {}) { - if (this.state.syncModeLocked && !sync) { - this.console.warn("Sync mode is locked, cannot do this action") - return false - } - - // playlist is an array of audio manifests - if (!playlist || !Array.isArray(playlist)) { - throw new Error("Playlist is required") - } - - - // check if the array has strings, if so its means that is the track id, then fetch the track - if (playlist.some((item) => typeof item === "string")) { - this.console.log("Resolving missing manifests by ids...") - playlist = await this.getTracksByIds(playlist) - } - - this.console.log("Starting playlist", playlist) - - this.state.loading = true - - // !IMPORTANT: abort preloads before destroying current instance - await this.abortPreloads() - - await this.destroyCurrentInstance() - - // clear current queue - this.audioQueue = [] - this.audioQueueHistory = [] - - // sort playlist entries to prioritize instance creating from the startIndex - playlist[startIndex].first = true - - const afterPlaylist = playlist.slice(startIndex) - const beforePlaylist = playlist.slice(0, startIndex).reverse() - - for await (const [index, manifest] of afterPlaylist.entries()) { - const instance = await this.createInstance(manifest) - - this.audioQueue.push(instance) - - if (index === 0) { - this.play(this.audioQueue[0]) - } - } - - for await (const [index, manifest] of beforePlaylist.entries()) { - const instance = await this.createInstance(manifest) - - this.audioQueueHistory.push(instance) - } - - return true - } - - async start(manifest, { sync = false, time } = {}) { - if (this.state.syncModeLocked && !sync) { - this.console.warn("Sync mode is locked, cannot do this action") - return false - } - - this.state.startingNew = true - - // !IMPORTANT: abort preloads before destroying current instance - await this.abortPreloads() - - await this.destroyCurrentInstance({ - sync - }) - - const instance = await this.createInstance(manifest) - - this.audioQueue = [instance] - - this.audioQueueHistory = [] - - this.state.loading = true - - this.play(this.audioQueue[0], { - time: time ?? 0 - }) - } - - next({ sync = false } = {}) { - if (this.state.syncModeLocked && !sync) { - this.console.warn("Sync mode is locked, cannot do this action") - return false - } - - if (this.audioQueue.length > 0) { - // move current audio instance to history - this.audioQueueHistory.push(this.audioQueue.shift()) - } - - // check if there is a next audio in queue - if (this.audioQueue.length === 0) { - this.console.log("no more audio on queue, stopping playback") - - this.destroyCurrentInstance() - - this.state.playbackStatus = "stopped" - this.state.currentAudioManifest = null - - return false - } - - let nextIndex = 0 - - // if is in shuffle mode, play a random audio - if (this.state.playbackMode === "shuffle") { - nextIndex = Math.floor(Math.random() * this.audioQueue.length) - } - - // play next audio - this.play(this.audioQueue[nextIndex]) - } - - previous({ sync = false } = {}) { - if (this.state.syncModeLocked && !sync) { - this.console.warn("Sync mode is locked, cannot do this action") - return false - } - - if (this.audioQueueHistory.length > 0) { - // move current audio instance to queue - this.audioQueue.unshift(this.audioQueueHistory.pop()) - - // play previous audio - this.play(this.audioQueue[0]) - } - - // check if there is a previous audio in history - if (this.audioQueueHistory.length === 0) { - // if there is no previous audio, start again from the first audio - this.play(this.audioQueue[0]) - } - } - - async pausePlayback() { - return await new Promise((resolve, reject) => { - if (!this.currentAudioInstance) { - this.console.error("No audio instance") - return null - } - - // set gain exponentially - this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime( - 0.0001, - this.audioContext.currentTime + (gradualFadeMs / 1000) - ) - - setTimeout(() => { - this.currentAudioInstance.audioElement.pause() - resolve() - }, gradualFadeMs) - - this.native_controls.updateIsPlaying(false) - }) - } - - async resumePlayback() { - return await new Promise((resolve, reject) => { - if (!this.currentAudioInstance) { - this.console.error("No audio instance") - return null - } - - // ensure audio elemeto starts from 0 volume - this.currentAudioInstance.gainNode.gain.value = 0.0001 - - this.currentAudioInstance.audioElement.play().then(() => { - resolve() - }) - - // set gain exponentially - this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime( - this.state.audioVolume, - this.audioContext.currentTime + (gradualFadeMs / 1000) - ) - - this.native_controls.updateIsPlaying(true) - }) - } - - stop() { - this.destroyCurrentInstance() - - this.abortPreloads() - - this.state.playbackStatus = "stopped" - this.state.currentAudioManifest = null - - this.state.livestream = false - - this.audioQueue = [] - - this.native_controls.destroy() - } - - close() { - this.stop() - this.detachPlayerComponent() - } - - toggleMute(to) { - if (app.isMobile) { - this.console.warn("Cannot mute on mobile") - return false - } - - this.state.audioMuted = to ?? !this.state.audioMuted - - if (this.currentAudioInstance) { - this.currentAudioInstance.audioElement.muted = this.state.audioMuted - } - - return this.state.audioMuted - } - - toggleMinimize(to) { - this.state.minimized = to ?? !this.state.minimized - - return this.state.minimized - } - - volume(volume) { - if (typeof volume !== "number") { - return this.state.audioVolume - } - - 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.audioVolume = volume - - if (this.currentAudioInstance) { - if (this.currentAudioInstance.gainNode) { - this.currentAudioInstance.gainNode.gain.value = this.state.audioVolume - } - } - - return this.state.audioVolume - } - - seek(time, { sync = false } = {}) { - if (!this.currentAudioInstance) { - return false - } - - // if time not provided, return current time - if (typeof time === "undefined") { - return this.currentAudioInstance.audioElement.currentTime - } - - if (this.state.syncModeLocked && !sync) { - this.console.warn("Sync mode is locked, cannot do this action") - return false - } - - // if time is provided, seek to that time - if (typeof time === "number") { - this.currentAudioInstance.audioElement.currentTime = time - - return time - } - } - - duration() { - if (!this.currentAudioInstance) { - return false - } - - return this.currentAudioInstance.audioElement.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.currentAudioInstance) { - this.currentAudioInstance.audioElement.loop = this.state.loop - } - - return this.state.loop - } - - velocity(to) { - if (this.state.syncModeLocked) { - this.console.warn("Sync mode is locked, cannot do this action") - return false - } - - if (typeof to !== "number") { - this.console.warn("Velocity must be a number") - return false - } - - this.state.velocity = to - - if (this.currentAudioInstance) { - this.currentAudioInstance.audioElement.playbackRate = this.state.velocity - } - - return this.state.velocity - } - - collapse(to) { - if (typeof to !== "boolean") { - this.console.warn("Collapse must be a boolean") - return false - } - - this.state.collapsed = to ?? !this.state.collapsed - - return this.state.collapsed - } - - toggleSyncMode(to, lock) { - if (typeof to !== "boolean") { - this.console.warn("Sync mode must be a boolean") - return false - } - - this.state.syncMode = to ?? !this.state.syncMode - - this.state.syncModeLocked = lock ?? false - - this.console.log(`Sync mode is now ${this.state.syncMode ? "enabled" : "disabled"} | Locked: ${this.state.syncModeLocked ? "yes" : "no"}`) - - return this.state.syncMode - } - - currentState() { - return { - playbackStatus: this.state.playbackStatus, - colorAnalysis: this.state?.coverColorAnalysis ?? null, - manifest: this.currentAudioInstance?.manifest ?? null, - loading: this.state.loading, - time: this.seek(), - duration: this.currentAudioInstance?.audioElement?.duration ?? null, - audioMuted: this.state.audioMuted, - audioVolume: this.state.audioVolume, - } - } - - async getTracksByIds(list) { - if (!Array.isArray(list)) { - this.console.warn("List must be an array") - return false - } - - let ids = [] - - list.forEach((item) => { - if (typeof item === "string") { - ids.push(item) - } - }) - - if (ids.length === 0) { - return list - } - - const fetchedTracks = await PlaylistModel.getTracks(ids).catch((err) => { - this.console.error(err) - return false - }) - - if (!fetchedTracks) { - return list - } - - // replace fetched tracks with the ones in the list - fetchedTracks.forEach((fetchedTrack) => { - const index = list.findIndex((item) => item === fetchedTrack._id) - - if (index !== -1) { - list[index] = fetchedTrack - } - }) - - return list - } - - async setSampleRate(to) { - // must be a integer - if (typeof to !== "number") { - this.console.error("Sample rate must be a number") - return this.audioContext.sampleRate - } - - // must be a integer - if (!Number.isInteger(to)) { - this.console.error("Sample rate must be a integer") - return this.audioContext.sampleRate - } - - return await new Promise((resolve, reject) => { - app.confirm({ - title: "Change sample rate", - content: `To change the sample rate, the app needs to be reloaded. Do you want to continue?`, - onOk: () => { - try { - this.audioContext = new AudioContext({ sampleRate: to }) - - AudioPlayerStorage.set("sample_rate", to) - - app.navigation.reload() - - return resolve(this.audioContext.sampleRate) - } catch (error) { - app.message.error(`Failed to change sample rate, ${error.message}`) - return resolve(this.audioContext.sampleRate) - } - }, - onCancel: () => { - return resolve(this.audioContext.sampleRate) - } - }) - }) - } -} \ No newline at end of file diff --git a/packages/app/src/cores/player/processorNode.js b/packages/app/src/cores/player/processorNode.js deleted file mode 100644 index 6e902076..00000000 --- a/packages/app/src/cores/player/processorNode.js +++ /dev/null @@ -1,172 +0,0 @@ -export default class ProcessorNode { - constructor(PlayerCore) { - if (!PlayerCore) { - throw new Error("PlayerCore is required") - } - - this.PlayerCore = PlayerCore - this.audioContext = PlayerCore.audioContext - } - - async _init() { - // check if has init method - if (typeof this.init === "function") { - await this.init(this.audioContext) - } - - // check if has declared bus events - if (typeof this.busEvents === "object") { - Object.entries(this.busEvents).forEach((event, fn) => { - app.eventBus.on(event, fn) - }) - } - - if (typeof this.processor._last === "undefined") { - this.processor._last = this.processor - } - - return this - } - - _attach(instance, index) { - if (typeof instance !== "object") { - instance = this.PlayerCore.currentAudioInstance - } - - // check if has dependsOnSettings - if (Array.isArray(this.constructor.dependsOnSettings)) { - // check if the instance has the settings - if (!this.constructor.dependsOnSettings.every((setting) => app.cores.settings.get(setting))) { - this.console.warn(`Skipping attachment for [${this.constructor.refName ?? this.constructor.name}] node, cause is not passing the settings dependecy > ${this.constructor.dependsOnSettings.join(", ")}`) - - return instance - } - } - - // if index is not defined, attach to the last node - if (!index) { - index = instance.attachedProcessors.length - } - - const prevNode = instance.attachedProcessors[index - 1] - const nextNode = instance.attachedProcessors[index + 1] - - const currentIndex = this._findIndex(instance) - - // check if is already attached - if (currentIndex !== false) { - this.console.warn(`[${this.constructor.refName ?? this.constructor.name}] node is already attached`) - - return instance - } - - // first check if has prevNode and if is connected to something - // if has, disconnect it - // if it not has, its means that is the first node, so connect to the media source - if (prevNode && prevNode.processor._last.numberOfOutputs > 0) { - //this.console.log(`[${this.constructor.refName ?? this.constructor.name}] node is already attached to the previous node, disconnecting...`) - // if has outputs, disconnect from the next node - prevNode.processor._last.disconnect() - - // now, connect to the processor - prevNode.processor._last.connect(this.processor) - } else { - //this.console.log(`[${this.constructor.refName ?? this.constructor.name}] node is the first node, connecting to the media source...`) - instance.media.connect(this.processor) - } - - // now, check if it has a next node - // if has, connect to it - // if not, connect to the destination - if (nextNode) { - this.processor.connect(nextNode.processor) - } - - // add to the attachedProcessors - instance.attachedProcessors.splice(index, 0, this) - - // handle instance mutation - if (typeof this.mutateInstance === "function") { - instance = this.mutateInstance(instance) - } - - return instance - } - - _detach(instance) { - if (typeof instance !== "object") { - instance = this.PlayerCore.currentAudioInstance - } - - // find index of the node within the attachedProcessors serching for matching refName - const index = this._findIndex(instance) - - if (!index) { - return instance - } - - // retrieve the previous and next nodes - const prevNode = instance.attachedProcessors[index - 1] - const nextNode = instance.attachedProcessors[index + 1] - - // check if has previous node and if has outputs - if (prevNode && prevNode.processor._last.numberOfOutputs > 0) { - // if has outputs, disconnect from the previous node - prevNode.processor._last.disconnect() - } - - // disconnect - instance = this._destroy(instance) - - // now, connect the previous node to the next node - if (prevNode && nextNode) { - prevNode.processor._last.connect(nextNode.processor) - } else { - // it means that this is the last node, so connect to the destination - prevNode.processor._last.connect(this.audioContext.destination) - } - - return instance - } - - _destroy(instance) { - if (typeof instance !== "object") { - instance = this.PlayerCore.currentAudioInstance - } - - const index = this._findIndex(instance) - - if (!index) { - return instance - } - - this.processor.disconnect() - - instance.attachedProcessors.splice(index, 1) - - return instance - } - - _findIndex(instance) { - if (!instance) { - instance = this.PlayerCore.currentAudioInstance - } - - if (!instance) { - this.console.warn(`Instance is not defined`) - - return false - } - - // find index of the node within the attachedProcessors serching for matching refName - const index = instance.attachedProcessors.findIndex((node) => { - return node.constructor.refName === this.constructor.refName - }) - - if (index === -1) { - return false - } - - return index - } -} \ No newline at end of file diff --git a/packages/app/src/cores/playerv2/processors/bpmNode/index.js b/packages/app/src/cores/player/processors/bpmNode/index.js similarity index 100% rename from packages/app/src/cores/playerv2/processors/bpmNode/index.js rename to packages/app/src/cores/player/processors/bpmNode/index.js diff --git a/packages/app/src/cores/player/processors/compressorNode/index.js b/packages/app/src/cores/player/processors/compressorNode/index.js index a610640c..14aa0083 100644 --- a/packages/app/src/cores/player/processors/compressorNode/index.js +++ b/packages/app/src/cores/player/processors/compressorNode/index.js @@ -1,5 +1,5 @@ -import AudioPlayerStorage from "../../storage" -import ProcessorNode from "../../processorNode" +import AudioPlayerStorage from "../../player.storage" +import ProcessorNode from "../node" export default class CompressorProcessorNode extends ProcessorNode { static refName = "compressor" diff --git a/packages/app/src/cores/player/processors/eqNode/index.js b/packages/app/src/cores/player/processors/eqNode/index.js index 2843a75f..3b27f230 100644 --- a/packages/app/src/cores/player/processors/eqNode/index.js +++ b/packages/app/src/cores/player/processors/eqNode/index.js @@ -1,5 +1,5 @@ -import ProcessorNode from "../../processorNode" -import AudioPlayerStorage from "../../storage" +import ProcessorNode from "../node" +import AudioPlayerStorage from "../../player.storage" export default class EqProcessorNode extends ProcessorNode { static refName = "eq" diff --git a/packages/app/src/cores/player/processors/gainNode/index.js b/packages/app/src/cores/player/processors/gainNode/index.js index bf0f53d0..1098d753 100644 --- a/packages/app/src/cores/player/processors/gainNode/index.js +++ b/packages/app/src/cores/player/processors/gainNode/index.js @@ -1,5 +1,5 @@ -import AudioPlayerStorage from "../../storage" -import ProcessorNode from "../../processorNode" +import AudioPlayerStorage from "../../player.storage" +import ProcessorNode from "../node" export default class GainProcessorNode extends ProcessorNode { static refName = "gain" diff --git a/packages/app/src/cores/playerv2/processors/index.js b/packages/app/src/cores/player/processors/index.js similarity index 100% rename from packages/app/src/cores/playerv2/processors/index.js rename to packages/app/src/cores/player/processors/index.js diff --git a/packages/app/src/cores/playerv2/processors/node.js b/packages/app/src/cores/player/processors/node.js similarity index 100% rename from packages/app/src/cores/playerv2/processors/node.js rename to packages/app/src/cores/player/processors/node.js diff --git a/packages/app/src/cores/player/servicesToManifestResolver.js b/packages/app/src/cores/player/servicesToManifestResolver.js index 38136c29..ba68cd39 100644 --- a/packages/app/src/cores/player/servicesToManifestResolver.js +++ b/packages/app/src/cores/player/servicesToManifestResolver.js @@ -4,17 +4,19 @@ export default { "tidal": async (manifest) => { const resolvedManifest = await SyncModel.tidalCore.getTrackManifest(manifest.id) - this.console.log(resolvedManifest) - manifest.source = resolvedManifest.playback.url - manifest.title = resolvedManifest.metadata.title - manifest.artist = resolvedManifest.metadata.artists.map(artist => artist.name).join(", ") - manifest.album = resolvedManifest.metadata.album.title + if (!manifest.metadata) { + manifest.metadata = {} + } + + manifest.metadata.title = resolvedManifest.metadata.title + manifest.metadata.artist = resolvedManifest.metadata.artists.map(artist => artist.name).join(", ") + manifest.metadata.album = resolvedManifest.metadata.album.title const coverUID = resolvedManifest.metadata.album.cover.replace(/-/g, "/") - manifest.cover = `https://resources.tidal.com/images/${coverUID}/1280x1280.jpg` + manifest.metadata.cover = `https://resources.tidal.com/images/${coverUID}/1280x1280.jpg` return manifest } diff --git a/packages/app/src/cores/player/storage.js b/packages/app/src/cores/player/storage.js deleted file mode 100644 index b21a1d5f..00000000 --- a/packages/app/src/cores/player/storage.js +++ /dev/null @@ -1,25 +0,0 @@ -import store from "store" - -export default class AudioPlayerStorage { - static storeKey = "audioPlayer" - - static get(key) { - const data = store.get(AudioPlayerStorage.storeKey) - - if (data) { - return data[key] - } - - return null - } - - static set(key, value) { - const data = store.get(AudioPlayerStorage.storeKey) ?? {} - - data[key] = value - - store.set(AudioPlayerStorage.storeKey, data) - - return data - } -} \ No newline at end of file diff --git a/packages/app/src/cores/playerv2/processors/compressorNode/index.js b/packages/app/src/cores/playerv2/processors/compressorNode/index.js deleted file mode 100644 index 14aa0083..00000000 --- a/packages/app/src/cores/playerv2/processors/compressorNode/index.js +++ /dev/null @@ -1,55 +0,0 @@ -import AudioPlayerStorage from "../../player.storage" -import ProcessorNode from "../node" - -export default class CompressorProcessorNode extends ProcessorNode { - static refName = "compressor" - static dependsOnSettings = ["player.compressor"] - static defaultCompressorValues = { - threshold: -50, - knee: 40, - ratio: 12, - attack: 0.003, - release: 0.25, - } - - state = { - compressorValues: AudioPlayerStorage.get("compressor") ?? CompressorProcessorNode.defaultCompressorValues, - } - - exposeToPublic = { - modifyValues: function (values) { - this.state.compressorValues = { - ...this.state.compressorValues, - ...values, - } - - AudioPlayerStorage.set("compressor", this.state.compressorValues) - - this.applyValues() - }.bind(this), - resetDefaultValues: function () { - this.exposeToPublic.modifyValues(CompressorProcessorNode.defaultCompressorValues) - - return this.state.compressorValues - }.bind(this), - detach: this._detach.bind(this), - attach: this._attach.bind(this), - values: this.state.compressorValues, - } - - async init(AudioContext) { - if (!AudioContext) { - throw new Error("AudioContext is required") - } - - this.processor = AudioContext.createDynamicsCompressor() - - this.applyValues() - } - - applyValues() { - Object.keys(this.state.compressorValues).forEach((key) => { - this.processor[key].value = this.state.compressorValues[key] - }) - } -} \ No newline at end of file diff --git a/packages/app/src/cores/playerv2/processors/eqNode/index.js b/packages/app/src/cores/playerv2/processors/eqNode/index.js deleted file mode 100644 index 3b27f230..00000000 --- a/packages/app/src/cores/playerv2/processors/eqNode/index.js +++ /dev/null @@ -1,131 +0,0 @@ -import ProcessorNode from "../node" -import AudioPlayerStorage from "../../player.storage" - -export default class EqProcessorNode extends ProcessorNode { - static refName = "eq" - static lock = true - - static defaultEqValue = { - 32: { - gain: 0, - }, - 64: { - gain: 0, - }, - 125: { - gain: 0, - }, - 250: { - gain: 0, - }, - 500: { - gain: 0, - }, - 1000: { - gain: 0, - }, - 2000: { - gain: 0, - }, - 4000: { - gain: 0, - }, - 8000: { - gain: 0, - }, - 16000: { - gain: 0, - } - } - - state = { - eqValues: AudioPlayerStorage.get("eq_values") ?? EqProcessorNode.defaultEqValue, - } - - exposeToPublic = { - modifyValues: function (values) { - Object.keys(values).forEach((key) => { - if (isNaN(key)) { - delete values[key] - } - }) - - this.state.eqValues = { - ...this.state.eqValues, - ...values, - } - - AudioPlayerStorage.set("eq_values", this.state.eqValues) - - this.applyValues() - }.bind(this), - resetDefaultValues: function () { - this.exposeToPublic.modifyValues(EqProcessorNode.defaultEqValue) - - return this.state - }.bind(this), - values: () => this.state, - } - - async init() { - if (!this.audioContext) { - throw new Error("audioContext is required") - } - - this.processor = this.audioContext.createGain() - - this.processor.gain.value = 1 - - this.processor.eqNodes = [] - - const values = Object.entries(this.state.eqValues).map((entry) => { - return { - freq: parseFloat(entry[0]), - gain: parseFloat(entry[1].gain), - } - }) - - values.forEach((eqValue, index) => { - // chekc if freq and gain is valid - if (isNaN(eqValue.freq)) { - eqValue.freq = 0 - } - if (isNaN(eqValue.gain)) { - eqValue.gain = 0 - } - - this.processor.eqNodes[index] = this.audioContext.createBiquadFilter() - this.processor.eqNodes[index].type = "peaking" - this.processor.eqNodes[index].frequency.value = eqValue.freq - this.processor.eqNodes[index].gain.value = eqValue.gain - }) - - // connect nodes - for await (let [index, eqNode] of this.processor.eqNodes.entries()) { - const nextNode = this.processor.eqNodes[index + 1] - - if (index === 0) { - this.processor.connect(eqNode) - } - - if (nextNode) { - eqNode.connect(nextNode) - } - } - - // set last processor for processor node can properly connect to the next node - this.processor._last = this.processor.eqNodes.at(-1) - } - - applyValues() { - // apply to current instance - this.processor.eqNodes.forEach((processor) => { - const gainValue = this.state.eqValues[processor.frequency.value].gain - - if (processor.gain.value !== gainValue) { - this.console.debug(`[EQ] Applying values to ${processor.frequency.value} Hz frequency with gain ${gainValue}`) - processor.gain.value = this.state.eqValues[processor.frequency.value].gain - } - }) - } -} \ No newline at end of file diff --git a/packages/app/src/cores/playerv2/processors/gainNode/index.js b/packages/app/src/cores/playerv2/processors/gainNode/index.js deleted file mode 100644 index 1098d753..00000000 --- a/packages/app/src/cores/playerv2/processors/gainNode/index.js +++ /dev/null @@ -1,60 +0,0 @@ -import AudioPlayerStorage from "../../player.storage" -import ProcessorNode from "../node" - -export default class GainProcessorNode extends ProcessorNode { - static refName = "gain" - - static lock = true - - static defaultValues = { - gain: 1, - } - - state = { - gain: AudioPlayerStorage.get("gain") ?? GainProcessorNode.defaultValues.gain, - } - - exposeToPublic = { - modifyValues: function (values) { - this.state = { - ...this.state, - ...values, - } - - AudioPlayerStorage.set("gain", this.state.gain) - - this.applyValues() - }.bind(this), - resetDefaultValues: function () { - this.exposeToPublic.modifyValues(GainProcessorNode.defaultValues) - - return this.state - }.bind(this), - values: () => this.state, - } - - applyValues() { - // apply to current instance - this.processor.gain.value = app.cores.player.volume() * this.state.gain - } - - async init() { - if (!this.audioContext) { - throw new Error("audioContext is required") - } - - this.processor = this.audioContext.createGain() - - this.applyValues() - } - - mutateInstance(instance) { - if (!instance) { - throw new Error("instance is required") - } - - instance.gainNode = this.processor - - return instance - } -} \ No newline at end of file diff --git a/packages/app/src/cores/playerv2/servicesToManifestResolver.js b/packages/app/src/cores/playerv2/servicesToManifestResolver.js deleted file mode 100644 index ba68cd39..00000000 --- a/packages/app/src/cores/playerv2/servicesToManifestResolver.js +++ /dev/null @@ -1,23 +0,0 @@ -import SyncModel from "comty.js/models/sync" - -export default { - "tidal": async (manifest) => { - const resolvedManifest = await SyncModel.tidalCore.getTrackManifest(manifest.id) - - manifest.source = resolvedManifest.playback.url - - if (!manifest.metadata) { - manifest.metadata = {} - } - - manifest.metadata.title = resolvedManifest.metadata.title - manifest.metadata.artist = resolvedManifest.metadata.artists.map(artist => artist.name).join(", ") - manifest.metadata.album = resolvedManifest.metadata.album.title - - const coverUID = resolvedManifest.metadata.album.cover.replace(/-/g, "/") - - manifest.metadata.cover = `https://resources.tidal.com/images/${coverUID}/1280x1280.jpg` - - return manifest - } -} \ No newline at end of file