From a478432d61cdf1bd9f487508607fc0a215dfe58e Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Wed, 21 May 2025 19:04:59 +0000 Subject: [PATCH] Implement music sync room and refine related features - Add WebSocket-based sync room for real-time music playback sync. - Expand music exploration search to include albums and artists. - Adjust track and release data fetching and deletion on server. - Enhance DASH segmentation job with codec overrides and MPD updates. - Update music service configuration for websockets and middlewares. - Make minor UI adjustments to the search component. --- .../app/src/components/Searcher/index.jsx | 4 +- .../app/src/components/Searcher/index.less | 6 +- .../app/src/pages/music/list/[id]/index.jsx | 14 ++- .../tabs/explore/components/Navbar/index.jsx | 6 +- .../components/SearchResults/index.jsx | 13 ++- .../classes/SegmentedAudioMPDJob/index.js | 97 ++++++++++++++--- .../server/classes/SyncRoomManager/index.js | 35 ++++++ .../classes/Transformation/handlers/a-dash.js | 4 +- .../services/music/classes/release/index.js | 2 +- .../music/classes/track/methods/get.js | 28 ++--- .../server/services/music/music.service.js | 35 +++++- packages/server/services/music/package.json | 5 +- .../routes/music/my/library/favorite/get.js | 2 +- .../routes/music/my/library/favorite/put.js | 2 +- .../music/routes/music/my/library/get.js | 2 +- .../music/routes/music/my/releases/get.js | 2 +- .../music/routes/music/recently/get.js | 2 +- .../music/releases/[release_id]/data/get.js | 2 +- .../music/releases/[release_id]/delete.js | 2 +- .../music/routes/music/releases/put.js | 30 +++--- .../music/tracks/[track_id]/data/get.js | 20 ++-- .../routes/music/tracks/[track_id]/delete.js | 28 ++--- .../music/tracks/[track_id]/lyrics/put.js | 102 +++++++++--------- .../music/tracks/[track_id]/override/put.js | 58 +++++----- .../services/music/routes/music/tracks/get.js | 1 + .../services/music/routes/music/tracks/put.js | 2 +- .../music/ws_routes/sync_room/join.js | 13 +++ .../music/ws_routes/sync_room/leave.js | 8 ++ .../music/ws_routes/sync_room/push.js | 7 ++ .../music/ws_routes/sync_room/push_lyrics.js | 14 +++ .../ws_routes/sync_room/request_lyrics.js | 5 + 31 files changed, 375 insertions(+), 176 deletions(-) create mode 100644 packages/server/classes/SyncRoomManager/index.js create mode 100644 packages/server/services/music/ws_routes/sync_room/join.js create mode 100644 packages/server/services/music/ws_routes/sync_room/leave.js create mode 100644 packages/server/services/music/ws_routes/sync_room/push.js create mode 100644 packages/server/services/music/ws_routes/sync_room/push_lyrics.js create mode 100644 packages/server/services/music/ws_routes/sync_room/request_lyrics.js diff --git a/packages/app/src/components/Searcher/index.jsx b/packages/app/src/components/Searcher/index.jsx index 9ddbe3c0..bcfeae6d 100755 --- a/packages/app/src/components/Searcher/index.jsx +++ b/packages/app/src/components/Searcher/index.jsx @@ -184,12 +184,12 @@ const Searcher = (props) => { if (typeof props.model === "function") { result = await props.model(value, { ...props.modelParams, - limit_per_section: app.isMobile ? 3 : 5, + limit: app.isMobile ? 3 : 5, }) } else { result = await SearchModel.search(value, { ...props.modelParams, - limit_per_section: app.isMobile ? 3 : 5, + limit: app.isMobile ? 3 : 5, }) } diff --git a/packages/app/src/components/Searcher/index.less b/packages/app/src/components/Searcher/index.less index d0acd1bc..4df6276f 100755 --- a/packages/app/src/components/Searcher/index.less +++ b/packages/app/src/components/Searcher/index.less @@ -90,7 +90,6 @@ html { align-items: center; - border: 0; padding: 0; margin: 0; @@ -101,6 +100,7 @@ html { padding: 0 10px; background-color: var(--background-color-primary); + border: 3px solid var(--border-color) !important; .ant-input-prefix { font-size: 2rem; @@ -127,6 +127,9 @@ html { flex-wrap: wrap; width: 100%; + height: fit-content; + max-height: 70vh; + gap: 10px; padding: 20px; @@ -137,6 +140,7 @@ html { color: var(--text-color); background-color: var(--background-color-primary); + border: 3px solid var(--border-color); .ant-result, .ant-result-title, diff --git a/packages/app/src/pages/music/list/[id]/index.jsx b/packages/app/src/pages/music/list/[id]/index.jsx index 88a56eb3..19e4d5b5 100644 --- a/packages/app/src/pages/music/list/[id]/index.jsx +++ b/packages/app/src/pages/music/list/[id]/index.jsx @@ -8,11 +8,19 @@ import MusicService from "@models/music" import "./index.less" const ListView = (props) => { - const { type, id } = props.params + const { id } = props.params - const [loading, result, error, makeRequest] = app.cores.api.useRequest( + const query = new URLSearchParams(window.location.search) + const type = query.get("type") + const service = query.get("service") + + const [loading, result, error] = app.cores.api.useRequest( MusicService.getReleaseData, id, + { + type: type, + service: service, + }, ) if (error) { @@ -29,6 +37,8 @@ const ListView = (props) => { return } + console.log(result) + return ( { useUrlQuery renderResults={false} model={async (keywords, params) => - SearchModel.search(keywords, params, ["tracks"]) + SearchModel.search(keywords, params, [ + "tracks", + "albums", + "artists", + ]) } onSearchResult={props.setSearchResults} onEmpty={() => props.setSearchResults(false)} diff --git a/packages/app/src/pages/music/tabs/explore/components/SearchResults/index.jsx b/packages/app/src/pages/music/tabs/explore/components/SearchResults/index.jsx index 5abf8880..d97d81b7 100644 --- a/packages/app/src/pages/music/tabs/explore/components/SearchResults/index.jsx +++ b/packages/app/src/pages/music/tabs/explore/components/SearchResults/index.jsx @@ -8,11 +8,11 @@ import MusicTrack from "@components/Music/Track" import Playlist from "@components/Music/Playlist" const ResultGroupsDecorators = { - playlists: { - icon: "MdPlaylistPlay", - label: "Playlists", + albums: { + icon: "MdAlbum", + label: "Albums", renderItem: (props) => { - return + return }, }, tracks: { @@ -23,7 +23,6 @@ const ResultGroupsDecorators = { app.cores.player.start(props.item)} onClick={() => app.location.push(`/play/${props.item._id}`)} /> ) @@ -40,6 +39,10 @@ const SearchResults = ({ data }) => { // filter out groups with no items array property groupsKeys = groupsKeys.filter((key) => { + if (!data[key]) { + return false + } + if (!Array.isArray(data[key].items)) { return false } diff --git a/packages/server/classes/SegmentedAudioMPDJob/index.js b/packages/server/classes/SegmentedAudioMPDJob/index.js index d6e75bc3..bd295747 100644 --- a/packages/server/classes/SegmentedAudioMPDJob/index.js +++ b/packages/server/classes/SegmentedAudioMPDJob/index.js @@ -3,6 +3,10 @@ import path from "node:path" import { FFMPEGLib, Utils } from "../FFMPEGLib" +const codecOverrides = { + wav: "flac", +} + export default class SegmentedAudioMPDJob extends FFMPEGLib { constructor(params = {}) { super() @@ -26,11 +30,11 @@ export default class SegmentedAudioMPDJob extends FFMPEGLib { `-c:a ${this.params.audioCodec}`, `-map 0:a`, `-f dash`, - `-dash_segment_type mp4`, `-segment_time ${this.params.segmentTime}`, `-use_template 1`, `-use_timeline 1`, - `-init_seg_name "init.m4s"`, + //`-dash_segment_type mp4`, + //`-init_seg_name "init.m4s"`, ] if (this.params.includeMetadata === false) { @@ -89,25 +93,69 @@ export default class SegmentedAudioMPDJob extends FFMPEGLib { } } + _updateMpdBandwidthAndSamplingRate = async ({ + mpdPath, + bandwidth, + samplingRate, + } = {}) => { + try { + let mpdContent = await fs.promises.readFile(mpdPath, "utf-8") + + // Regex to find all tags + const representationRegex = /(]*)(>)/g + + mpdContent = mpdContent.replace( + representationRegex, + (match, startTag, endTag) => { + // Remove existing bandwidth and audioSamplingRate attributes if present + let newTag = startTag + .replace(/\sbandwidth="[^"]*"/, "") + .replace(/\saudioSamplingRate="[^"]*"/, "") + + // Add new attributes + newTag += ` bandwidth="${bandwidth}" audioSamplingRate="${samplingRate}"` + + return newTag + endTag + }, + ) + + await fs.promises.writeFile(mpdPath, mpdContent, "utf-8") + } catch (error) { + console.error( + `[SegmentedAudioMPDJob] Error updating MPD bandwidth/audioSamplingRate for ${mpdPath}:`, + error, + ) + } + } + run = async () => { - const segmentationCmd = this.buildSegmentationArgs() const outputPath = this.params.outputDir ?? `${path.dirname(this.params.input)}/dash` const outputFile = path.join(outputPath, this.params.outputMasterName) - this.emit("start", { - input: this.params.input, - output: outputPath, - params: this.params, - }) - - if (!fs.existsSync(outputPath)) { - fs.mkdirSync(outputPath, { recursive: true }) - } - try { + this.emit("start", { + input: this.params.input, + output: outputPath, + params: this.params, + }) + const inputProbe = await Utils.probe(this.params.input) + if ( + this.params.audioCodec === "copy" && + codecOverrides[inputProbe.format.format_name] + ) { + this.params.audioCodec = + codecOverrides[inputProbe.format.format_name] + } + + const segmentationCmd = this.buildSegmentationArgs() + + if (!fs.existsSync(outputPath)) { + fs.mkdirSync(outputPath, { recursive: true }) + } + const ffmpegResult = await this.ffmpeg({ args: segmentationCmd, onProcess: (process) => { @@ -135,6 +183,29 @@ export default class SegmentedAudioMPDJob extends FFMPEGLib { let outputProbe = await Utils.probe(outputFile) + let bandwidth = null + let samplingRate = null + + if ( + outputProbe && + outputProbe.streams && + outputProbe.streams.length > 0 + ) { + bandwidth = + outputProbe.format.bit_rate ?? + outputProbe.streams[0].bit_rate + + samplingRate = outputProbe.streams[0].sample_rate + } + + if (bandwidth && samplingRate) { + await this._updateMpdBandwidthAndSamplingRate({ + mpdPath: outputFile, + bandwidth: bandwidth, + samplingRate: samplingRate, + }) + } + this.emit("end", { probe: { input: inputProbe, diff --git a/packages/server/classes/SyncRoomManager/index.js b/packages/server/classes/SyncRoomManager/index.js new file mode 100644 index 00000000..0e5419d5 --- /dev/null +++ b/packages/server/classes/SyncRoomManager/index.js @@ -0,0 +1,35 @@ +export class SyncRoom { + constructor(ownerSocket) { + this.ownerSocket = ownerSocket + } + + id = global.nanoid() + + buffer = new Set() + members = new Set() + + push = async (data) => { + if (this.buffer.size > 5) { + this.buffer.delete(this.buffer.keys().next().value) + } + + this.buffer.add(data) + + for (const socket of this.members) { + socket.emit(`syncroom:push`, data) + } + } + + join = (socket) => { + this.members.add(socket) + + // send the latest buffer + socket.emit("syncroom.buffer", this.buffer[0]) + } + + leave = (socket) => { + this.members.delete(socket) + } +} + +export default class SyncRoomManager {} diff --git a/packages/server/classes/Transformation/handlers/a-dash.js b/packages/server/classes/Transformation/handlers/a-dash.js index b286bc45..bd5074d0 100644 --- a/packages/server/classes/Transformation/handlers/a-dash.js +++ b/packages/server/classes/Transformation/handlers/a-dash.js @@ -2,7 +2,7 @@ import path from "node:path" import SegmentedAudioMPDJob from "@shared-classes/SegmentedAudioMPDJob" export default async ({ filePath, workPath, onProgress }) => { - return new Promise(async (resolve, reject) => { + return new Promise((resolve) => { const outputDir = path.resolve(workPath, "a-dash") const job = new SegmentedAudioMPDJob({ @@ -10,7 +10,7 @@ export default async ({ filePath, workPath, onProgress }) => { outputDir: outputDir, // set to default as raw flac - audioCodec: "flac", + audioCodec: "copy", audioBitrate: "default", audioSampleRate: "default", }) diff --git a/packages/server/services/music/classes/release/index.js b/packages/server/services/music/classes/release/index.js index 32d1f9e3..027337a9 100644 --- a/packages/server/services/music/classes/release/index.js +++ b/packages/server/services/music/classes/release/index.js @@ -130,7 +130,7 @@ export default class Release { const items = release.items ?? release.list - const items_ids = items.map((item) => item._id.toString()) + const items_ids = items.map((item) => item._id ?? item) // delete all releated tracks await Track.deleteMany({ diff --git a/packages/server/services/music/classes/track/methods/get.js b/packages/server/services/music/classes/track/methods/get.js index e3f327ec..26111c50 100644 --- a/packages/server/services/music/classes/track/methods/get.js +++ b/packages/server/services/music/classes/track/methods/get.js @@ -6,12 +6,12 @@ async function fullfillData(list, { user_id = null }) { list = [list] } - const trackIds = list.map((track) => { - return track._id - }) - // if user_id is provided, fetch likes if (user_id) { + const trackIds = list.map((track) => { + return track._id + }) + const tracksLikes = await Library.isFavorite( user_id, trackIds, @@ -32,21 +32,15 @@ async function fullfillData(list, { user_id = null }) { }) list = await Promise.all(list) + } else { + list = list.map((track) => { + delete track.source + delete track.publisher + + return track + }) } - // process some metadata - list = list.map(async (track) => { - if (track.metadata) { - if (track.metadata.bitrate && track.metadata.bitrate > 9000) { - track.metadata.lossless = true - } - } - - return track - }) - - list = await Promise.all(list) - return list } diff --git a/packages/server/services/music/music.service.js b/packages/server/services/music/music.service.js index 15f1fec2..e2df3f83 100755 --- a/packages/server/services/music/music.service.js +++ b/packages/server/services/music/music.service.js @@ -7,12 +7,20 @@ import RedisClient from "@shared-classes/RedisClient" import SharedMiddlewares from "@shared-middlewares" import LimitsClass from "@shared-classes/Limits" +import InjectedAuth from "@shared-lib/injectedAuth" + export default class API extends Server { static refName = "music" - static useEngine = "hyper-express-ng" - static enableWebsockets = true - static routesPath = `${__dirname}/routes` - static listen_port = process.env.HTTP_LISTEN_PORT ?? 3003 + static listenPort = process.env.HTTP_LISTEN_PORT ?? 3003 + + static websockets = { + enabled: true, + path: "/music", + } + + static bypassCors = true + + static useMiddlewares = ["logs"] middlewares = { ...SharedMiddlewares, @@ -24,9 +32,28 @@ export default class API extends Server { redis: RedisClient(), } + handleWsUpgrade = async (context, token, res) => { + if (!token) { + return res.upgrade(context) + } + + context = await InjectedAuth(context, token, res).catch(() => { + res.close(401, "Failed to verify auth token") + return false + }) + + if (!context || !context.user) { + res.close(401, "Unauthorized or missing auth token") + return false + } + + return res.upgrade(context) + } + async onInitialize() { global.sse = this.contexts.SSEManager global.redis = this.contexts.redis.client + global.syncRoomLyrics = new Map() await this.contexts.db.initialize() await this.contexts.redis.initialize() diff --git a/packages/server/services/music/package.json b/packages/server/services/music/package.json index 07e7f330..0bdd36bb 100755 --- a/packages/server/services/music/package.json +++ b/packages/server/services/music/package.json @@ -1,3 +1,6 @@ { - "name": "music" + "name": "music", + "dependencies": { + "linebridge": "^1.0.0-alpha.4" + } } diff --git a/packages/server/services/music/routes/music/my/library/favorite/get.js b/packages/server/services/music/routes/music/my/library/favorite/get.js index d7037ef8..7cbdfe8b 100644 --- a/packages/server/services/music/routes/music/my/library/favorite/get.js +++ b/packages/server/services/music/routes/music/my/library/favorite/get.js @@ -1,7 +1,7 @@ import Library from "@classes/library" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const { kind, item_id } = req.query diff --git a/packages/server/services/music/routes/music/my/library/favorite/put.js b/packages/server/services/music/routes/music/my/library/favorite/put.js index 937d84c4..b1a59ac4 100644 --- a/packages/server/services/music/routes/music/my/library/favorite/put.js +++ b/packages/server/services/music/routes/music/my/library/favorite/put.js @@ -1,7 +1,7 @@ import Library from "@classes/library" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const { kind, item_id, to } = req.body diff --git a/packages/server/services/music/routes/music/my/library/get.js b/packages/server/services/music/routes/music/my/library/get.js index a073ce2f..7c3cd688 100644 --- a/packages/server/services/music/routes/music/my/library/get.js +++ b/packages/server/services/music/routes/music/my/library/get.js @@ -1,7 +1,7 @@ import Library from "@classes/library" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const userId = req.auth.session.user_id const { limit = 50, offset = 0, kind } = req.query diff --git a/packages/server/services/music/routes/music/my/releases/get.js b/packages/server/services/music/routes/music/my/releases/get.js index 4c696b69..505e09c2 100644 --- a/packages/server/services/music/routes/music/my/releases/get.js +++ b/packages/server/services/music/routes/music/my/releases/get.js @@ -1,7 +1,7 @@ import { MusicRelease, Track } from "@db_models" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const { keywords, limit = 10, offset = 0 } = req.query diff --git a/packages/server/services/music/routes/music/recently/get.js b/packages/server/services/music/routes/music/recently/get.js index eb7fc0d5..e57b2fe1 100644 --- a/packages/server/services/music/routes/music/recently/get.js +++ b/packages/server/services/music/routes/music/recently/get.js @@ -3,7 +3,7 @@ import { RecentActivity } from "@db_models" import TrackClass from "@classes/track" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req, res) => { const user_id = req.auth.session.user_id diff --git a/packages/server/services/music/routes/music/releases/[release_id]/data/get.js b/packages/server/services/music/routes/music/releases/[release_id]/data/get.js index 4e0caca5..b352dd18 100644 --- a/packages/server/services/music/routes/music/releases/[release_id]/data/get.js +++ b/packages/server/services/music/routes/music/releases/[release_id]/data/get.js @@ -1,7 +1,7 @@ import ReleaseClass from "@classes/release" export default { - middlewares: ["withOptionalAuthentication"], + useMiddlewares: ["withOptionalAuthentication"], fn: async (req) => { const { release_id } = req.params const { limit = 50, offset = 0 } = req.query diff --git a/packages/server/services/music/routes/music/releases/[release_id]/delete.js b/packages/server/services/music/routes/music/releases/[release_id]/delete.js index 30a40b86..37ad34ba 100644 --- a/packages/server/services/music/routes/music/releases/[release_id]/delete.js +++ b/packages/server/services/music/routes/music/releases/[release_id]/delete.js @@ -1,7 +1,7 @@ import ReleaseClass from "@classes/release" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { return await ReleaseClass.delete(req.params.release_id, { user_id: req.auth.session.user_id, diff --git a/packages/server/services/music/routes/music/releases/put.js b/packages/server/services/music/routes/music/releases/put.js index dcb91418..60103835 100644 --- a/packages/server/services/music/routes/music/releases/put.js +++ b/packages/server/services/music/routes/music/releases/put.js @@ -1,18 +1,18 @@ import ReleaseClass from "@classes/release" export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - if (req.body._id) { - return await ReleaseClass.update(req.body._id, { - ...req.body, - user_id: req.auth.session.user_id, - }) - } else { - return await ReleaseClass.create({ - ...req.body, - user_id: req.auth.session.user_id, - }) - } - } -} \ No newline at end of file + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + if (req.body._id) { + return await ReleaseClass.update(req.body._id, { + ...req.body, + user_id: req.auth.session.user_id, + }) + } else { + return await ReleaseClass.create({ + ...req.body, + user_id: req.auth.session.user_id, + }) + } + }, +} diff --git a/packages/server/services/music/routes/music/tracks/[track_id]/data/get.js b/packages/server/services/music/routes/music/tracks/[track_id]/data/get.js index 327a0516..b7b4f6c0 100644 --- a/packages/server/services/music/routes/music/tracks/[track_id]/data/get.js +++ b/packages/server/services/music/routes/music/tracks/[track_id]/data/get.js @@ -1,15 +1,15 @@ import TrackClass from "@classes/track" export default { - middlewares: ["withOptionalAuthentication"], - fn: async (req) => { - const { track_id } = req.params - const user_id = req.auth?.session?.user_id + useMiddlewares: ["withOptionalAuthentication"], + fn: async (req) => { + const { track_id } = req.params + const user_id = req.auth?.session?.user_id - const track = await TrackClass.get(track_id, { - user_id - }) + const track = await TrackClass.get(track_id, { + user_id, + }) - return track - } -} \ No newline at end of file + return track + }, +} diff --git a/packages/server/services/music/routes/music/tracks/[track_id]/delete.js b/packages/server/services/music/routes/music/tracks/[track_id]/delete.js index 5c14506f..9102e572 100644 --- a/packages/server/services/music/routes/music/tracks/[track_id]/delete.js +++ b/packages/server/services/music/routes/music/tracks/[track_id]/delete.js @@ -1,21 +1,21 @@ import TrackClass from "@classes/track" export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - const { track_id } = req.params + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + const { track_id } = req.params - const track = await TrackClass.get(track_id) + const track = await TrackClass.get(track_id) - if (track.publisher.user_id !== req.auth.session.user_id) { - throw new Error("Forbidden, you don't own this track") - } + if (track.publisher.user_id !== req.auth.session.user_id) { + throw new Error("Forbidden, you don't own this track") + } - await TrackClass.delete(track_id) + await TrackClass.delete(track_id) - return { - success: true, - track: track, - } - } -} \ No newline at end of file + return { + success: true, + track: track, + } + }, +} diff --git a/packages/server/services/music/routes/music/tracks/[track_id]/lyrics/put.js b/packages/server/services/music/routes/music/tracks/[track_id]/lyrics/put.js index 97288a21..2e917b84 100644 --- a/packages/server/services/music/routes/music/tracks/[track_id]/lyrics/put.js +++ b/packages/server/services/music/routes/music/tracks/[track_id]/lyrics/put.js @@ -1,66 +1,66 @@ import { TrackLyric, Track } from "@db_models" export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - const { track_id } = req.params - const { video_source, lrc, sync_audio_at } = req.body + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + const { track_id } = req.params + const { video_source, lrc, sync_audio_at } = req.body - // check if track exists - let track = await Track.findById(track_id).catch(() => null) + // check if track exists + let track = await Track.findById(track_id).catch(() => null) - if (!track) { - throw new OperationError(404, "Track not found") - } + if (!track) { + throw new OperationError(404, "Track not found") + } - if (track.publisher.user_id !== req.auth.session.user_id) { - throw new OperationError(403, "Unauthorized") - } + if (track.publisher.user_id !== req.auth.session.user_id) { + throw new OperationError(403, "Unauthorized") + } - console.log(`Setting lyrics for track ${track_id} >`, { - track_id: track_id, - video_source: video_source, - lrc: lrc, - }) + console.log(`Setting lyrics for track ${track_id} >`, { + track_id: track_id, + video_source: video_source, + lrc: lrc, + }) - // check if trackLyric exists - let trackLyric = await TrackLyric.findOne({ - track_id: track_id - }) + // check if trackLyric exists + let trackLyric = await TrackLyric.findOne({ + track_id: track_id, + }) - // if trackLyric exists, update it, else create it - if (!trackLyric) { - trackLyric = new TrackLyric({ - track_id: track_id, - video_source: video_source, - lrc: lrc, - sync_audio_at: sync_audio_at, - }) + // if trackLyric exists, update it, else create it + if (!trackLyric) { + trackLyric = new TrackLyric({ + track_id: track_id, + video_source: video_source, + lrc: lrc, + sync_audio_at: sync_audio_at, + }) - await trackLyric.save() - } else { - const update = Object() + await trackLyric.save() + } else { + const update = Object() - if (typeof video_source !== "undefined") { - update.video_source = video_source - } + if (typeof video_source !== "undefined") { + update.video_source = video_source + } - if (typeof lrc !== "undefined") { - update.lrc = lrc - } + if (typeof lrc !== "undefined") { + update.lrc = lrc + } - if (typeof sync_audio_at !== "undefined") { - update.sync_audio_at = sync_audio_at - } + if (typeof sync_audio_at !== "undefined") { + update.sync_audio_at = sync_audio_at + } - trackLyric = await TrackLyric.findOneAndUpdate( - { - track_id: track_id, - }, - update, - ) - } + trackLyric = await TrackLyric.findOneAndUpdate( + { + track_id: track_id, + }, + update, + ) + } - return trackLyric - } -} \ No newline at end of file + return trackLyric + }, +} diff --git a/packages/server/services/music/routes/music/tracks/[track_id]/override/put.js b/packages/server/services/music/routes/music/tracks/[track_id]/override/put.js index 7f22fe7b..c30e53e4 100644 --- a/packages/server/services/music/routes/music/tracks/[track_id]/override/put.js +++ b/packages/server/services/music/routes/music/tracks/[track_id]/override/put.js @@ -1,36 +1,36 @@ import { TrackOverride } from "@db_models" export default { - middlewares: ["withAuthentication", "onlyAdmin"], - fn: async (req) => { - const { track_id } = req.params - const { service, override } = req.body + useMiddlewares: ["withAuthentication", "onlyAdmin"], + fn: async (req) => { + const { track_id } = req.params + const { service, override } = req.body - let trackOverride = await TrackOverride.findOne({ - track_id: track_id, - service: service, - }).catch(() => null) + let trackOverride = await TrackOverride.findOne({ + track_id: track_id, + service: service, + }).catch(() => null) - if (!trackOverride) { - trackOverride = new TrackOverride({ - track_id: track_id, - service: service, - override: override, - }) + if (!trackOverride) { + trackOverride = new TrackOverride({ + track_id: track_id, + service: service, + override: override, + }) - await trackOverride.save() - } else { - trackOverride = await TrackOverride.findOneAndUpdate( - { - track_id: track_id, - service: service, - }, - { - override: override, - }, - ) - } + await trackOverride.save() + } else { + trackOverride = await TrackOverride.findOneAndUpdate( + { + track_id: track_id, + service: service, + }, + { + override: override, + }, + ) + } - return trackOverride.override - } -} \ No newline at end of file + return trackOverride.override + }, +} diff --git a/packages/server/services/music/routes/music/tracks/get.js b/packages/server/services/music/routes/music/tracks/get.js index 3dda149a..c51d2b82 100644 --- a/packages/server/services/music/routes/music/tracks/get.js +++ b/packages/server/services/music/routes/music/tracks/get.js @@ -19,6 +19,7 @@ export default async (req) => { const items = await Track.find(query) .limit(limit) + .select("-source -publisher -public") .skip(trim) .sort({ _id: -1 }) diff --git a/packages/server/services/music/routes/music/tracks/put.js b/packages/server/services/music/routes/music/tracks/put.js index 415bb16a..f35a5879 100644 --- a/packages/server/services/music/routes/music/tracks/put.js +++ b/packages/server/services/music/routes/music/tracks/put.js @@ -2,7 +2,7 @@ import requiredFields from "@shared-utils/requiredFields" import TrackClass from "@classes/track" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { if (Array.isArray(req.body.items)) { let results = [] diff --git a/packages/server/services/music/ws_routes/sync_room/join.js b/packages/server/services/music/ws_routes/sync_room/join.js new file mode 100644 index 00000000..a3a09f8b --- /dev/null +++ b/packages/server/services/music/ws_routes/sync_room/join.js @@ -0,0 +1,13 @@ +import leave from "./leave" + +export default async (client, user_id) => { + console.log(`[SYNC-ROOM] Join ${client.userId} -> ${user_id}`) + + if (client.syncroom) { + await leave(client, client.syncroom) + } + + // subscribe to stream topic + await client.subscribe(`syncroom/${user_id}`) + client.syncroom = user_id +} diff --git a/packages/server/services/music/ws_routes/sync_room/leave.js b/packages/server/services/music/ws_routes/sync_room/leave.js new file mode 100644 index 00000000..fa0dbfab --- /dev/null +++ b/packages/server/services/music/ws_routes/sync_room/leave.js @@ -0,0 +1,8 @@ +export default async (client, user_id) => { + console.log(`[SYNC-ROOM] Leave ${client.userId} -> ${user_id}`) + + // unsubscribe from sync topic + await client.unsubscribe(`syncroom/${user_id}`) + + client.syncroom = null +} diff --git a/packages/server/services/music/ws_routes/sync_room/push.js b/packages/server/services/music/ws_routes/sync_room/push.js new file mode 100644 index 00000000..e9f52f08 --- /dev/null +++ b/packages/server/services/music/ws_routes/sync_room/push.js @@ -0,0 +1,7 @@ +export default async (client, payload) => { + console.log(`[SYNC-ROOM] Pushing to sync ${client.userId}`, payload) + + const roomId = `syncroom/${client.userId}` + + global.websockets.senders.toTopic(roomId, "sync:receive", payload) +} diff --git a/packages/server/services/music/ws_routes/sync_room/push_lyrics.js b/packages/server/services/music/ws_routes/sync_room/push_lyrics.js new file mode 100644 index 00000000..47bdf09c --- /dev/null +++ b/packages/server/services/music/ws_routes/sync_room/push_lyrics.js @@ -0,0 +1,14 @@ +export default async (client, payload) => { + console.log(`[SYNC-ROOM] Pushing lyrics to sync ${client.userId}`) + + const roomId = `syncroom/${client.userId}` + + if (!payload) { + // delete lyrics + global.syncRoomLyrics.delete(client.userId) + } else { + global.syncRoomLyrics.set(client.userId, payload) + } + + global.websockets.senders.toTopic(roomId, "sync:lyrics:receive", payload) +} diff --git a/packages/server/services/music/ws_routes/sync_room/request_lyrics.js b/packages/server/services/music/ws_routes/sync_room/request_lyrics.js new file mode 100644 index 00000000..1b51b7de --- /dev/null +++ b/packages/server/services/music/ws_routes/sync_room/request_lyrics.js @@ -0,0 +1,5 @@ +export default async (client) => { + console.log(`[SYNC-ROOM] Requesting lyrics of room ${client.syncroom}`) + + return global.syncRoomLyrics.get(client.syncroom) +}