Skip to content

Commit 76e1635

Browse files
committed
feat: add music sync room feature and bump version to 1.2.0
1. 新增音乐同步房间功能,支持共享播放列表和同步播放控制 2. 更新README文档,添加音乐同步房间的功能说明 3. 扩展RoomType类型,新增'music'枚举值 4. 定义音乐同步相关的类型接口MusicQueueItem和MusicSyncState 5. 添加音乐同步相关的websocket事件处理和广播逻辑 6. 将服务端版本号更新至1.2.0
1 parent 9b48263 commit 76e1635

5 files changed

Lines changed: 113 additions & 4 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
## 功能特性
1010

1111
- 🎬 多人同步观影(播放/暂停/跳转同步)
12+
- 🎵 音乐同步房间(共享播放列表、同步播放控制)
1213
- 🖥️ 屏幕共享房间(实时共享桌面/窗口)
1314
- 💬 实时聊天系统
1415
- 🎙️ 语音通话(WebRTC)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "watch-room-server",
3-
"version": "1.1.0",
3+
"version": "1.2.0",
44
"description": "Standalone watch room server for MoonTVPlus",
55
"main": "dist/index.js",
66
"type": "module",

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ app.get('/stats', (req, res) => {
6161
app.get('/', (_req, res) => {
6262
res.json({
6363
name: 'Watch Room Server',
64-
version: '1.1.0',
64+
version: '1.2.0',
6565
description: 'Standalone watch room server for MoonTVPlus',
6666
endpoints: {
6767
health: '/health',

src/types.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export type RTCIceCandidateInit = {
1212
sdpMid?: string | null;
1313
};
1414

15-
export type RoomType = 'sync' | 'screen';
15+
export type RoomType = 'sync' | 'screen' | 'music';
1616

1717
export interface ScreenState {
1818
type: 'screen';
@@ -22,6 +22,29 @@ export interface ScreenState {
2222
startedAt?: number;
2323
}
2424

25+
export interface MusicQueueItem {
26+
id: string;
27+
name: string;
28+
artist: string;
29+
album?: string;
30+
pic?: string;
31+
platform: string;
32+
songmid?: string;
33+
duration?: number;
34+
durationText?: string;
35+
}
36+
37+
export interface MusicSyncState {
38+
type: 'music';
39+
song: MusicQueueItem;
40+
nextSong: MusicQueueItem | null;
41+
currentTime: number;
42+
isPlaying: boolean;
43+
quality: string;
44+
playMode: 'loop' | 'single' | 'random';
45+
updatedAt: number;
46+
}
47+
2548
export interface Room {
2649
id: string;
2750
name: string;
@@ -33,7 +56,7 @@ export interface Room {
3356
ownerName: string;
3457
ownerToken: string;
3558
memberCount: number;
36-
currentState: PlayState | LiveState | ScreenState | null;
59+
currentState: PlayState | LiveState | ScreenState | MusicSyncState | null;
3760
createdAt: number;
3861
lastOwnerHeartbeat: number;
3962
}
@@ -102,6 +125,12 @@ export interface ServerToClientEvents {
102125
'screen:offer': (data: { userId: string; offer: RTCSessionDescriptionInit }) => void;
103126
'screen:answer': (data: { userId: string; answer: RTCSessionDescriptionInit }) => void;
104127
'screen:ice': (data: { userId: string; candidate: RTCIceCandidateInit }) => void;
128+
'music:change': (state: MusicSyncState) => void;
129+
'music:update': (state: MusicSyncState) => void;
130+
'music:play': (state: MusicSyncState) => void;
131+
'music:pause': (state: MusicSyncState) => void;
132+
'music:seek': (state: MusicSyncState) => void;
133+
'music:queue': (state: MusicSyncState) => void;
105134
'chat:message': (message: ChatMessage) => void;
106135
'voice:offer': (data: { userId: string; offer: RTCSessionDescriptionInit }) => void;
107136
'voice:answer': (data: { userId: string; answer: RTCSessionDescriptionInit }) => void;
@@ -159,6 +188,13 @@ export interface ClientToServerEvents {
159188
'screen:answer': (data: { targetUserId: string; answer: RTCSessionDescriptionInit }) => void;
160189
'screen:ice': (data: { targetUserId: string; candidate: RTCIceCandidateInit }) => void;
161190

191+
'music:change': (state: MusicSyncState) => void;
192+
'music:update': (state: MusicSyncState) => void;
193+
'music:play': (state: MusicSyncState) => void;
194+
'music:pause': (state: MusicSyncState) => void;
195+
'music:seek': (state: MusicSyncState) => void;
196+
'music:queue': (state: MusicSyncState) => void;
197+
162198
'chat:message': (data: { content: string; type: 'text' | 'emoji' }) => void;
163199

164200
'voice:offer': (data: { targetUserId: string; offer: RTCSessionDescriptionInit }) => void;

src/watch-room-server.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,78 @@ export class WatchRoomServer {
344344
});
345345
});
346346

347+
socket.on('music:change', (state) => {
348+
const roomInfo = this.socketToRoom.get(socket.id);
349+
if (!roomInfo || !roomInfo.isOwner) return;
350+
351+
const room = this.rooms.get(roomInfo.roomId);
352+
if (room?.roomType === 'music') {
353+
room.currentState = state;
354+
this.rooms.set(roomInfo.roomId, room);
355+
socket.to(roomInfo.roomId).emit('music:change', state);
356+
}
357+
});
358+
359+
socket.on('music:update', (state) => {
360+
const roomInfo = this.socketToRoom.get(socket.id);
361+
if (!roomInfo || !roomInfo.isOwner) return;
362+
363+
const room = this.rooms.get(roomInfo.roomId);
364+
if (room?.roomType === 'music') {
365+
room.currentState = state;
366+
this.rooms.set(roomInfo.roomId, room);
367+
socket.to(roomInfo.roomId).emit('music:update', state);
368+
}
369+
});
370+
371+
socket.on('music:queue', (state) => {
372+
const roomInfo = this.socketToRoom.get(socket.id);
373+
if (!roomInfo || !roomInfo.isOwner) return;
374+
375+
const room = this.rooms.get(roomInfo.roomId);
376+
if (room?.roomType === 'music') {
377+
room.currentState = state;
378+
this.rooms.set(roomInfo.roomId, room);
379+
socket.to(roomInfo.roomId).emit('music:queue', state);
380+
}
381+
});
382+
383+
socket.on('music:play', (state) => {
384+
const roomInfo = this.socketToRoom.get(socket.id);
385+
if (!roomInfo || !roomInfo.isOwner) return;
386+
387+
const room = this.rooms.get(roomInfo.roomId);
388+
if (room?.roomType === 'music') {
389+
room.currentState = state;
390+
this.rooms.set(roomInfo.roomId, room);
391+
socket.to(roomInfo.roomId).emit('music:play', state);
392+
}
393+
});
394+
395+
socket.on('music:pause', (state) => {
396+
const roomInfo = this.socketToRoom.get(socket.id);
397+
if (!roomInfo || !roomInfo.isOwner) return;
398+
399+
const room = this.rooms.get(roomInfo.roomId);
400+
if (room?.roomType === 'music') {
401+
room.currentState = state;
402+
this.rooms.set(roomInfo.roomId, room);
403+
socket.to(roomInfo.roomId).emit('music:pause', state);
404+
}
405+
});
406+
407+
socket.on('music:seek', (state) => {
408+
const roomInfo = this.socketToRoom.get(socket.id);
409+
if (!roomInfo || !roomInfo.isOwner) return;
410+
411+
const room = this.rooms.get(roomInfo.roomId);
412+
if (room?.roomType === 'music') {
413+
room.currentState = state;
414+
this.rooms.set(roomInfo.roomId, room);
415+
socket.to(roomInfo.roomId).emit('music:seek', state);
416+
}
417+
});
418+
347419
// 聊天消息
348420
socket.on('chat:message', (data) => {
349421
const roomInfo = this.socketToRoom.get(socket.id);

0 commit comments

Comments
 (0)