mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 02:24:16 +00:00
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.
This commit is contained in:
parent
0eaecf6fd3
commit
a478432d61
@ -184,12 +184,12 @@ const Searcher = (props) => {
|
|||||||
if (typeof props.model === "function") {
|
if (typeof props.model === "function") {
|
||||||
result = await props.model(value, {
|
result = await props.model(value, {
|
||||||
...props.modelParams,
|
...props.modelParams,
|
||||||
limit_per_section: app.isMobile ? 3 : 5,
|
limit: app.isMobile ? 3 : 5,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
result = await SearchModel.search(value, {
|
result = await SearchModel.search(value, {
|
||||||
...props.modelParams,
|
...props.modelParams,
|
||||||
limit_per_section: app.isMobile ? 3 : 5,
|
limit: app.isMobile ? 3 : 5,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +90,6 @@ html {
|
|||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
@ -101,6 +100,7 @@ html {
|
|||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
|
|
||||||
background-color: var(--background-color-primary);
|
background-color: var(--background-color-primary);
|
||||||
|
border: 3px solid var(--border-color) !important;
|
||||||
|
|
||||||
.ant-input-prefix {
|
.ant-input-prefix {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
@ -127,6 +127,9 @@ html {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: fit-content;
|
||||||
|
max-height: 70vh;
|
||||||
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
||||||
@ -137,6 +140,7 @@ html {
|
|||||||
|
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
background-color: var(--background-color-primary);
|
background-color: var(--background-color-primary);
|
||||||
|
border: 3px solid var(--border-color);
|
||||||
|
|
||||||
.ant-result,
|
.ant-result,
|
||||||
.ant-result-title,
|
.ant-result-title,
|
||||||
|
@ -8,11 +8,19 @@ import MusicService from "@models/music"
|
|||||||
import "./index.less"
|
import "./index.less"
|
||||||
|
|
||||||
const ListView = (props) => {
|
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,
|
MusicService.getReleaseData,
|
||||||
id,
|
id,
|
||||||
|
{
|
||||||
|
type: type,
|
||||||
|
service: service,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -29,6 +37,8 @@ const ListView = (props) => {
|
|||||||
return <antd.Skeleton active />
|
return <antd.Skeleton active />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(result)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PlaylistView
|
<PlaylistView
|
||||||
playlist={result}
|
playlist={result}
|
||||||
|
@ -10,7 +10,11 @@ const MusicNavbar = React.forwardRef((props, ref) => {
|
|||||||
useUrlQuery
|
useUrlQuery
|
||||||
renderResults={false}
|
renderResults={false}
|
||||||
model={async (keywords, params) =>
|
model={async (keywords, params) =>
|
||||||
SearchModel.search(keywords, params, ["tracks"])
|
SearchModel.search(keywords, params, [
|
||||||
|
"tracks",
|
||||||
|
"albums",
|
||||||
|
"artists",
|
||||||
|
])
|
||||||
}
|
}
|
||||||
onSearchResult={props.setSearchResults}
|
onSearchResult={props.setSearchResults}
|
||||||
onEmpty={() => props.setSearchResults(false)}
|
onEmpty={() => props.setSearchResults(false)}
|
||||||
|
@ -8,11 +8,11 @@ import MusicTrack from "@components/Music/Track"
|
|||||||
import Playlist from "@components/Music/Playlist"
|
import Playlist from "@components/Music/Playlist"
|
||||||
|
|
||||||
const ResultGroupsDecorators = {
|
const ResultGroupsDecorators = {
|
||||||
playlists: {
|
albums: {
|
||||||
icon: "MdPlaylistPlay",
|
icon: "MdAlbum",
|
||||||
label: "Playlists",
|
label: "Albums",
|
||||||
renderItem: (props) => {
|
renderItem: (props) => {
|
||||||
return <Playlist key={props.key} playlist={props.item} />
|
return <Playlist row playlist={props.item} />
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tracks: {
|
tracks: {
|
||||||
@ -23,7 +23,6 @@ const ResultGroupsDecorators = {
|
|||||||
<MusicTrack
|
<MusicTrack
|
||||||
key={props.key}
|
key={props.key}
|
||||||
track={props.item}
|
track={props.item}
|
||||||
//onClickPlayBtn={() => app.cores.player.start(props.item)}
|
|
||||||
onClick={() => app.location.push(`/play/${props.item._id}`)}
|
onClick={() => app.location.push(`/play/${props.item._id}`)}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -40,6 +39,10 @@ const SearchResults = ({ data }) => {
|
|||||||
|
|
||||||
// filter out groups with no items array property
|
// filter out groups with no items array property
|
||||||
groupsKeys = groupsKeys.filter((key) => {
|
groupsKeys = groupsKeys.filter((key) => {
|
||||||
|
if (!data[key]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (!Array.isArray(data[key].items)) {
|
if (!Array.isArray(data[key].items)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,10 @@ import path from "node:path"
|
|||||||
|
|
||||||
import { FFMPEGLib, Utils } from "../FFMPEGLib"
|
import { FFMPEGLib, Utils } from "../FFMPEGLib"
|
||||||
|
|
||||||
|
const codecOverrides = {
|
||||||
|
wav: "flac",
|
||||||
|
}
|
||||||
|
|
||||||
export default class SegmentedAudioMPDJob extends FFMPEGLib {
|
export default class SegmentedAudioMPDJob extends FFMPEGLib {
|
||||||
constructor(params = {}) {
|
constructor(params = {}) {
|
||||||
super()
|
super()
|
||||||
@ -26,11 +30,11 @@ export default class SegmentedAudioMPDJob extends FFMPEGLib {
|
|||||||
`-c:a ${this.params.audioCodec}`,
|
`-c:a ${this.params.audioCodec}`,
|
||||||
`-map 0:a`,
|
`-map 0:a`,
|
||||||
`-f dash`,
|
`-f dash`,
|
||||||
`-dash_segment_type mp4`,
|
|
||||||
`-segment_time ${this.params.segmentTime}`,
|
`-segment_time ${this.params.segmentTime}`,
|
||||||
`-use_template 1`,
|
`-use_template 1`,
|
||||||
`-use_timeline 1`,
|
`-use_timeline 1`,
|
||||||
`-init_seg_name "init.m4s"`,
|
//`-dash_segment_type mp4`,
|
||||||
|
//`-init_seg_name "init.m4s"`,
|
||||||
]
|
]
|
||||||
|
|
||||||
if (this.params.includeMetadata === false) {
|
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 <Representation ...> tags
|
||||||
|
const representationRegex = /(<Representation\b[^>]*)(>)/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 () => {
|
run = async () => {
|
||||||
const segmentationCmd = this.buildSegmentationArgs()
|
|
||||||
const outputPath =
|
const outputPath =
|
||||||
this.params.outputDir ?? `${path.dirname(this.params.input)}/dash`
|
this.params.outputDir ?? `${path.dirname(this.params.input)}/dash`
|
||||||
const outputFile = path.join(outputPath, this.params.outputMasterName)
|
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 {
|
try {
|
||||||
|
this.emit("start", {
|
||||||
|
input: this.params.input,
|
||||||
|
output: outputPath,
|
||||||
|
params: this.params,
|
||||||
|
})
|
||||||
|
|
||||||
const inputProbe = await Utils.probe(this.params.input)
|
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({
|
const ffmpegResult = await this.ffmpeg({
|
||||||
args: segmentationCmd,
|
args: segmentationCmd,
|
||||||
onProcess: (process) => {
|
onProcess: (process) => {
|
||||||
@ -135,6 +183,29 @@ export default class SegmentedAudioMPDJob extends FFMPEGLib {
|
|||||||
|
|
||||||
let outputProbe = await Utils.probe(outputFile)
|
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", {
|
this.emit("end", {
|
||||||
probe: {
|
probe: {
|
||||||
input: inputProbe,
|
input: inputProbe,
|
||||||
|
35
packages/server/classes/SyncRoomManager/index.js
Normal file
35
packages/server/classes/SyncRoomManager/index.js
Normal file
@ -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 {}
|
@ -2,7 +2,7 @@ import path from "node:path"
|
|||||||
import SegmentedAudioMPDJob from "@shared-classes/SegmentedAudioMPDJob"
|
import SegmentedAudioMPDJob from "@shared-classes/SegmentedAudioMPDJob"
|
||||||
|
|
||||||
export default async ({ filePath, workPath, onProgress }) => {
|
export default async ({ filePath, workPath, onProgress }) => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
const outputDir = path.resolve(workPath, "a-dash")
|
const outputDir = path.resolve(workPath, "a-dash")
|
||||||
|
|
||||||
const job = new SegmentedAudioMPDJob({
|
const job = new SegmentedAudioMPDJob({
|
||||||
@ -10,7 +10,7 @@ export default async ({ filePath, workPath, onProgress }) => {
|
|||||||
outputDir: outputDir,
|
outputDir: outputDir,
|
||||||
|
|
||||||
// set to default as raw flac
|
// set to default as raw flac
|
||||||
audioCodec: "flac",
|
audioCodec: "copy",
|
||||||
audioBitrate: "default",
|
audioBitrate: "default",
|
||||||
audioSampleRate: "default",
|
audioSampleRate: "default",
|
||||||
})
|
})
|
||||||
|
@ -130,7 +130,7 @@ export default class Release {
|
|||||||
|
|
||||||
const items = release.items ?? release.list
|
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
|
// delete all releated tracks
|
||||||
await Track.deleteMany({
|
await Track.deleteMany({
|
||||||
|
@ -6,12 +6,12 @@ async function fullfillData(list, { user_id = null }) {
|
|||||||
list = [list]
|
list = [list]
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackIds = list.map((track) => {
|
|
||||||
return track._id
|
|
||||||
})
|
|
||||||
|
|
||||||
// if user_id is provided, fetch likes
|
// if user_id is provided, fetch likes
|
||||||
if (user_id) {
|
if (user_id) {
|
||||||
|
const trackIds = list.map((track) => {
|
||||||
|
return track._id
|
||||||
|
})
|
||||||
|
|
||||||
const tracksLikes = await Library.isFavorite(
|
const tracksLikes = await Library.isFavorite(
|
||||||
user_id,
|
user_id,
|
||||||
trackIds,
|
trackIds,
|
||||||
@ -32,21 +32,15 @@ async function fullfillData(list, { user_id = null }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
list = await Promise.all(list)
|
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
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,20 @@ import RedisClient from "@shared-classes/RedisClient"
|
|||||||
import SharedMiddlewares from "@shared-middlewares"
|
import SharedMiddlewares from "@shared-middlewares"
|
||||||
import LimitsClass from "@shared-classes/Limits"
|
import LimitsClass from "@shared-classes/Limits"
|
||||||
|
|
||||||
|
import InjectedAuth from "@shared-lib/injectedAuth"
|
||||||
|
|
||||||
export default class API extends Server {
|
export default class API extends Server {
|
||||||
static refName = "music"
|
static refName = "music"
|
||||||
static useEngine = "hyper-express-ng"
|
static listenPort = process.env.HTTP_LISTEN_PORT ?? 3003
|
||||||
static enableWebsockets = true
|
|
||||||
static routesPath = `${__dirname}/routes`
|
static websockets = {
|
||||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3003
|
enabled: true,
|
||||||
|
path: "/music",
|
||||||
|
}
|
||||||
|
|
||||||
|
static bypassCors = true
|
||||||
|
|
||||||
|
static useMiddlewares = ["logs"]
|
||||||
|
|
||||||
middlewares = {
|
middlewares = {
|
||||||
...SharedMiddlewares,
|
...SharedMiddlewares,
|
||||||
@ -24,9 +32,28 @@ export default class API extends Server {
|
|||||||
redis: RedisClient(),
|
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() {
|
async onInitialize() {
|
||||||
global.sse = this.contexts.SSEManager
|
global.sse = this.contexts.SSEManager
|
||||||
global.redis = this.contexts.redis.client
|
global.redis = this.contexts.redis.client
|
||||||
|
global.syncRoomLyrics = new Map()
|
||||||
|
|
||||||
await this.contexts.db.initialize()
|
await this.contexts.db.initialize()
|
||||||
await this.contexts.redis.initialize()
|
await this.contexts.redis.initialize()
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "music"
|
"name": "music",
|
||||||
|
"dependencies": {
|
||||||
|
"linebridge": "^1.0.0-alpha.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Library from "@classes/library"
|
import Library from "@classes/library"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { kind, item_id } = req.query
|
const { kind, item_id } = req.query
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Library from "@classes/library"
|
import Library from "@classes/library"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { kind, item_id, to } = req.body
|
const { kind, item_id, to } = req.body
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Library from "@classes/library"
|
import Library from "@classes/library"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const userId = req.auth.session.user_id
|
const userId = req.auth.session.user_id
|
||||||
const { limit = 50, offset = 0, kind } = req.query
|
const { limit = 50, offset = 0, kind } = req.query
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { MusicRelease, Track } from "@db_models"
|
import { MusicRelease, Track } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { keywords, limit = 10, offset = 0 } = req.query
|
const { keywords, limit = 10, offset = 0 } = req.query
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import { RecentActivity } from "@db_models"
|
|||||||
import TrackClass from "@classes/track"
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req, res) => {
|
fn: async (req, res) => {
|
||||||
const user_id = req.auth.session.user_id
|
const user_id = req.auth.session.user_id
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import ReleaseClass from "@classes/release"
|
import ReleaseClass from "@classes/release"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withOptionalAuthentication"],
|
useMiddlewares: ["withOptionalAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { release_id } = req.params
|
const { release_id } = req.params
|
||||||
const { limit = 50, offset = 0 } = req.query
|
const { limit = 50, offset = 0 } = req.query
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import ReleaseClass from "@classes/release"
|
import ReleaseClass from "@classes/release"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
return await ReleaseClass.delete(req.params.release_id, {
|
return await ReleaseClass.delete(req.params.release_id, {
|
||||||
user_id: req.auth.session.user_id,
|
user_id: req.auth.session.user_id,
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import ReleaseClass from "@classes/release"
|
import ReleaseClass from "@classes/release"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
if (req.body._id) {
|
if (req.body._id) {
|
||||||
return await ReleaseClass.update(req.body._id, {
|
return await ReleaseClass.update(req.body._id, {
|
||||||
...req.body,
|
...req.body,
|
||||||
user_id: req.auth.session.user_id,
|
user_id: req.auth.session.user_id,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
return await ReleaseClass.create({
|
return await ReleaseClass.create({
|
||||||
...req.body,
|
...req.body,
|
||||||
user_id: req.auth.session.user_id,
|
user_id: req.auth.session.user_id,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import TrackClass from "@classes/track"
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withOptionalAuthentication"],
|
useMiddlewares: ["withOptionalAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { track_id } = req.params
|
const { track_id } = req.params
|
||||||
const user_id = req.auth?.session?.user_id
|
const user_id = req.auth?.session?.user_id
|
||||||
|
|
||||||
const track = await TrackClass.get(track_id, {
|
const track = await TrackClass.get(track_id, {
|
||||||
user_id
|
user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return track
|
return track
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import TrackClass from "@classes/track"
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { track_id } = req.params
|
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) {
|
if (track.publisher.user_id !== req.auth.session.user_id) {
|
||||||
throw new Error("Forbidden, you don't own this track")
|
throw new Error("Forbidden, you don't own this track")
|
||||||
}
|
}
|
||||||
|
|
||||||
await TrackClass.delete(track_id)
|
await TrackClass.delete(track_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
track: track,
|
track: track,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,66 +1,66 @@
|
|||||||
import { TrackLyric, Track } from "@db_models"
|
import { TrackLyric, Track } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { track_id } = req.params
|
const { track_id } = req.params
|
||||||
const { video_source, lrc, sync_audio_at } = req.body
|
const { video_source, lrc, sync_audio_at } = req.body
|
||||||
|
|
||||||
// check if track exists
|
// check if track exists
|
||||||
let track = await Track.findById(track_id).catch(() => null)
|
let track = await Track.findById(track_id).catch(() => null)
|
||||||
|
|
||||||
if (!track) {
|
if (!track) {
|
||||||
throw new OperationError(404, "Track not found")
|
throw new OperationError(404, "Track not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.publisher.user_id !== req.auth.session.user_id) {
|
if (track.publisher.user_id !== req.auth.session.user_id) {
|
||||||
throw new OperationError(403, "Unauthorized")
|
throw new OperationError(403, "Unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Setting lyrics for track ${track_id} >`, {
|
console.log(`Setting lyrics for track ${track_id} >`, {
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
video_source: video_source,
|
video_source: video_source,
|
||||||
lrc: lrc,
|
lrc: lrc,
|
||||||
})
|
})
|
||||||
|
|
||||||
// check if trackLyric exists
|
// check if trackLyric exists
|
||||||
let trackLyric = await TrackLyric.findOne({
|
let trackLyric = await TrackLyric.findOne({
|
||||||
track_id: track_id
|
track_id: track_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
// if trackLyric exists, update it, else create it
|
// if trackLyric exists, update it, else create it
|
||||||
if (!trackLyric) {
|
if (!trackLyric) {
|
||||||
trackLyric = new TrackLyric({
|
trackLyric = new TrackLyric({
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
video_source: video_source,
|
video_source: video_source,
|
||||||
lrc: lrc,
|
lrc: lrc,
|
||||||
sync_audio_at: sync_audio_at,
|
sync_audio_at: sync_audio_at,
|
||||||
})
|
})
|
||||||
|
|
||||||
await trackLyric.save()
|
await trackLyric.save()
|
||||||
} else {
|
} else {
|
||||||
const update = Object()
|
const update = Object()
|
||||||
|
|
||||||
if (typeof video_source !== "undefined") {
|
if (typeof video_source !== "undefined") {
|
||||||
update.video_source = video_source
|
update.video_source = video_source
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof lrc !== "undefined") {
|
if (typeof lrc !== "undefined") {
|
||||||
update.lrc = lrc
|
update.lrc = lrc
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof sync_audio_at !== "undefined") {
|
if (typeof sync_audio_at !== "undefined") {
|
||||||
update.sync_audio_at = sync_audio_at
|
update.sync_audio_at = sync_audio_at
|
||||||
}
|
}
|
||||||
|
|
||||||
trackLyric = await TrackLyric.findOneAndUpdate(
|
trackLyric = await TrackLyric.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
},
|
},
|
||||||
update,
|
update,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return trackLyric
|
return trackLyric
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
import { TrackOverride } from "@db_models"
|
import { TrackOverride } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication", "onlyAdmin"],
|
useMiddlewares: ["withAuthentication", "onlyAdmin"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { track_id } = req.params
|
const { track_id } = req.params
|
||||||
const { service, override } = req.body
|
const { service, override } = req.body
|
||||||
|
|
||||||
let trackOverride = await TrackOverride.findOne({
|
let trackOverride = await TrackOverride.findOne({
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
service: service,
|
service: service,
|
||||||
}).catch(() => null)
|
}).catch(() => null)
|
||||||
|
|
||||||
if (!trackOverride) {
|
if (!trackOverride) {
|
||||||
trackOverride = new TrackOverride({
|
trackOverride = new TrackOverride({
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
service: service,
|
service: service,
|
||||||
override: override,
|
override: override,
|
||||||
})
|
})
|
||||||
|
|
||||||
await trackOverride.save()
|
await trackOverride.save()
|
||||||
} else {
|
} else {
|
||||||
trackOverride = await TrackOverride.findOneAndUpdate(
|
trackOverride = await TrackOverride.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
track_id: track_id,
|
track_id: track_id,
|
||||||
service: service,
|
service: service,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
override: override,
|
override: override,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return trackOverride.override
|
return trackOverride.override
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ export default async (req) => {
|
|||||||
|
|
||||||
const items = await Track.find(query)
|
const items = await Track.find(query)
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
.select("-source -publisher -public")
|
||||||
.skip(trim)
|
.skip(trim)
|
||||||
.sort({ _id: -1 })
|
.sort({ _id: -1 })
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import requiredFields from "@shared-utils/requiredFields"
|
|||||||
import TrackClass from "@classes/track"
|
import TrackClass from "@classes/track"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
if (Array.isArray(req.body.items)) {
|
if (Array.isArray(req.body.items)) {
|
||||||
let results = []
|
let results = []
|
||||||
|
13
packages/server/services/music/ws_routes/sync_room/join.js
Normal file
13
packages/server/services/music/ws_routes/sync_room/join.js
Normal file
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
export default async (client) => {
|
||||||
|
console.log(`[SYNC-ROOM] Requesting lyrics of room ${client.syncroom}`)
|
||||||
|
|
||||||
|
return global.syncRoomLyrics.get(client.syncroom)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user