From b4f1282ba50d363c0828cf3bf7bd9f7b507bf999 Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Wed, 5 Feb 2025 02:34:55 +0000 Subject: [PATCH] move track manifest & track instance classes to player core --- .../app/src/classes/TrackManifest/index.js | 133 ----------- .../player/classes/TrackInstance.js} | 62 +++-- .../src/cores/player/classes/TrackManifest.js | 214 ++++++++++++++++++ 3 files changed, 263 insertions(+), 146 deletions(-) delete mode 100644 packages/app/src/classes/TrackManifest/index.js rename packages/app/src/{classes/TrackInstance/index.js => cores/player/classes/TrackInstance.js} (66%) create mode 100644 packages/app/src/cores/player/classes/TrackManifest.js diff --git a/packages/app/src/classes/TrackManifest/index.js b/packages/app/src/classes/TrackManifest/index.js deleted file mode 100644 index 70e25a32..00000000 --- a/packages/app/src/classes/TrackManifest/index.js +++ /dev/null @@ -1,133 +0,0 @@ -import jsmediatags from "jsmediatags/dist/jsmediatags.min.js" -import { FastAverageColor } from "fast-average-color" - -import MusicService from "@models/music" - -export default class TrackManifest { - constructor(params) { - this.params = params - - this.uid = params.uid ?? params._id - this._id = params._id - - if (typeof params.cover !== "undefined") { - this.cover = params.cover - } - - if (typeof params.title !== "undefined") { - this.title = params.title - } - - if (typeof params.album !== "undefined") { - this.album = params.album - } - - if (typeof params.artist !== "undefined") { - this.artist = params.artist - } - - if (typeof params.artists !== "undefined" || Array.isArray(params.artists)) { - this.artistStr = params.artists.join(", ") - } - - if (typeof params.source !== "undefined") { - this.source = params.source - } - - if (typeof params.metadata !== "undefined") { - this.metadata = params.metadata - } - - if (typeof params.lyrics_enabled !== "undefined") { - this.lyrics_enabled = params.lyrics_enabled - } - - return this - } - - _id = null // used for api requests - uid = null // used for internal - - cover = "https://storage.ragestudio.net/comty-static-assets/default_song.png" - title = "Untitled" - album = "Unknown" - artist = "Unknown" - source = null - metadata = null - - // Extended from db - lyrics_enabled = false - liked = null - - async initialize() { - if (this.params.file) { - this.metadata = await this.analyzeMetadata(this.params.file.originFileObj) - - if (this.metadata.tags) { - if (this.metadata.tags.title) { - this.title = this.metadata.tags.title - } - - if (this.metadata.tags.artist) { - this.artist = this.metadata.tags.artist - } - - if (this.metadata.tags.album) { - this.album = this.metadata.tags.album - } - - if (this.metadata.tags.picture) { - this.cover = app.cores.remoteStorage.binaryArrayToFile(this.metadata.tags.picture, "cover") - - const coverUpload = await app.cores.remoteStorage.uploadFile(this.cover) - - this.cover = coverUpload.url - } - - this.handleChanges({ - cover: this.cover, - title: this.title, - artist: this.artist, - album: this.album, - }) - } - } - - return this - } - - handleChanges = (changes) => { - if (typeof this.params.onChange === "function") { - this.params.onChange(this.uid, changes) - } - } - - analyzeMetadata = async (file) => { - return new Promise((resolve, reject) => { - jsmediatags.read(file, { - onSuccess: (data) => { - return resolve(data) - }, - onError: (error) => { - return reject(error) - } - }) - }) - } - - analyzeCoverColor = async () => { - const fac = new FastAverageColor() - - this.cover_analysis = await fac.getColorAsync(this.cover) - - return this - } - - fetchLikeStatus = async () => { - if (!this._id) { - return null - } - - return await MusicService.isItemFavourited("track", this._id) - } -} \ No newline at end of file diff --git a/packages/app/src/classes/TrackInstance/index.js b/packages/app/src/cores/player/classes/TrackInstance.js similarity index 66% rename from packages/app/src/classes/TrackInstance/index.js rename to packages/app/src/cores/player/classes/TrackInstance.js index eb9fa8f8..842b09fa 100644 --- a/packages/app/src/classes/TrackInstance/index.js +++ b/packages/app/src/cores/player/classes/TrackInstance.js @@ -1,4 +1,5 @@ -import TrackManifest from "../TrackManifest" +import TrackManifest from "./TrackManifest" +import { MediaPlayer } from "dashjs" export default class TrackInstance { constructor(player, manifest) { @@ -61,9 +62,9 @@ export default class TrackInstance { "pause": () => { this.player.state.playback_status = "paused" }, - // "durationchange": (duration) => { - - // }, + "durationchange": () => { + this.player.eventBus.emit(`player.durationchange`, this.audio.duration) + }, "waiting": () => { if (this.waitUpdateTimeout) { clearTimeout(this.waitUpdateTimeout) @@ -83,12 +84,22 @@ export default class TrackInstance { initialize = async () => { this.manifest = await this.resolveManifest() - this.audio = new Audio(this.manifest.source) + this.audio = new Audio() this.audio.signal = this.abortController.signal this.audio.crossOrigin = "anonymous" this.audio.preload = "metadata" + // support for dash audio streaming + if (this.manifest.source.endsWith(".mpd")) { + this.muxerPlayer = MediaPlayer().create() + this.muxerPlayer.initialize(this.audio, null, false) + + this.muxerPlayer.attachSource(this.manifest.source) + } else { + this.audio.src = this.manifest.source + } + for (const [key, value] of Object.entries(this.mediaEvents)) { this.audio.addEventListener(key, value) } @@ -101,7 +112,13 @@ export default class TrackInstance { } stop = () => { - this.audio.pause() + if (this.audio) { + this.audio.pause() + } + + if (this.muxerPlayer) { + this.muxerPlayer.destroy() + } const lastProcessor = this.attachedProcessors[this.attachedProcessors.length - 1] @@ -119,18 +136,22 @@ export default class TrackInstance { } } - this.manifest = new TrackManifest(this.manifest) - - this.manifest = await this.manifest.analyzeCoverColor() + this.manifest = new TrackManifest(this.manifest, { + serviceProviders: this.player.serviceProviders, + }) if (this.manifest.service) { - if (!this.player.service_providers.has(manifest.service)) { - throw new Error(`Service ${manifest.service} is not supported`) + if (!this.player.serviceProviders.has(this.manifest.service)) { + throw new Error(`Service ${this.manifest.service} is not supported`) } // try to resolve source file - if (this.manifest.service !== "inherit" && !this.manifest.source) { - this.manifest = await this.player.service_providers.resolve(this.manifest.service, this.manifest) + if (!this.manifest.source) { + console.log("Resolving manifest cause no source defined") + + this.manifest = await this.player.serviceProviders.resolve(this.manifest.service, this.manifest) + + console.log("Manifest resolved", this.manifest) } } @@ -148,6 +169,21 @@ export default class TrackInstance { this.manifest.metadata.title = this.manifest.source.split("/").pop() } + // check if has overrides + const override = await this.manifest.serviceOperations.fetchOverride() + + if (override) { + console.log(`Override found for track ${this.manifest._id}`, override) + this.manifest.overrides = override + } + + // FIXME: idk why this is here, move somewhere else + // try { + // this.manifest = await this.manifest.analyzeCoverColor() + // } catch (error) { + // //x + // } + return this.manifest } } \ No newline at end of file diff --git a/packages/app/src/cores/player/classes/TrackManifest.js b/packages/app/src/cores/player/classes/TrackManifest.js new file mode 100644 index 00000000..05051d83 --- /dev/null +++ b/packages/app/src/cores/player/classes/TrackManifest.js @@ -0,0 +1,214 @@ +import jsmediatags from "jsmediatags/dist/jsmediatags.min.js" +import { FastAverageColor } from "fast-average-color" + +export default class TrackManifest { + constructor(params, ctx) { + this.params = params + this.ctx = ctx + + this.uid = params.uid ?? params._id + this._id = params._id + + if (typeof params.service !== "undefined") { + this.service = params.service + } + + if (typeof params.overrides !== "undefined") { + this.overrides = params.overrides + } + + if (typeof params.cover !== "undefined") { + this.cover = params.cover + } + + if (typeof params.title !== "undefined") { + this.title = params.title + } + + if (typeof params.album !== "undefined") { + this.album = params.album + } + + if (typeof params.artist !== "undefined") { + this.artist = params.artist + } + + if ( + typeof params.artists !== "undefined" || + Array.isArray(params.artists) + ) { + this.artistStr = params.artists.join(", ") + } + + if (typeof params.source !== "undefined") { + this.source = params.source + } + + if (typeof params.metadata !== "undefined") { + this.metadata = params.metadata + } + + if (typeof params.lyrics_enabled !== "undefined") { + this.lyrics_enabled = params.lyrics_enabled + } + + return this + } + + _id = null // used for api requests + uid = null // used for internal + + cover = + "https://storage.ragestudio.net/comty-static-assets/default_song.png" + title = "Untitled" + album = "Unknown" + artist = "Unknown" + source = null + metadata = null + + // set default service to default + service = "default" + + // Extended from db + lyrics_enabled = false + liked = null + + // TODO: implement this server feature to fetch some data from the server, + // used for example to fix a incorrect lyrics time + overrides = null + + async initialize() { + if (this.params.file) { + this.metadata = await this.analyzeMetadata( + this.params.file.originFileObj, + ) + + this.metadata.format = this.metadata.type.toUpperCase() + + if (this.metadata.tags) { + if (this.metadata.tags.title) { + this.title = this.metadata.tags.title + } + + if (this.metadata.tags.artist) { + this.artist = this.metadata.tags.artist + } + + if (this.metadata.tags.album) { + this.album = this.metadata.tags.album + } + + if (this.metadata.tags.picture) { + this.cover = app.cores.remoteStorage.binaryArrayToFile( + this.metadata.tags.picture, + "cover", + ) + + const coverUpload = + await app.cores.remoteStorage.uploadFile(this.cover) + + this.cover = coverUpload.url + + delete this.metadata.tags.picture + } + + this.handleChanges({ + cover: this.cover, + title: this.title, + artist: this.artist, + album: this.album, + }) + } + } + + return this + } + + handleChanges = (changes) => { + if (typeof this.params.onChange === "function") { + this.params.onChange(this.uid, changes) + } + } + + analyzeMetadata = async (file) => { + return new Promise((resolve, reject) => { + jsmediatags.read(file, { + onSuccess: (data) => { + return resolve(data) + }, + onError: (error) => { + return reject(error) + }, + }) + }) + } + + analyzeCoverColor = async () => { + const fac = new FastAverageColor() + + return await fac.getColorAsync(this.cover) + } + + serviceOperations = { + fetchLikeStatus: async () => { + if (!this._id) { + return null + } + + return await this.ctx.serviceProviders.operation( + "isItemFavourited", + this.service, + this, + "track", + ) + }, + fetchLyrics: async () => { + if (!this._id) { + return null + } + + return await this.ctx.serviceProviders.operation( + "resolveLyrics", + this.service, + this, + ) + }, + fetchOverride: async () => { + if (!this._id) { + return null + } + + return await this.ctx.serviceProviders.operation( + "resolveOverride", + this.service, + this, + ) + }, + toggleItemFavourite: async (to) => { + if (!this._id) { + return null + } + + return await this.ctx.serviceProviders.operation( + "toggleItemFavourite", + this.service, + this, + "track", + to, + ) + }, + } + + toSeriableObject = () => { + return { + _id: this._id, + uid: this.uid, + title: this.title, + album: this.album, + artist: this.artist, + source: this.source, + metadata: this.metadata, + liked: this.liked, + } + } +}