Files
KoalaSync/server/media-state.js
T
2026-08-20 17:27:08 +02:00

88 lines
3.2 KiB
JavaScript

import { EVENTS, MAX_MEDIA_TIME } from '../shared/constants.js';
function clampMediaTime(value) {
if (typeof value !== 'number' || !Number.isFinite(value)) return null;
return Math.max(0, Math.min(MAX_MEDIA_TIME, value));
}
export function effectiveMediaPosition(mediaState, now = Date.now()) {
if (!mediaState) return null;
const currentTime = clampMediaTime(mediaState.currentTime);
if (currentTime === null) return null;
if (mediaState.playbackState !== 'playing') return currentTime;
const updatedAt = typeof mediaState.updatedAt === 'number' && Number.isFinite(mediaState.updatedAt)
? mediaState.updatedAt
: now;
return clampMediaTime(currentTime + Math.max(0, now - updatedAt) / 1000);
}
export function snapshotMediaState(mediaState, now = Date.now()) {
if (!mediaState) return null;
const currentTime = effectiveMediaPosition(mediaState, now);
if (currentTime === null
|| !Number.isSafeInteger(mediaState.revision)
|| mediaState.revision < 1
|| (mediaState.playbackState !== 'playing' && mediaState.playbackState !== 'paused')) {
return null;
}
return {
revision: mediaState.revision,
playbackState: mediaState.playbackState,
currentTime,
updatedBy: mediaState.updatedBy
};
}
function commitMediaState(room, playbackState, currentTime, updatedBy, now) {
const normalizedTime = clampMediaTime(currentTime);
if (normalizedTime === null
|| (playbackState !== 'playing' && playbackState !== 'paused')
|| typeof updatedBy !== 'string'
|| !updatedBy) {
return false;
}
room.mediaState = {
revision: (room.mediaState?.revision || 0) + 1,
playbackState,
currentTime: normalizedTime,
updatedAt: now,
updatedBy
};
return true;
}
export function updateMediaStateFromControl(room, eventName, payload, senderPeerId, {
now = Date.now(),
senderPlaybackState = null
} = {}) {
if (!room || !payload || typeof payload !== 'object') return false;
if (eventName === EVENTS.PLAY || eventName === EVENTS.PAUSE) {
const eventPosition = clampMediaTime(payload.currentTime);
const currentTime = eventPosition ?? effectiveMediaPosition(room.mediaState, now);
if (currentTime === null) return false;
return commitMediaState(
room,
eventName === EVENTS.PLAY ? 'playing' : 'paused',
currentTime,
senderPeerId,
now
);
}
if (eventName === EVENTS.SEEK) {
const targetTime = clampMediaTime(payload.targetTime) ?? clampMediaTime(payload.currentTime);
const playbackState = payload.playbackState === 'playing' || payload.playbackState === 'paused'
? payload.playbackState
: (room.mediaState?.playbackState || senderPlaybackState);
if (targetTime === null) return false;
return commitMediaState(room, playbackState, targetTime, senderPeerId, now);
}
return false;
}
export function commitForceSyncMediaState(room, targetTime, senderPeerId, now = Date.now()) {
return commitMediaState(room, 'playing', targetTime, senderPeerId, now);
}