mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-11 19:44:15 +00:00
support for sync mode
This commit is contained in:
parent
c35d002e9e
commit
57eac8878b
@ -7,11 +7,23 @@ import { FastAverageColor } from "fast-average-color"
|
|||||||
import EmbbededMediaPlayer from "components/EmbbededMediaPlayer"
|
import EmbbededMediaPlayer from "components/EmbbededMediaPlayer"
|
||||||
import BackgroundMediaPlayer from "components/BackgroundMediaPlayer"
|
import BackgroundMediaPlayer from "components/BackgroundMediaPlayer"
|
||||||
|
|
||||||
import { DOMWindow } from "components/RenderWindow"
|
|
||||||
|
|
||||||
import GainProcessorNode from "./processors/gainNode"
|
import GainProcessorNode from "./processors/gainNode"
|
||||||
import CompressorProcessorNode from "./processors/compressorNode"
|
import CompressorProcessorNode from "./processors/compressorNode"
|
||||||
|
|
||||||
|
function useMusicSync(event, data) {
|
||||||
|
const currentRoomData = app.cores.sync.music.currentRoomData()
|
||||||
|
|
||||||
|
if (!currentRoomData) {
|
||||||
|
console.warn("No room data available")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.cores.sync.music.dispatchEvent(event, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the time tooks to fade in/out the volume when playing/pausing
|
||||||
|
const gradualFadeMs = 150
|
||||||
|
|
||||||
// TODO: Check if source playing is a stream. Also handle if it's a stream resuming after a pause will seek to the last position
|
// TODO: Check if source playing is a stream. Also handle if it's a stream resuming after a pause will seek to the last position
|
||||||
export default class Player extends Core {
|
export default class Player extends Core {
|
||||||
static refName = "player"
|
static refName = "player"
|
||||||
@ -50,6 +62,9 @@ export default class Player extends Core {
|
|||||||
currentAudioManifest: null,
|
currentAudioManifest: null,
|
||||||
playbackStatus: "stopped",
|
playbackStatus: "stopped",
|
||||||
livestream: false,
|
livestream: false,
|
||||||
|
syncMode: false,
|
||||||
|
syncModeLocked: false,
|
||||||
|
startingNew: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
public = {
|
public = {
|
||||||
@ -110,45 +125,19 @@ export default class Player extends Core {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.syncModeLocked) {
|
||||||
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (this.currentAudioInstance.audioElement.paused) {
|
if (this.currentAudioInstance.audioElement.paused) {
|
||||||
this.public.playback.play()
|
this.resumePlayback()
|
||||||
} else {
|
} else {
|
||||||
this.public.playback.pause()
|
this.pausePlayback()
|
||||||
}
|
}
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
play: function () {
|
play: this.resumePlayback.bind(this),
|
||||||
if (!this.currentAudioInstance) {
|
pause: this.pausePlayback.bind(this),
|
||||||
console.error("No audio instance")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// set gain exponentially
|
|
||||||
this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime(
|
|
||||||
this.state.audioVolume,
|
|
||||||
this.audioContext.currentTime + 0.1
|
|
||||||
)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.currentAudioInstance.audioElement.play()
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
}.bind(this),
|
|
||||||
pause: function () {
|
|
||||||
if (!this.currentAudioInstance) {
|
|
||||||
console.error("No audio instance")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// set gain exponentially
|
|
||||||
this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime(
|
|
||||||
0.0001,
|
|
||||||
this.audioContext.currentTime + 0.1
|
|
||||||
)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.currentAudioInstance.audioElement.pause()
|
|
||||||
}, 100)
|
|
||||||
}.bind(this),
|
|
||||||
next: this.next.bind(this),
|
next: this.next.bind(this),
|
||||||
previous: this.previous.bind(this),
|
previous: this.previous.bind(this),
|
||||||
stop: this.stop.bind(this),
|
stop: this.stop.bind(this),
|
||||||
@ -167,6 +156,7 @@ export default class Player extends Core {
|
|||||||
duration: this.duration.bind(this),
|
duration: this.duration.bind(this),
|
||||||
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),
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeAudioProcessors() {
|
async initializeAudioProcessors() {
|
||||||
@ -216,13 +206,15 @@ export default class Player extends Core {
|
|||||||
case "crossfading": {
|
case "crossfading": {
|
||||||
app.eventBus.emit("player.crossfading.update", change.object.crossfading)
|
app.eventBus.emit("player.crossfading.update", change.object.crossfading)
|
||||||
|
|
||||||
console.log("crossfading", change.object.crossfading)
|
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "loading": {
|
case "loading": {
|
||||||
app.eventBus.emit("player.loading.update", change.object.loading)
|
app.eventBus.emit("player.loading.update", change.object.loading)
|
||||||
|
|
||||||
|
if (this.state.syncMode) {
|
||||||
|
useMusicSync("music:player:loading", change.object.loading)
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "currentAudioManifest": {
|
case "currentAudioManifest": {
|
||||||
@ -242,6 +234,12 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.syncMode) {
|
||||||
|
useMusicSync("music:player:start", {
|
||||||
|
manifest: change.object.currentAudioManifest
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "coverColorAnalysis": {
|
case "coverColorAnalysis": {
|
||||||
@ -282,6 +280,19 @@ export default class Player extends Core {
|
|||||||
case "playbackStatus": {
|
case "playbackStatus": {
|
||||||
app.eventBus.emit("player.status.update", change.object.playbackStatus)
|
app.eventBus.emit("player.status.update", change.object.playbackStatus)
|
||||||
|
|
||||||
|
if (this.state.syncMode) {
|
||||||
|
if (this.state.loading) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
useMusicSync("music:player:status", {
|
||||||
|
status: change.object.playbackStatus,
|
||||||
|
time: this.currentAudioInstance.audioElement.currentTime,
|
||||||
|
duration: this.currentAudioInstance.audioElement.duration,
|
||||||
|
startingNew: this.state.startingNew,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case "minimized": {
|
case "minimized": {
|
||||||
@ -297,6 +308,12 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
case "syncModeLocked": {
|
||||||
|
app.eventBus.emit("player.syncModeLocked.update", change.object.syncModeLocked)
|
||||||
|
}
|
||||||
|
case "syncMode": {
|
||||||
|
app.eventBus.emit("player.syncMode.update", change.object.syncMode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -318,11 +335,7 @@ export default class Player extends Core {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentDomWindow = new DOMWindow({
|
this.currentDomWindow = app.layout.floatingStack.add("mediaPlayer", EmbbededMediaPlayer)
|
||||||
id: "mediaPlayer"
|
|
||||||
})
|
|
||||||
|
|
||||||
this.currentDomWindow.render(React.createElement(EmbbededMediaPlayer))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detachPlayerComponent() {
|
detachPlayerComponent() {
|
||||||
@ -331,7 +344,8 @@ export default class Player extends Core {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
this.currentDomWindow.destroy()
|
app.layout.floatingStack.remove("mediaPlayer")
|
||||||
|
|
||||||
this.currentDomWindow = null
|
this.currentDomWindow = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,13 +430,14 @@ export default class Player extends Core {
|
|||||||
// Instance managing methods
|
// Instance managing methods
|
||||||
//
|
//
|
||||||
|
|
||||||
async destroyCurrentInstance() {
|
async destroyCurrentInstance({ sync = false } = {}) {
|
||||||
if (!this.currentAudioInstance) {
|
if (!this.currentAudioInstance) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop playback
|
// stop playback
|
||||||
if (this.currentAudioInstance.audioElement) {
|
if (this.currentAudioInstance.audioElement) {
|
||||||
|
// 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,23 +488,35 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
// handle on end
|
// handle on end
|
||||||
instanceObj.audioElement.addEventListener("ended", () => {
|
instanceObj.audioElement.addEventListener("ended", () => {
|
||||||
// cancel if is crossfading
|
// if is in sync locked mode, do noting
|
||||||
|
if (this.state.syncModeLocked) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
this.next()
|
this.next()
|
||||||
})
|
})
|
||||||
|
|
||||||
instanceObj.audioElement.addEventListener("play", () => {
|
instanceObj.audioElement.addEventListener("play", () => {
|
||||||
this.state.loading = false
|
|
||||||
|
|
||||||
this.state.playbackStatus = "playing"
|
this.state.playbackStatus = "playing"
|
||||||
|
|
||||||
instanceObj.audioElement.loop = this.state.playbackMode === "repeat"
|
instanceObj.audioElement.loop = this.state.playbackMode === "repeat"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
instanceObj.audioElement.addEventListener("loadeddata", () => {
|
||||||
|
this.state.loading = false
|
||||||
|
|
||||||
|
console.log("Loaded audio data", instanceObj.audioElement.src)
|
||||||
|
})
|
||||||
|
|
||||||
instanceObj.audioElement.addEventListener("playing", () => {
|
instanceObj.audioElement.addEventListener("playing", () => {
|
||||||
this.state.loading = false
|
this.state.loading = false
|
||||||
|
|
||||||
this.state.playbackStatus = "playing"
|
this.state.playbackStatus = "playing"
|
||||||
|
|
||||||
|
if (this.state.startingNew) {
|
||||||
|
this.state.startingNew = false
|
||||||
|
}
|
||||||
|
|
||||||
if (this.waitUpdateTimeout) {
|
if (this.waitUpdateTimeout) {
|
||||||
clearTimeout(this.waitUpdateTimeout)
|
clearTimeout(this.waitUpdateTimeout)
|
||||||
this.waitUpdateTimeout = null
|
this.waitUpdateTimeout = null
|
||||||
@ -530,7 +557,12 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
instanceObj.audioElement.addEventListener("seeked", () => {
|
instanceObj.audioElement.addEventListener("seeked", () => {
|
||||||
app.eventBus.emit("player.seek.update", instanceObj.audioElement.currentTime)
|
app.eventBus.emit("player.seek.update", instanceObj.audioElement.currentTime)
|
||||||
createCrossfadeInterval()
|
|
||||||
|
if (this.state.syncMode) {
|
||||||
|
useMusicSync("music:player:seek", {
|
||||||
|
position: instanceObj.audioElement.currentTime
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// // detect if the audio is a live stream
|
// // detect if the audio is a live stream
|
||||||
@ -644,7 +676,12 @@ export default class Player extends Core {
|
|||||||
}, { once: true })
|
}, { once: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
async startPlaylist(playlist, startIndex = 0) {
|
async startPlaylist(playlist, startIndex = 0, { sync = false } = {}) {
|
||||||
|
if (this.state.syncModeLocked && !sync) {
|
||||||
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// playlist is an array of audio manifests
|
// playlist is an array of audio manifests
|
||||||
if (!playlist || !Array.isArray(playlist)) {
|
if (!playlist || !Array.isArray(playlist)) {
|
||||||
throw new Error("Playlist is required")
|
throw new Error("Playlist is required")
|
||||||
@ -653,7 +690,7 @@ export default class Player extends Core {
|
|||||||
// !IMPORTANT: abort preloads before destroying current instance
|
// !IMPORTANT: abort preloads before destroying current instance
|
||||||
await this.abortPreloads()
|
await this.abortPreloads()
|
||||||
|
|
||||||
this.destroyCurrentInstance()
|
await this.destroyCurrentInstance()
|
||||||
|
|
||||||
// clear current queue
|
// clear current queue
|
||||||
this.audioQueue = []
|
this.audioQueue = []
|
||||||
@ -676,11 +713,20 @@ export default class Player extends Core {
|
|||||||
this.play(this.audioQueue[0])
|
this.play(this.audioQueue[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(manifest) {
|
async start(manifest, { sync = false } = {}) {
|
||||||
|
if (this.state.syncModeLocked && !sync) {
|
||||||
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.startingNew = true
|
||||||
|
|
||||||
// !IMPORTANT: abort preloads before destroying current instance
|
// !IMPORTANT: abort preloads before destroying current instance
|
||||||
await this.abortPreloads()
|
await this.abortPreloads()
|
||||||
|
|
||||||
this.destroyCurrentInstance()
|
await this.destroyCurrentInstance({
|
||||||
|
sync
|
||||||
|
})
|
||||||
|
|
||||||
const instance = await this.createInstance(manifest)
|
const instance = await this.createInstance(manifest)
|
||||||
|
|
||||||
@ -693,7 +739,12 @@ export default class Player extends Core {
|
|||||||
this.play(this.audioQueue[0])
|
this.play(this.audioQueue[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
next() {
|
next({ sync = false } = {}) {
|
||||||
|
if (this.state.syncModeLocked && !sync) {
|
||||||
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (this.audioQueue.length > 0) {
|
if (this.audioQueue.length > 0) {
|
||||||
// move current audio instance to history
|
// move current audio instance to history
|
||||||
this.audioQueueHistory.push(this.audioQueue.shift())
|
this.audioQueueHistory.push(this.audioQueue.shift())
|
||||||
@ -722,7 +773,12 @@ export default class Player extends Core {
|
|||||||
this.play(this.audioQueue[nextIndex])
|
this.play(this.audioQueue[nextIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
previous() {
|
previous({ sync = false } = {}) {
|
||||||
|
if (this.state.syncModeLocked && !sync) {
|
||||||
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (this.audioQueueHistory.length > 0) {
|
if (this.audioQueueHistory.length > 0) {
|
||||||
// move current audio instance to queue
|
// move current audio instance to queue
|
||||||
this.audioQueue.unshift(this.audioQueueHistory.pop())
|
this.audioQueue.unshift(this.audioQueueHistory.pop())
|
||||||
@ -738,6 +794,48 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async pausePlayback() {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
if (!this.currentAudioInstance) {
|
||||||
|
console.error("No audio instance")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// set gain exponentially
|
||||||
|
this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime(
|
||||||
|
0.0001,
|
||||||
|
this.audioContext.currentTime + (gradualFadeMs / 1000)
|
||||||
|
)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.currentAudioInstance.audioElement.pause()
|
||||||
|
resolve()
|
||||||
|
}, gradualFadeMs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async resumePlayback() {
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
if (!this.currentAudioInstance) {
|
||||||
|
console.error("No audio instance")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure audio elemeto starts from 0 volume
|
||||||
|
this.currentAudioInstance.gainNode.gain.value = 0.0001
|
||||||
|
|
||||||
|
this.currentAudioInstance.audioElement.play().then(() => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
// set gain exponentially
|
||||||
|
this.currentAudioInstance.gainNode.gain.linearRampToValueAtTime(
|
||||||
|
this.state.audioVolume,
|
||||||
|
this.audioContext.currentTime + (gradualFadeMs / 1000)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.destroyCurrentInstance()
|
this.destroyCurrentInstance()
|
||||||
|
|
||||||
@ -800,7 +898,7 @@ export default class Player extends Core {
|
|||||||
return this.state.audioVolume
|
return this.state.audioVolume
|
||||||
}
|
}
|
||||||
|
|
||||||
seek(time) {
|
seek(time, { sync = false } = {}) {
|
||||||
if (!this.currentAudioInstance) {
|
if (!this.currentAudioInstance) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -810,6 +908,11 @@ export default class Player extends Core {
|
|||||||
return this.currentAudioInstance.audioElement.currentTime
|
return this.currentAudioInstance.audioElement.currentTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.syncModeLocked && !sync) {
|
||||||
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// if time is provided, seek to that time
|
// if time is provided, seek to that time
|
||||||
if (typeof time === "number") {
|
if (typeof time === "number") {
|
||||||
this.currentAudioInstance.audioElement.currentTime = time
|
this.currentAudioInstance.audioElement.currentTime = time
|
||||||
@ -842,6 +945,11 @@ export default class Player extends Core {
|
|||||||
}
|
}
|
||||||
|
|
||||||
velocity(to) {
|
velocity(to) {
|
||||||
|
if (this.state.syncModeLocked) {
|
||||||
|
console.warn("Sync mode is locked, cannot do this action")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof to !== "number") {
|
if (typeof to !== "number") {
|
||||||
console.warn("Velocity must be a number")
|
console.warn("Velocity must be a number")
|
||||||
return false
|
return false
|
||||||
@ -866,4 +974,19 @@ export default class Player extends Core {
|
|||||||
|
|
||||||
return this.state.collapsed
|
return this.state.collapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toogleSyncMode(to, lock) {
|
||||||
|
if (typeof to !== "boolean") {
|
||||||
|
console.warn("Sync mode must be a boolean")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.syncMode = to ?? !this.state.syncMode
|
||||||
|
|
||||||
|
this.state.syncModeLocked = lock ?? false
|
||||||
|
|
||||||
|
console.log(`Sync mode is now ${this.state.syncMode ? "enabled" : "disabled"} | Locked: ${this.state.syncModeLocked ? "yes" : "no"}`)
|
||||||
|
|
||||||
|
return this.state.syncMode
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user