This commit is contained in:
parent
58392b7d6a
commit
381e8b79b0
17 changed files with 282 additions and 82 deletions
|
|
@ -172,14 +172,33 @@
|
|||
debug: false,
|
||||
enableWorker: true,
|
||||
lowLatencyMode: true,
|
||||
backBufferLength: 90,
|
||||
// Increased retry settings for LLHLS resilience
|
||||
fragLoadingMaxRetry: 6,
|
||||
fragLoadingRetryDelay: 1000,
|
||||
manifestLoadingMaxRetry: 4,
|
||||
levelLoadingMaxRetry: 4,
|
||||
maxBufferLength: 30,
|
||||
maxBufferHole: 0.5,
|
||||
|
||||
// Buffer Management - optimized for low latency
|
||||
maxBufferLength: 4,
|
||||
maxMaxBufferLength: 6,
|
||||
backBufferLength: 10,
|
||||
|
||||
// Live Edge Sync - key for reducing latency
|
||||
liveSyncDurationCount: 3,
|
||||
liveMaxLatencyDurationCount: 5,
|
||||
liveSyncDuration: 3,
|
||||
liveMaxLatencyDuration: 6,
|
||||
maxLiveSyncPlaybackRate: 1.5,
|
||||
|
||||
// Faster Recovery - reduced retry delays
|
||||
fragLoadingMaxRetry: 4,
|
||||
fragLoadingRetryDelay: 200,
|
||||
fragLoadingMaxRetryTimeout: 4000,
|
||||
manifestLoadingMaxRetry: 3,
|
||||
manifestLoadingRetryDelay: 200,
|
||||
levelLoadingMaxRetry: 3,
|
||||
levelLoadingRetryDelay: 200,
|
||||
|
||||
// Startup optimization
|
||||
startFragPrefetch: true,
|
||||
testBandwidth: false,
|
||||
maxBufferHole: 0.1,
|
||||
|
||||
xhrSetup: function(xhr, url) {
|
||||
let finalUrl = url;
|
||||
|
||||
|
|
|
|||
|
|
@ -161,14 +161,33 @@
|
|||
debug: false,
|
||||
enableWorker: true,
|
||||
lowLatencyMode: true,
|
||||
backBufferLength: 30,
|
||||
// Increased retry settings for LLHLS resilience
|
||||
fragLoadingMaxRetry: 6,
|
||||
fragLoadingRetryDelay: 1000,
|
||||
manifestLoadingMaxRetry: 4,
|
||||
levelLoadingMaxRetry: 4,
|
||||
maxBufferLength: 30,
|
||||
maxBufferHole: 0.5,
|
||||
|
||||
// Buffer Management - optimized for low latency
|
||||
maxBufferLength: 4,
|
||||
maxMaxBufferLength: 6,
|
||||
backBufferLength: 10,
|
||||
|
||||
// Live Edge Sync - key for reducing latency
|
||||
liveSyncDurationCount: 3,
|
||||
liveMaxLatencyDurationCount: 5,
|
||||
liveSyncDuration: 3,
|
||||
liveMaxLatencyDuration: 6,
|
||||
maxLiveSyncPlaybackRate: 1.5,
|
||||
|
||||
// Faster Recovery - reduced retry delays
|
||||
fragLoadingMaxRetry: 4,
|
||||
fragLoadingRetryDelay: 200,
|
||||
fragLoadingMaxRetryTimeout: 4000,
|
||||
manifestLoadingMaxRetry: 3,
|
||||
manifestLoadingRetryDelay: 200,
|
||||
levelLoadingMaxRetry: 3,
|
||||
levelLoadingRetryDelay: 200,
|
||||
|
||||
// Startup optimization
|
||||
startFragPrefetch: true,
|
||||
testBandwidth: false,
|
||||
maxBufferHole: 0.1,
|
||||
|
||||
xhrSetup: function(xhr, url) {
|
||||
let finalUrl = url;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@
|
|||
/** @type {string} Waveform color */
|
||||
export let color = '#ec4899';
|
||||
|
||||
/** @type {string} Color for the played portion */
|
||||
export let playedColor = '#f472b6';
|
||||
|
||||
/** @type {number} Opacity of the waveform (0-1) */
|
||||
export let opacity = 0.15;
|
||||
|
||||
|
|
@ -32,9 +35,8 @@
|
|||
let peaks = [];
|
||||
let loading = true;
|
||||
|
||||
// Calculate scroll position based on playback
|
||||
$: progress = duration > 0 ? currentTime / duration : 0;
|
||||
$: translateX = isCurrentTrack && isPlaying ? -(progress * 50) : 0;
|
||||
// Calculate progress percentage (0-100)
|
||||
$: progress = duration > 0 ? (currentTime / duration) * 100 : 0;
|
||||
|
||||
async function fetchWaveform() {
|
||||
if (!audioId && !waveformPath) {
|
||||
|
|
@ -91,34 +93,52 @@
|
|||
>
|
||||
<svg
|
||||
class="waveform-svg"
|
||||
viewBox="0 0 400 100"
|
||||
viewBox="0 0 1000 100"
|
||||
preserveAspectRatio="none"
|
||||
style="transform: translateX({translateX}%);"
|
||||
>
|
||||
<!-- Mirror waveform: bars extend up and down from center -->
|
||||
{#each peaks as peak, i}
|
||||
{@const barWidth = 400 / peaks.length}
|
||||
{@const barWidth = 1000 / peaks.length}
|
||||
{@const barHeight = peak * 45}
|
||||
{@const x = i * barWidth}
|
||||
{@const barProgress = (x / 1000) * 100}
|
||||
{@const isPlayed = isCurrentTrack && barProgress < progress}
|
||||
{@const barColor = isPlayed ? playedColor : color}
|
||||
{@const barOpacity = isPlayed ? 0.6 : 1}
|
||||
<!-- Top bar (from center going up) -->
|
||||
<rect
|
||||
x={x}
|
||||
y={50 - barHeight}
|
||||
width={barWidth * 0.8}
|
||||
width={barWidth * 0.7}
|
||||
height={barHeight}
|
||||
fill={color}
|
||||
rx="1"
|
||||
fill={barColor}
|
||||
opacity={barOpacity}
|
||||
rx="0.5"
|
||||
/>
|
||||
<!-- Bottom bar (from center going down) -->
|
||||
<rect
|
||||
x={x}
|
||||
y={50}
|
||||
width={barWidth * 0.8}
|
||||
width={barWidth * 0.7}
|
||||
height={barHeight}
|
||||
fill={color}
|
||||
rx="1"
|
||||
fill={barColor}
|
||||
opacity={barOpacity}
|
||||
rx="0.5"
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<!-- Playhead line -->
|
||||
{#if isCurrentTrack && progress > 0}
|
||||
<line
|
||||
x1={progress * 10}
|
||||
y1="5"
|
||||
x2={progress * 10}
|
||||
y2="95"
|
||||
stroke={playedColor}
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -133,8 +153,7 @@
|
|||
}
|
||||
|
||||
.waveform-svg {
|
||||
width: 200%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
let showCalendar = false;
|
||||
let calendarDate = new Date();
|
||||
let timeInterval;
|
||||
let calendarContainerEl;
|
||||
|
||||
// Tab navigation - includes audio, ebooks, games, and treasury
|
||||
let activeTab = 'terminal';
|
||||
|
|
@ -308,7 +309,11 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={() => showCalendar = false} />
|
||||
<svelte:window on:click={(e) => {
|
||||
if (calendarContainerEl && !calendarContainerEl.contains(e.target)) {
|
||||
showCalendar = false;
|
||||
}
|
||||
}} />
|
||||
|
||||
{#if isOpen && $isAuthenticated}
|
||||
<div
|
||||
|
|
@ -325,8 +330,8 @@
|
|||
<div class="terminal-header" on:mousedown={!isDocked ? startDrag : null}>
|
||||
<TerminalTabBar {tabs} {activeTab} on:tabChange={handleTabChange} />
|
||||
<div class="header-right">
|
||||
<div class="datetime-container">
|
||||
<button class="datetime-button" on:click|stopPropagation={toggleCalendar} title="Show calendar">
|
||||
<div class="datetime-container" bind:this={calendarContainerEl}>
|
||||
<button class="datetime-button" on:click={toggleCalendar} title="Show calendar">
|
||||
<span class="datetime-date">{formatDate(currentTime)}</span>
|
||||
<span class="datetime-time">{formatTime(currentTime)}</span>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,24 @@
|
|||
let hoveredFace = null;
|
||||
let hoveredPixel = null;
|
||||
|
||||
// Validate pixel position (matches backend validation)
|
||||
function isValidPixelPosition(faceId, x, y) {
|
||||
// X and Y must be within face bounds
|
||||
if (x < 0 || x >= FACE_SIZE || y < 0 || y >= FACE_SIZE) return false;
|
||||
|
||||
// For triangular faces (1-4), validate within triangle bounds
|
||||
if (faceId > 0) {
|
||||
// Triangle with base at bottom (y=199), apex at top (y=0)
|
||||
const halfWidth = Math.floor((y + 1) / 2);
|
||||
const center = Math.floor(FACE_SIZE / 2);
|
||||
const minX = center - halfWidth;
|
||||
const maxX = center + halfWidth;
|
||||
if (x < minX || x > maxX) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unsubscribe function for store
|
||||
let unsubscribePyramid;
|
||||
|
||||
|
|
@ -274,11 +292,18 @@
|
|||
const x = Math.floor(uv.x * FACE_SIZE);
|
||||
const y = Math.floor((1 - uv.y) * FACE_SIZE);
|
||||
|
||||
hoveredFace = faceId;
|
||||
hoveredPixel = { faceId, x, y };
|
||||
// Only set hover if position is valid (especially for triangular faces)
|
||||
if (isValidPixelPosition(faceId, x, y)) {
|
||||
hoveredFace = faceId;
|
||||
hoveredPixel = { faceId, x, y };
|
||||
|
||||
dispatch('hover', { faceId, x, y });
|
||||
pyramidStore.setHoveredPixel({ faceId, x, y });
|
||||
dispatch('hover', { faceId, x, y });
|
||||
pyramidStore.setHoveredPixel({ faceId, x, y });
|
||||
} else {
|
||||
hoveredFace = null;
|
||||
hoveredPixel = null;
|
||||
pyramidStore.setHoveredPixel(null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hoveredFace = null;
|
||||
|
|
|
|||
|
|
@ -426,20 +426,42 @@
|
|||
sources: sources,
|
||||
webrtcConfig: {
|
||||
timeoutMaxRetry: 4,
|
||||
connectionTimeout: 10000
|
||||
connectionTimeout: 10000,
|
||||
iceServers: [
|
||||
{ urls: 'stun:stun.l.google.com:19302' }
|
||||
]
|
||||
},
|
||||
hlsConfig: {
|
||||
debug: false,
|
||||
enableWorker: true,
|
||||
lowLatencyMode: true,
|
||||
backBufferLength: 90,
|
||||
// Increased retry settings for LLHLS resilience
|
||||
fragLoadingMaxRetry: 6,
|
||||
fragLoadingRetryDelay: 1000,
|
||||
manifestLoadingMaxRetry: 4,
|
||||
levelLoadingMaxRetry: 4,
|
||||
maxBufferLength: 30,
|
||||
maxBufferHole: 0.5,
|
||||
|
||||
// Buffer Management - optimized for low latency
|
||||
maxBufferLength: 4,
|
||||
maxMaxBufferLength: 6,
|
||||
backBufferLength: 10,
|
||||
|
||||
// Live Edge Sync - key for reducing latency
|
||||
liveSyncDurationCount: 3,
|
||||
liveMaxLatencyDurationCount: 5,
|
||||
liveSyncDuration: 3,
|
||||
liveMaxLatencyDuration: 6,
|
||||
maxLiveSyncPlaybackRate: 1.5,
|
||||
|
||||
// Faster Recovery - reduced retry delays
|
||||
fragLoadingMaxRetry: 4,
|
||||
fragLoadingRetryDelay: 200,
|
||||
fragLoadingMaxRetryTimeout: 4000,
|
||||
manifestLoadingMaxRetry: 3,
|
||||
manifestLoadingRetryDelay: 200,
|
||||
levelLoadingMaxRetry: 3,
|
||||
levelLoadingRetryDelay: 200,
|
||||
|
||||
// Startup optimization
|
||||
startFragPrefetch: true,
|
||||
testBandwidth: false,
|
||||
maxBufferHole: 0.1,
|
||||
|
||||
xhrSetup: function(xhr, url) {
|
||||
let finalUrl = url;
|
||||
|
||||
|
|
|
|||
|
|
@ -39,13 +39,10 @@
|
|||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
function isInPlaylist(audioId) {
|
||||
return $audioPlaylist.queue.some(t => t.id === audioId);
|
||||
}
|
||||
|
||||
function isCurrentlyPlaying(audioId) {
|
||||
return $currentTrack?.id === audioId && $audioPlaylist.isPlaying;
|
||||
}
|
||||
// Reactive sets for tracking playlist state - these update when stores change
|
||||
$: playlistIds = new Set($audioPlaylist.queue.map(t => t.id));
|
||||
$: currentPlayingId = $audioPlaylist.isPlaying ? $currentTrack?.id : null;
|
||||
$: currentTrackId = $currentTrack?.id;
|
||||
|
||||
function handlePlayClick(audio) {
|
||||
if ($currentTrack?.id === audio.id) {
|
||||
|
|
@ -91,7 +88,7 @@
|
|||
}
|
||||
|
||||
function togglePlaylist(audio) {
|
||||
if (isInPlaylist(audio.id)) {
|
||||
if (playlistIds.has(audio.id)) {
|
||||
audioPlaylist.removeTrack(audio.id);
|
||||
} else {
|
||||
audioPlaylist.addTrack({
|
||||
|
|
@ -135,7 +132,7 @@
|
|||
|
||||
function addAllToPlaylist() {
|
||||
audioFiles.forEach(audio => {
|
||||
if (!isInPlaylist(audio.id)) {
|
||||
if (!playlistIds.has(audio.id)) {
|
||||
addToPlaylist(audio);
|
||||
}
|
||||
});
|
||||
|
|
@ -521,7 +518,7 @@
|
|||
isPlaying={$audioPlaylist.isPlaying}
|
||||
currentTime={$audioPlaylist.currentTime}
|
||||
duration={$audioPlaylist.duration}
|
||||
isCurrentTrack={$currentTrack?.id === audio.id}
|
||||
isCurrentTrack={currentTrackId === audio.id}
|
||||
/>
|
||||
{/if}
|
||||
<span class="audio-number">{index + 1}</span>
|
||||
|
|
@ -546,19 +543,19 @@
|
|||
<div class="audio-actions">
|
||||
<button
|
||||
class="action-btn play"
|
||||
class:playing={isCurrentlyPlaying(audio.id)}
|
||||
class:playing={currentPlayingId === audio.id}
|
||||
on:click={() => handlePlayClick(audio)}
|
||||
title={isCurrentlyPlaying(audio.id) ? 'Pause' : 'Play now'}
|
||||
title={currentPlayingId === audio.id ? 'Pause' : 'Play now'}
|
||||
>
|
||||
{isCurrentlyPlaying(audio.id) ? '▮▮' : '▶'}
|
||||
{currentPlayingId === audio.id ? '▮▮' : '▶'}
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
class:added={isInPlaylist(audio.id)}
|
||||
class:added={playlistIds.has(audio.id)}
|
||||
on:click={() => togglePlaylist(audio)}
|
||||
title={isInPlaylist(audio.id) ? 'Remove from playlist' : 'Add to playlist'}
|
||||
title={playlistIds.has(audio.id) ? 'Remove from playlist' : 'Add to playlist'}
|
||||
>
|
||||
{isInPlaylist(audio.id) ? '✓' : '+'}
|
||||
{playlistIds.has(audio.id) ? '✓' : '+'}
|
||||
</button>
|
||||
{#if $isAuthenticated}
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
let showCalendar = false;
|
||||
let calendarDate = new Date();
|
||||
let timeInterval;
|
||||
let calendarContainerEl;
|
||||
|
||||
const tabs = [
|
||||
{ id: 'terminal', label: 'Terminal' },
|
||||
|
|
@ -196,7 +197,11 @@
|
|||
});
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={() => showCalendar = false} />
|
||||
<svelte:window on:click={(e) => {
|
||||
if (calendarContainerEl && !calendarContainerEl.contains(e.target)) {
|
||||
showCalendar = false;
|
||||
}
|
||||
}} />
|
||||
|
||||
<svelte:head>
|
||||
<title>{$siteSettings.site_title} - Terminal</title>
|
||||
|
|
@ -219,8 +224,8 @@
|
|||
<div class="popout-header">
|
||||
<TerminalTabBar {tabs} {activeTab} on:tabChange={handleTabChange} />
|
||||
<div class="header-right">
|
||||
<div class="datetime-container">
|
||||
<button class="datetime-button" on:click|stopPropagation={toggleCalendar} title="Show calendar">
|
||||
<div class="datetime-container" bind:this={calendarContainerEl}>
|
||||
<button class="datetime-button" on:click={toggleCalendar} title="Show calendar">
|
||||
<span class="datetime-date">{formatDate(currentTime)}</span>
|
||||
<span class="datetime-time">{formatTime(currentTime)}</span>
|
||||
</button>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue