mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Fix audio assets
This commit is contained in:
@@ -17,7 +17,6 @@ let pendingDraw = false;
|
||||
let sortedAssetsCache = [];
|
||||
let assetsDirty = true;
|
||||
let renderIntervalId = null;
|
||||
const audioPlaceholder = 'data:image/svg+xml;utf8,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="320" height="80"><rect width="100%" height="100%" fill="#0f172a" rx="8"/><g fill="#22d3ee" transform="translate(20 20)"><circle cx="15" cy="20" r="6"/><rect x="28" y="5" width="12" height="30" rx="2"/><rect x="45" y="10" width="140" height="5" fill="#a5f3fc"/><rect x="45" y="23" width="110" height="5" fill="#a5f3fc"/></g><text x="20" y="70" fill="#e5e7eb" font-family="sans-serif" font-size="14">Audio</text></svg>');
|
||||
const audioUnlockEvents = ['pointerdown', 'keydown', 'touchstart'];
|
||||
|
||||
audioUnlockEvents.forEach((eventName) => {
|
||||
@@ -99,6 +98,12 @@ function handleEvent(event) {
|
||||
assets.delete(event.assetId);
|
||||
clearMedia(event.assetId);
|
||||
renderStates.delete(event.assetId);
|
||||
} else if (event.type === 'PLAY' && event.payload) {
|
||||
const payload = { ...event.payload, zIndex: Math.max(1, event.payload.zIndex ?? 1) };
|
||||
assets.set(payload.id, payload);
|
||||
if (isAudioAsset(payload)) {
|
||||
handleAudioPlay(payload, event.play !== false);
|
||||
}
|
||||
} else if (event.payload && !event.payload.hidden) {
|
||||
const payload = { ...event.payload, zIndex: Math.max(1, event.payload.zIndex ?? 1) };
|
||||
assets.set(payload.id, payload);
|
||||
@@ -169,18 +174,17 @@ function drawAsset(asset) {
|
||||
ctx.translate(renderState.x + halfWidth, renderState.y + halfHeight);
|
||||
ctx.rotate(renderState.rotation * Math.PI / 180);
|
||||
|
||||
const media = ensureMedia(asset);
|
||||
const drawSource = media?.isAnimated ? media.bitmap : media;
|
||||
const ready = isAudioAsset(asset) || isDrawable(media);
|
||||
if (isAudioAsset(asset)) {
|
||||
autoStartAudio(asset);
|
||||
}
|
||||
if (ready && drawSource) {
|
||||
ctx.drawImage(drawSource, -halfWidth, -halfHeight, renderState.width, renderState.height);
|
||||
ctx.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAudioAsset(asset)) {
|
||||
drawAudioIndicators(asset, halfWidth, halfHeight);
|
||||
const media = ensureMedia(asset);
|
||||
const drawSource = media?.isAnimated ? media.bitmap : media;
|
||||
const ready = isDrawable(media);
|
||||
if (ready && drawSource) {
|
||||
ctx.drawImage(drawSource, -halfWidth, -halfHeight, renderState.width, renderState.height);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
@@ -253,59 +257,6 @@ function isGifAsset(asset) {
|
||||
return asset?.mediaType?.toLowerCase() === 'image/gif';
|
||||
}
|
||||
|
||||
function drawAudioIndicators(asset, halfWidth, halfHeight) {
|
||||
const controller = audioControllers.get(asset.id);
|
||||
const isPlaying = controller && !controller.element.paused && !controller.element.ended;
|
||||
const hasDelay = !!(controller && controller.delayTimeout);
|
||||
if (!isPlaying && !hasDelay) {
|
||||
return;
|
||||
}
|
||||
const indicatorSize = 18;
|
||||
const padding = 8;
|
||||
let x = -halfWidth + padding + indicatorSize / 2;
|
||||
const y = -halfHeight + padding + indicatorSize / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.setLineDash([]);
|
||||
if (isPlaying) {
|
||||
ctx.fillStyle = 'rgba(34, 197, 94, 0.9)';
|
||||
ctx.strokeStyle = '#020617';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, indicatorSize / 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#020617';
|
||||
ctx.beginPath();
|
||||
const radius = indicatorSize * 0.22;
|
||||
ctx.moveTo(x - radius, y - radius * 1.1);
|
||||
ctx.lineTo(x + radius * 1.2, y);
|
||||
ctx.lineTo(x - radius, y + radius * 1.1);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
x += indicatorSize + 4;
|
||||
}
|
||||
|
||||
if (hasDelay) {
|
||||
ctx.fillStyle = 'rgba(234, 179, 8, 0.9)';
|
||||
ctx.strokeStyle = '#020617';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, indicatorSize / 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.strokeStyle = '#020617';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x, y - indicatorSize * 0.22);
|
||||
ctx.moveTo(x, y);
|
||||
ctx.lineTo(x + indicatorSize * 0.22, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
function isDrawable(element) {
|
||||
if (!element) {
|
||||
return false;
|
||||
@@ -365,6 +316,7 @@ function ensureAudioController(asset) {
|
||||
element,
|
||||
delayTimeout: null,
|
||||
loopEnabled: false,
|
||||
loopActive: true,
|
||||
delayMs: 0,
|
||||
baseDelayMs: 0
|
||||
};
|
||||
@@ -376,19 +328,24 @@ function ensureAudioController(asset) {
|
||||
|
||||
function applyAudioSettings(controller, asset, resetPosition = false) {
|
||||
controller.loopEnabled = !!asset.audioLoop;
|
||||
controller.loopActive = controller.loopEnabled && controller.loopActive !== false;
|
||||
controller.baseDelayMs = Math.max(0, asset.audioDelayMillis || 0);
|
||||
controller.delayMs = controller.baseDelayMs;
|
||||
const speed = Math.max(0.25, asset.audioSpeed || 1);
|
||||
const pitch = Math.max(0.5, asset.audioPitch || 1);
|
||||
controller.element.playbackRate = speed * pitch;
|
||||
const volume = Math.max(0, Math.min(1, asset.audioVolume ?? 1));
|
||||
controller.element.volume = volume;
|
||||
applyAudioElementSettings(controller.element, asset);
|
||||
if (resetPosition) {
|
||||
controller.element.currentTime = 0;
|
||||
controller.element.pause();
|
||||
}
|
||||
}
|
||||
|
||||
function applyAudioElementSettings(element, asset) {
|
||||
const speed = Math.max(0.25, asset.audioSpeed || 1);
|
||||
const pitch = Math.max(0.5, asset.audioPitch || 1);
|
||||
element.playbackRate = speed * pitch;
|
||||
const volume = Math.max(0, Math.min(1, asset.audioVolume ?? 1));
|
||||
element.volume = volume;
|
||||
}
|
||||
|
||||
function handleAudioEnded(assetId) {
|
||||
const controller = audioControllers.get(assetId);
|
||||
if (!controller) return;
|
||||
@@ -396,7 +353,7 @@ function handleAudioEnded(assetId) {
|
||||
if (controller.delayTimeout) {
|
||||
clearTimeout(controller.delayTimeout);
|
||||
}
|
||||
if (controller.loopEnabled) {
|
||||
if (controller.loopEnabled && controller.loopActive) {
|
||||
controller.delayTimeout = setTimeout(() => {
|
||||
safePlay(controller);
|
||||
}, controller.delayMs);
|
||||
@@ -405,6 +362,19 @@ function handleAudioEnded(assetId) {
|
||||
}
|
||||
}
|
||||
|
||||
function stopAudio(assetId) {
|
||||
const controller = audioControllers.get(assetId);
|
||||
if (!controller) return;
|
||||
if (controller.delayTimeout) {
|
||||
clearTimeout(controller.delayTimeout);
|
||||
}
|
||||
controller.element.pause();
|
||||
controller.element.currentTime = 0;
|
||||
controller.delayTimeout = null;
|
||||
controller.delayMs = controller.baseDelayMs;
|
||||
controller.loopActive = false;
|
||||
}
|
||||
|
||||
function playAudioImmediately(asset) {
|
||||
const controller = ensureAudioController(asset);
|
||||
if (controller.delayTimeout) {
|
||||
@@ -418,11 +388,42 @@ function playAudioImmediately(asset) {
|
||||
controller.delayMs = controller.baseDelayMs ?? originalDelay ?? 0;
|
||||
}
|
||||
|
||||
function playOverlappingAudio(asset) {
|
||||
const temp = new Audio(asset.url);
|
||||
temp.autoplay = true;
|
||||
temp.preload = 'auto';
|
||||
temp.controls = false;
|
||||
applyAudioElementSettings(temp, asset);
|
||||
const controller = { element: temp };
|
||||
temp.onended = () => {
|
||||
temp.remove();
|
||||
};
|
||||
safePlay(controller);
|
||||
}
|
||||
|
||||
function handleAudioPlay(asset, shouldPlay) {
|
||||
const controller = ensureAudioController(asset);
|
||||
controller.loopActive = !!shouldPlay;
|
||||
if (!shouldPlay) {
|
||||
stopAudio(asset.id);
|
||||
return;
|
||||
}
|
||||
if (asset.audioLoop) {
|
||||
controller.delayMs = controller.baseDelayMs;
|
||||
safePlay(controller);
|
||||
} else {
|
||||
playOverlappingAudio(asset);
|
||||
}
|
||||
}
|
||||
|
||||
function autoStartAudio(asset) {
|
||||
if (!isAudioAsset(asset) || asset.hidden) {
|
||||
return;
|
||||
}
|
||||
const controller = ensureAudioController(asset);
|
||||
if (!controller.loopEnabled || !controller.loopActive) {
|
||||
return;
|
||||
}
|
||||
if (!controller.element.paused && !controller.element.ended) {
|
||||
return;
|
||||
}
|
||||
@@ -443,10 +444,8 @@ function ensureMedia(asset) {
|
||||
|
||||
if (isAudioAsset(asset)) {
|
||||
ensureAudioController(asset);
|
||||
const placeholder = new Image();
|
||||
placeholder.src = audioPlaceholder;
|
||||
mediaCache.set(asset.id, placeholder);
|
||||
return placeholder;
|
||||
mediaCache.delete(asset.id);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isGifAsset(asset) && 'ImageDecoder' in window) {
|
||||
|
||||
Reference in New Issue
Block a user