remove cache system, tweak background
This commit is contained in:
parent
b4e69b78a2
commit
d30d6f28d7
BIN
background.png
Normal file
BIN
background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
260
index.html
260
index.html
@ -26,8 +26,8 @@
|
|||||||
background: linear-gradient(145deg, #2a2a2a 0%, #1a1a1a 50%, #0f0f0f 100%);
|
background: linear-gradient(145deg, #2a2a2a 0%, #1a1a1a 50%, #0f0f0f 100%);
|
||||||
border: 8px solid #0a0a0a;
|
border: 8px solid #0a0a0a;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 0 0 2px #3a3a3a,
|
/* inset 0 0 0 2px #3a3a3a, */
|
||||||
inset 0 0 50px rgba(0,0,0,0.9),
|
inset 0 0 10px rgba(0,0,0,0.1),
|
||||||
0 30px 60px rgba(0,0,0,0.8),
|
0 30px 60px rgba(0,0,0,0.8),
|
||||||
5px 5px 0 rgba(0,0,0,0.3);
|
5px 5px 0 rgba(0,0,0,0.3);
|
||||||
/* Extra top padding to accommodate eject/lightning buttons */
|
/* Extra top padding to accommodate eject/lightning buttons */
|
||||||
@ -44,31 +44,15 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-image:
|
background-image: url('background.png');
|
||||||
linear-gradient(45deg, transparent 48%, rgba(255,255,255,0.03) 49%, rgba(255,255,255,0.03) 51%, transparent 52%),
|
background-size: 100%;
|
||||||
linear-gradient(-45deg, transparent 48%, rgba(0,0,0,0.4) 49%, rgba(0,0,0,0.4) 51%, transparent 52%),
|
background-position: center;
|
||||||
repeating-linear-gradient(90deg, transparent, transparent 3px, rgba(0,0,0,0.5) 3px, rgba(0,0,0,0.5) 4px),
|
background-repeat: repeat;
|
||||||
url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><filter id="noise"><feTurbulence type="fractalNoise" baseFrequency="2.5" numOctaves="6" /></filter><rect width="200" height="200" filter="url(%23noise)" opacity="0.4"/></svg>');
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.8;
|
opacity: 0.33;
|
||||||
mix-blend-mode: overlay;
|
mix-blend-mode: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rust and corrosion patches */
|
|
||||||
.player::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-image:
|
|
||||||
radial-gradient(ellipse at 20% 30%, rgba(139, 69, 19, 0.3) 0%, transparent 40%),
|
|
||||||
radial-gradient(ellipse at 80% 70%, rgba(101, 67, 33, 0.2) 0%, transparent 35%),
|
|
||||||
radial-gradient(ellipse at 60% 10%, rgba(120, 81, 45, 0.25) 0%, transparent 30%),
|
|
||||||
radial-gradient(ellipse at 10% 90%, rgba(139, 90, 43, 0.2) 0%, transparent 40%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Eject button - positioned top-left of player */
|
/* Eject button - positioned top-left of player */
|
||||||
.eject-btn {
|
.eject-btn {
|
||||||
@ -1306,6 +1290,7 @@
|
|||||||
function getTrackDuration(url) {
|
function getTrackDuration(url) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tempAudio = new Audio();
|
const tempAudio = new Audio();
|
||||||
|
tempAudio.preload = 'metadata'; // Only fetch headers, not entire file
|
||||||
|
|
||||||
// Set timeout for slow loads
|
// Set timeout for slow loads
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
@ -1326,14 +1311,9 @@
|
|||||||
reject(e);
|
reject(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try to use cached URL if available, otherwise use direct URL
|
// Direct URL - browser handles caching via HTTP headers
|
||||||
TrackCache.getTrack(url).then(blobUrl => {
|
|
||||||
tempAudio.src = blobUrl;
|
|
||||||
}).catch(() => {
|
|
||||||
// Fall back to direct URL
|
|
||||||
tempAudio.src = url;
|
tempAudio.src = url;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
@ -1608,196 +1588,6 @@
|
|||||||
// SOUND EFFECTS MODULE - END
|
// SOUND EFFECTS MODULE - END
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// TRACK CACHE MODULE - START
|
|
||||||
// Caches audio files locally using Cache API
|
|
||||||
// Uses HEAD requests to detect content changes
|
|
||||||
// ========================================
|
|
||||||
const TrackCache = {
|
|
||||||
CACHE_NAME: 'cassette-player-audio-v1',
|
|
||||||
META_KEY: 'track-cache-meta',
|
|
||||||
// Fallback cache duration (1 hour) if no ETag/Last-Modified
|
|
||||||
FALLBACK_MAX_AGE: 60 * 60 * 1000,
|
|
||||||
cache: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the cache - opens Cache API storage
|
|
||||||
* @returns {Promise<Cache>}
|
|
||||||
*/
|
|
||||||
async init() {
|
|
||||||
if (!this.cache) {
|
|
||||||
this.cache = await caches.open(this.CACHE_NAME);
|
|
||||||
}
|
|
||||||
return this.cache;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get metadata for all cached tracks from localStorage
|
|
||||||
* @returns {Object} Map of URL -> { etag, lastModified, cachedAt }
|
|
||||||
*/
|
|
||||||
getMetadata() {
|
|
||||||
try {
|
|
||||||
const data = localStorage.getItem(this.META_KEY);
|
|
||||||
return data ? JSON.parse(data) : {};
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('TrackCache: Failed to read metadata', e);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save metadata for a track
|
|
||||||
* @param {string} url - Track URL
|
|
||||||
* @param {Object} meta - { etag, lastModified, cachedAt }
|
|
||||||
*/
|
|
||||||
saveMetadata(url, meta) {
|
|
||||||
try {
|
|
||||||
const allMeta = this.getMetadata();
|
|
||||||
allMeta[url] = meta;
|
|
||||||
localStorage.setItem(this.META_KEY, JSON.stringify(allMeta));
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('TrackCache: Failed to save metadata', e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if cached content is stale using HEAD request
|
|
||||||
* @param {string} url - Track URL
|
|
||||||
* @returns {Promise<boolean>} True if content has changed, false if still valid
|
|
||||||
*/
|
|
||||||
async isStale(url) {
|
|
||||||
const meta = this.getMetadata()[url];
|
|
||||||
if (!meta) return true; // No metadata = treat as stale
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, { method: 'HEAD' });
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
// HEAD failed, assume cached version is still valid
|
|
||||||
console.log('TrackCache: HEAD request failed, using cached version');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newEtag = response.headers.get('ETag');
|
|
||||||
const newLastModified = response.headers.get('Last-Modified');
|
|
||||||
|
|
||||||
// Check ETag first (most reliable)
|
|
||||||
if (newEtag && meta.etag) {
|
|
||||||
const isChanged = newEtag !== meta.etag;
|
|
||||||
if (isChanged) {
|
|
||||||
console.log(`TrackCache: ETag changed for ${url}`);
|
|
||||||
}
|
|
||||||
return isChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to Last-Modified
|
|
||||||
if (newLastModified && meta.lastModified) {
|
|
||||||
const isChanged = newLastModified !== meta.lastModified;
|
|
||||||
if (isChanged) {
|
|
||||||
console.log(`TrackCache: Last-Modified changed for ${url}`);
|
|
||||||
}
|
|
||||||
return isChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No ETag or Last-Modified, use time-based fallback
|
|
||||||
const age = Date.now() - meta.cachedAt;
|
|
||||||
const isExpired = age > this.FALLBACK_MAX_AGE;
|
|
||||||
if (isExpired) {
|
|
||||||
console.log(`TrackCache: Cache expired (age: ${Math.round(age/1000)}s) for ${url}`);
|
|
||||||
}
|
|
||||||
return isExpired;
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
// Network error or CORS issue - assume cached version is valid
|
|
||||||
console.log('TrackCache: HEAD request error, using cached version', e.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch track from network and store in cache
|
|
||||||
* @param {string} url - Track URL
|
|
||||||
* @returns {Promise<string>} Blob URL for the audio
|
|
||||||
*/
|
|
||||||
async fetchAndCache(url) {
|
|
||||||
console.log(`TrackCache: Fetching ${url}`);
|
|
||||||
|
|
||||||
const response = await fetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch track: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract cache validation headers
|
|
||||||
const etag = response.headers.get('ETag');
|
|
||||||
const lastModified = response.headers.get('Last-Modified');
|
|
||||||
|
|
||||||
// Clone response before consuming it
|
|
||||||
const responseToCache = response.clone();
|
|
||||||
|
|
||||||
// Store in cache
|
|
||||||
await this.init();
|
|
||||||
await this.cache.put(url, responseToCache);
|
|
||||||
|
|
||||||
// Save metadata
|
|
||||||
this.saveMetadata(url, {
|
|
||||||
etag: etag,
|
|
||||||
lastModified: lastModified,
|
|
||||||
cachedAt: Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`TrackCache: Cached ${url} (ETag: ${etag || 'none'}, Last-Modified: ${lastModified || 'none'})`);
|
|
||||||
|
|
||||||
// Return blob URL for playback
|
|
||||||
const blob = await response.blob();
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get track - main entry point
|
|
||||||
* Checks cache, validates with HEAD, returns blob URL
|
|
||||||
* @param {string} url - Track URL
|
|
||||||
* @returns {Promise<string>} Blob URL for the audio
|
|
||||||
*/
|
|
||||||
async getTrack(url) {
|
|
||||||
await this.init();
|
|
||||||
|
|
||||||
// Check if we have it cached
|
|
||||||
const cachedResponse = await this.cache.match(url);
|
|
||||||
|
|
||||||
if (cachedResponse) {
|
|
||||||
// Check if content has changed
|
|
||||||
const stale = await this.isStale(url);
|
|
||||||
|
|
||||||
if (!stale) {
|
|
||||||
console.log(`TrackCache: Using cached version of ${url}`);
|
|
||||||
const blob = await cachedResponse.blob();
|
|
||||||
return URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Content changed, fetch new version
|
|
||||||
console.log(`TrackCache: Content changed, re-fetching ${url}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not cached or stale, fetch from network
|
|
||||||
return await this.fetchAndCache(url);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all cached tracks
|
|
||||||
* Useful for debugging or freeing storage
|
|
||||||
*/
|
|
||||||
async clearAll() {
|
|
||||||
await caches.delete(this.CACHE_NAME);
|
|
||||||
localStorage.removeItem(this.META_KEY);
|
|
||||||
this.cache = null;
|
|
||||||
console.log('TrackCache: Cleared all cached tracks');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// ========================================
|
|
||||||
// TRACK CACHE MODULE - END
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// BUTTON CLICK SOUNDS - Add to all buttons
|
// BUTTON CLICK SOUNDS - Add to all buttons
|
||||||
// Uses mousedown for immediate tactile feedback
|
// Uses mousedown for immediate tactile feedback
|
||||||
@ -1889,18 +1679,9 @@
|
|||||||
// Reset scroll position for new track
|
// Reset scroll position for new track
|
||||||
resetTitleScroll();
|
resetTitleScroll();
|
||||||
|
|
||||||
try {
|
// Direct URL - browser handles caching via HTTP headers
|
||||||
// Get track from cache or network
|
|
||||||
const blobUrl = await TrackCache.getTrack(playlist[index].url);
|
|
||||||
audio.src = blobUrl;
|
|
||||||
} catch (e) {
|
|
||||||
// Caching may fail due to CORS if audio is hosted on different origin
|
|
||||||
// without Access-Control-Allow-Origin headers
|
|
||||||
console.warn('TrackCache: Caching unavailable (likely CORS), using direct URL');
|
|
||||||
// Fall back to direct URL - audio element can still play cross-origin
|
|
||||||
audio.src = playlist[index].url;
|
audio.src = playlist[index].url;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Initial track load (async, no await needed for initial load)
|
// Initial track load (async, no await needed for initial load)
|
||||||
loadTrack(0);
|
loadTrack(0);
|
||||||
@ -1987,12 +1768,8 @@
|
|||||||
currentTrack = 0;
|
currentTrack = 0;
|
||||||
trackNameInner.textContent = playlist[0].name;
|
trackNameInner.textContent = playlist[0].name;
|
||||||
|
|
||||||
try {
|
// Direct URL - browser handles caching via HTTP headers
|
||||||
const blobUrl = await TrackCache.getTrack(playlist[0].url);
|
|
||||||
audio.src = blobUrl;
|
|
||||||
} catch (e) {
|
|
||||||
audio.src = playlist[0].url;
|
audio.src = playlist[0].url;
|
||||||
}
|
|
||||||
audio.load();
|
audio.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2123,12 +1900,8 @@
|
|||||||
trackNameInner.textContent = playlist[trackIndex].name;
|
trackNameInner.textContent = playlist[trackIndex].name;
|
||||||
resetTitleScroll();
|
resetTitleScroll();
|
||||||
|
|
||||||
try {
|
// Direct URL - browser handles caching via HTTP headers
|
||||||
const blobUrl = await TrackCache.getTrack(playlist[trackIndex].url);
|
|
||||||
audio.src = blobUrl;
|
|
||||||
} catch (e) {
|
|
||||||
audio.src = playlist[trackIndex].url;
|
audio.src = playlist[trackIndex].url;
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
audio.onloadedmetadata = () => {
|
audio.onloadedmetadata = () => {
|
||||||
@ -2256,13 +2029,8 @@
|
|||||||
trackNameInner.textContent = playlist[currentTrack].name;
|
trackNameInner.textContent = playlist[currentTrack].name;
|
||||||
resetTitleScroll();
|
resetTitleScroll();
|
||||||
|
|
||||||
try {
|
// Direct URL - browser handles caching via HTTP headers
|
||||||
const blobUrl = await TrackCache.getTrack(playlist[currentTrack].url);
|
|
||||||
audio.src = blobUrl;
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('TrackCache: Caching unavailable, using direct URL');
|
|
||||||
audio.src = playlist[currentTrack].url;
|
audio.src = playlist[currentTrack].url;
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
audio.oncanplay = function() {
|
audio.oncanplay = function() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user