mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-11 03:24:16 +00:00
improve sync by updating remote state
This commit is contained in:
parent
ab171c150a
commit
078d418684
@ -157,6 +157,7 @@ export default class Player extends Core {
|
|||||||
velocity: this.velocity.bind(this),
|
velocity: this.velocity.bind(this),
|
||||||
close: this.close.bind(this),
|
close: this.close.bind(this),
|
||||||
toogleSyncMode: this.toogleSyncMode.bind(this),
|
toogleSyncMode: this.toogleSyncMode.bind(this),
|
||||||
|
currentState: this.currentState.bind(this),
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeAudioProcessors() {
|
async initializeAudioProcessors() {
|
||||||
@ -212,7 +213,10 @@ export default class Player extends Core {
|
|||||||
app.eventBus.emit("player.loading.update", change.object.loading)
|
app.eventBus.emit("player.loading.update", change.object.loading)
|
||||||
|
|
||||||
if (this.state.syncMode) {
|
if (this.state.syncMode) {
|
||||||
useMusicSync("music:player:loading", change.object.loading)
|
useMusicSync("music:player:loading", {
|
||||||
|
loading: change.object.loading,
|
||||||
|
state: this.currentState()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
@ -236,7 +240,8 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
if (this.state.syncMode) {
|
if (this.state.syncMode) {
|
||||||
useMusicSync("music:player:start", {
|
useMusicSync("music:player:start", {
|
||||||
manifest: change.object.currentAudioManifest
|
manifest: change.object.currentAudioManifest,
|
||||||
|
state: this.currentState()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,6 +295,7 @@ export default class Player extends Core {
|
|||||||
time: this.currentAudioInstance.audioElement.currentTime,
|
time: this.currentAudioInstance.audioElement.currentTime,
|
||||||
duration: this.currentAudioInstance.audioElement.duration,
|
duration: this.currentAudioInstance.audioElement.duration,
|
||||||
startingNew: this.state.startingNew,
|
startingNew: this.state.startingNew,
|
||||||
|
state: this.currentState(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,6 +443,9 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
// stop playback
|
// stop playback
|
||||||
if (this.currentAudioInstance.audioElement) {
|
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
|
// if is in sync mode, just seek to last position to stop playback and avoid sync issues
|
||||||
this.currentAudioInstance.audioElement.pause()
|
this.currentAudioInstance.audioElement.pause()
|
||||||
}
|
}
|
||||||
@ -560,7 +569,8 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
if (this.state.syncMode) {
|
if (this.state.syncMode) {
|
||||||
useMusicSync("music:player:seek", {
|
useMusicSync("music:player:seek", {
|
||||||
position: instanceObj.audioElement.currentTime
|
position: instanceObj.audioElement.currentTime,
|
||||||
|
state: this.currentState(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -655,6 +665,13 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
instance.audioElement.muted = this.state.audioMuted
|
instance.audioElement.muted = this.state.audioMuted
|
||||||
|
|
||||||
|
console.log("Playing audio", instance.audioElement.src)
|
||||||
|
|
||||||
|
// 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.load()
|
||||||
|
|
||||||
instance.audioElement.play()
|
instance.audioElement.play()
|
||||||
@ -715,7 +732,7 @@ export default class Player extends Core {
|
|||||||
this.play(this.audioQueue[0])
|
this.play(this.audioQueue[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(manifest, { sync = false } = {}) {
|
async start(manifest, { sync = false, time } = {}) {
|
||||||
if (this.state.syncModeLocked && !sync) {
|
if (this.state.syncModeLocked && !sync) {
|
||||||
console.warn("Sync mode is locked, cannot do this action")
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
return false
|
return false
|
||||||
@ -738,7 +755,9 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
this.state.loading = true
|
this.state.loading = true
|
||||||
|
|
||||||
this.play(this.audioQueue[0])
|
this.play(this.audioQueue[0], {
|
||||||
|
time: time ?? 0
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
next({ sync = false } = {}) {
|
next({ sync = false } = {}) {
|
||||||
@ -991,4 +1010,16 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
return this.state.syncMode
|
return this.state.syncMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentState() {
|
||||||
|
return {
|
||||||
|
playbackStatus: this.state.playbackStatus,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -67,6 +67,8 @@ class MusicSyncSubCore {
|
|||||||
// check if user is owner
|
// check if user is owner
|
||||||
app.cores.player.toogleSyncMode(true, data.ownerUserId !== app.userData._id)
|
app.cores.player.toogleSyncMode(true, data.ownerUserId !== app.userData._id)
|
||||||
|
|
||||||
|
this.startSendStateInterval()
|
||||||
|
|
||||||
this.eventBus.emit("room:joined", data)
|
this.eventBus.emit("room:joined", data)
|
||||||
},
|
},
|
||||||
"room:left": (data) => {
|
"room:left": (data) => {
|
||||||
@ -120,7 +122,7 @@ class MusicSyncSubCore {
|
|||||||
},
|
},
|
||||||
// Room Control
|
// Room Control
|
||||||
"music:player:start": (data) => {
|
"music:player:start": (data) => {
|
||||||
if (data.selfUser.user_id === app.userData._id) {
|
if (data.command_issuer === app.userData._id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,10 +130,11 @@ class MusicSyncSubCore {
|
|||||||
|
|
||||||
app.cores.player.start(data.manifest, {
|
app.cores.player.start(data.manifest, {
|
||||||
sync: true,
|
sync: true,
|
||||||
|
time: data.time,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
"music:player:seek": (data) => {
|
"music:player:seek": (data) => {
|
||||||
if (data.selfUser.user_id === app.userData._id) {
|
if (data.command_issuer === app.userData._id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,13 +145,14 @@ class MusicSyncSubCore {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
"music:player:status": (data) => {
|
"music:player:status": (data) => {
|
||||||
if (data.selfUser.user_id === app.userData._id) {
|
if (data.command_issuer === app.userData._id) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// avoid dispatch if event pause and current time is the audio duration
|
// avoid dispatch if event pause and current time is the audio duration
|
||||||
if (data.startingNew || data.status === "paused" && data.time === data.duration) {
|
if (data.startingNew || data.status === "paused" && data.time === data.duration) {
|
||||||
return app.cores.player.stop()
|
//return app.cores.player.playback.stop()
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data.status) {
|
switch (data.status) {
|
||||||
@ -190,6 +194,33 @@ class MusicSyncSubCore {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startSendStateInterval() {
|
||||||
|
if (this.sendStateInterval) {
|
||||||
|
clearInterval(this.sendStateInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.firstStateSent = true
|
||||||
|
|
||||||
|
this.sendStateInterval = setInterval(() => {
|
||||||
|
if (!this.currentRoomData) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let state = app.cores.player.currentState()
|
||||||
|
|
||||||
|
console.log("state", state)
|
||||||
|
|
||||||
|
this.musicWs.emit("music:state:update", {
|
||||||
|
...state,
|
||||||
|
firstSync: this.firstStateSent
|
||||||
|
})
|
||||||
|
|
||||||
|
if (this.firstStateSent) {
|
||||||
|
this.firstStateSent = false
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
|
||||||
dispatchEvent(eventName, data) {
|
dispatchEvent(eventName, data) {
|
||||||
if (!eventName) {
|
if (!eventName) {
|
||||||
return false
|
return false
|
||||||
@ -254,6 +285,11 @@ class MusicSyncSubCore {
|
|||||||
|
|
||||||
this.currentRoomData = null
|
this.currentRoomData = null
|
||||||
|
|
||||||
|
if (this.sendStateInterval) {
|
||||||
|
this.firstStateSent = false
|
||||||
|
clearInterval(this.sendStateInterval)
|
||||||
|
}
|
||||||
|
|
||||||
Object.keys(this.roomEvents).forEach((eventName) => {
|
Object.keys(this.roomEvents).forEach((eventName) => {
|
||||||
this.musicWs.off(eventName, this.roomEvents[eventName])
|
this.musicWs.off(eventName, this.roomEvents[eventName])
|
||||||
})
|
})
|
||||||
|
@ -24,14 +24,15 @@ function generateFnHandler(fn, socket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function composePayloadData(socket, data) {
|
function composePayloadData(socket, data = {}) {
|
||||||
return {
|
return {
|
||||||
selfUser: {
|
user: {
|
||||||
user_id: socket.userData._id,
|
user_id: socket.userData._id,
|
||||||
username: socket.userData.username,
|
username: socket.userData.username,
|
||||||
fullName: socket.userData.fullName,
|
fullName: socket.userData.fullName,
|
||||||
avatar: socket.userData.avatar,
|
avatar: socket.userData.avatar,
|
||||||
},
|
},
|
||||||
|
command_issuer: data.command_issuer ?? socket.userData._id,
|
||||||
...data
|
...data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,6 +48,9 @@ class Room {
|
|||||||
this.roomOptions = roomOptions
|
this.roomOptions = roomOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// declare the maximum audio offset from owner
|
||||||
|
static maxOffsetFromOwner = 1
|
||||||
|
|
||||||
ownerUserId = null
|
ownerUserId = null
|
||||||
|
|
||||||
connections = []
|
connections = []
|
||||||
@ -65,6 +69,10 @@ class Room {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.state) {
|
||||||
|
this.currentState = data.state
|
||||||
|
}
|
||||||
|
|
||||||
this.io.to(this.roomId).emit("music:player:start", composePayloadData(socket, data))
|
this.io.to(this.roomId).emit("music:player:start", composePayloadData(socket, data))
|
||||||
},
|
},
|
||||||
"music:player:seek": (socket, data) => {
|
"music:player:seek": (socket, data) => {
|
||||||
@ -74,21 +82,86 @@ class Room {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.state) {
|
||||||
|
this.currentState = data.state
|
||||||
|
}
|
||||||
|
|
||||||
this.io.to(this.roomId).emit("music:player:seek", composePayloadData(socket, data))
|
this.io.to(this.roomId).emit("music:player:seek", composePayloadData(socket, data))
|
||||||
},
|
},
|
||||||
"music:player:loading": () => {
|
"music:player:loading": (socket, data) => {
|
||||||
// TODO: Softmode and Hardmode
|
// TODO: Softmode and Hardmode
|
||||||
|
// Ignore if is the owner
|
||||||
|
if (socket.userData._id === this.ownerUserId) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// sync with current state, seek if needed (if is not owner)
|
// if not loading, check if need to sync
|
||||||
|
if (!data.loading) {
|
||||||
|
// try to sync with current state
|
||||||
|
if (data.state.time > this.currentState.time + Room.maxOffsetFromOwner) {
|
||||||
|
socket.emit("music:player:seek", composePayloadData(socket, {
|
||||||
|
position: this.currentState.time,
|
||||||
|
command_issuer: this.ownerUserId,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"music:player:status": (socket, data) => {
|
"music:player:status": (socket, data) => {
|
||||||
if (socket.userData._id !== this.ownerUserId) {
|
if (socket.userData._id !== this.ownerUserId) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.state) {
|
||||||
|
this.currentState = data.state
|
||||||
|
}
|
||||||
|
|
||||||
this.io.to(this.roomId).emit("music:player:status", composePayloadData(socket, data))
|
this.io.to(this.roomId).emit("music:player:status", composePayloadData(socket, data))
|
||||||
},
|
},
|
||||||
// ROOM CONTROL
|
// UPDATE TICK
|
||||||
|
"music:state:update": (socket, data) => {
|
||||||
|
if (socket.userData._id === this.ownerUserId) {
|
||||||
|
// update current state
|
||||||
|
this.currentState = data
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.currentState) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.loading) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if match with current manifest
|
||||||
|
if (!data.manifest || data.manifest._id !== this.currentState.manifest._id) {
|
||||||
|
socket.emit("music:player:start", composePayloadData(socket, {
|
||||||
|
manifest: this.currentState.manifest,
|
||||||
|
time: this.currentState.time,
|
||||||
|
command_issuer: this.ownerUserId,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.firstSync) {
|
||||||
|
// if not owner, try to sync with current state
|
||||||
|
if (data.time > this.currentState.time + Room.maxOffsetFromOwner) {
|
||||||
|
socket.emit("music:player:seek", composePayloadData(socket, {
|
||||||
|
position: this.currentState.time,
|
||||||
|
command_issuer: this.ownerUserId,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if match with current playing status
|
||||||
|
if (data.playbackStatus !== this.currentState.playbackStatus && data.firstSync) {
|
||||||
|
socket.emit("music:player:status", composePayloadData(socket, {
|
||||||
|
status: this.currentState.playbackStatus,
|
||||||
|
command_issuer: this.ownerUserId,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// ROOM MODERATION CONTROL
|
||||||
"room:moderation:kick": (socket, data) => {
|
"room:moderation:kick": (socket, data) => {
|
||||||
if (socket.userData._id !== this.ownerUserId) {
|
if (socket.userData._id !== this.ownerUserId) {
|
||||||
return socket.emit("error", {
|
return socket.emit("error", {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user