Files
lan-play/web/js/library.js
T
Ultradesu eb0d4fb1c5
Build and Publish / Build and Publish Docker Image (push) Successful in 10m8s
Init
2026-08-05 12:32:52 +01:00

263 lines
9.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
document.addEventListener('alpine:init', () => {
Alpine.data('library', () => ({
currentPath: '', parentPath: null, entries: [], selected: null,
loading: false, playerLoading: false, playerLoadingCompact: false,
error: '', playerController: null, viewMode: 'grid',
infoOpen: false, infoLoading: false, infoError: '', diagnostics: null,
previewFrames: {}, previewUrls: {}, previewStates: {}, previewTimers: {},
previewActive: {}, previewImages: {}, previewGeneration: 0,
init() {
try { this.viewMode = localStorage.getItem('lan-play:view') || 'grid'; } catch (_) {}
this.locationHandler = () => { this.restoreLocation(); };
this.resizeHandler = () => { this.scheduleMarqueeCheck(); };
window.addEventListener('popstate', this.locationHandler);
window.addEventListener('resize', this.resizeHandler);
},
destroy() {
this.stopAllPreviews();
this.cancelPreviewLoads();
window.removeEventListener('popstate', this.locationHandler);
window.removeEventListener('resize', this.resizeHandler);
if (this.marqueeFrame) window.cancelAnimationFrame(this.marqueeFrame);
if (this.playerController) this.playerController.dispose();
},
async restoreLocation() {
const location = window.LanPlayLocation.read();
if (this.playerController) await this.playerController.stop();
this.selected = null;
await this.browse(location.path, false);
if (location.play) {
await this.play({
path: location.play,
name: window.LanPlayLocation.fileName(location.play),
kind: 'media'
}, false);
}
},
async browse(path, updateLocation = true) {
this.stopAllPreviews();
this.cancelPreviewLoads();
this.loading = true;
this.error = '';
try {
const data = await window.LanPlayApi.browse(path);
this.currentPath = data.path;
this.parentPath = data.parent;
this.entries = data.entries;
if (updateLocation) window.LanPlayLocation.write(data.path, null, false);
await this.$nextTick();
this.preloadPreviews(data.entries);
this.scheduleMarqueeCheck();
} catch (error) { this.error = error.message; }
finally { this.loading = false; }
},
async play(entry, updateLocation = true) {
this.stopAllPreviews();
this.enterFullscreen();
this.error = '';
this.selected = entry;
this.playerLoading = true;
if (updateLocation) window.LanPlayLocation.write(this.currentPath, entry.path, false);
await this.$nextTick();
if (!this.playerController) {
this.playerController = new window.LanPlayPlayer(
this.$refs.video,
this.$refs.videoWrapper,
(message) => { this.error = message; },
(loading, compact = false) => {
this.playerLoading = loading;
this.playerLoadingCompact = loading && compact;
},
() => { this.playNext(); }
);
}
this.playerController.setNextAvailable(Boolean(this.nextEntry()));
try { await this.playerController.play(entry); }
catch (error) { this.error = error.message; }
finally {
this.playerLoading = false;
this.playerLoadingCompact = false;
}
},
nextEntry() {
if (!this.selected) return null;
const media = this.entries.filter((entry) => entry.kind === 'media');
const index = media.findIndex((entry) => entry.path === this.selected.path);
return index >= 0 ? media[index + 1] || null : null;
},
async playNext() {
const next = this.nextEntry();
if (next) await this.play(next);
},
mediaTitle() {
if (!this.selected || !this.selected.name) return '';
return this.selected.name.replace(/\.[^.]+$/, '');
},
setViewMode(mode) {
this.viewMode = mode === 'table' ? 'table' : 'grid';
try { localStorage.setItem('lan-play:view', this.viewMode); } catch (_) {}
this.$nextTick(() => this.scheduleMarqueeCheck());
},
scheduleMarqueeCheck() {
if (this.marqueeFrame) window.cancelAnimationFrame(this.marqueeFrame);
this.marqueeFrame = window.requestAnimationFrame(() => {
this.marqueeFrame = null;
document.querySelectorAll('.entry-name').forEach((container) => {
if (container.clientWidth <= 0) return;
const text = container.querySelector('.entry-name-track');
container.classList.toggle('is-overflowing', Boolean(text) && text.scrollWidth > container.clientWidth + 1);
});
});
},
openEntry(entry) {
return entry.kind === 'directory' ? this.browse(entry.path) : this.play(entry);
},
async openInfo() {
this.infoOpen = true;
this.infoLoading = true;
this.infoError = '';
try {
const server = await window.LanPlayApi.info();
let dashVersion = 'Unknown';
try { dashVersion = dashjs.MediaPlayer().create().getVersion(); } catch (_) {}
this.diagnostics = {
...server,
interface: '20',
dashjs: dashVersion,
browser: navigator.userAgent,
display: `${window.screen.width}×${window.screen.height} @ ${window.devicePixelRatio || 1}x`,
fullscreen: Boolean(document.documentElement.requestFullscreen || document.documentElement.webkitRequestFullscreen),
mediaSource: 'MediaSource' in window
};
} catch (error) {
this.infoError = error.message;
} finally {
this.infoLoading = false;
}
},
closeInfo() {
this.infoOpen = false;
},
previewStyle(entry) {
if (this.previewStates[entry.path] !== 'ready') return '';
const url = this.previewUrls[entry.path];
if (!url) return '';
const frame = this.previewFrames[entry.path] || 0;
const column = frame % 5;
const row = Math.floor(frame / 5);
return `background-image:url("${url}");background-position:${column * 25}% ${row * 100}%`;
},
startPreview(entry) {
if (!entry || entry.kind !== 'media') return;
this.previewActive[entry.path] = true;
this.stopPreview(entry);
this.previewActive[entry.path] = true;
if (this.previewStates[entry.path] !== 'ready') return;
this.previewFrames[entry.path] = 0;
this.previewTimers[entry.path] = window.setInterval(() => {
this.previewFrames[entry.path] = ((this.previewFrames[entry.path] || 0) + 1) % 10;
}, 500);
},
stopPreview(entry) {
if (!entry) return;
this.previewActive[entry.path] = false;
const timer = this.previewTimers[entry.path];
if (timer) window.clearInterval(timer);
delete this.previewTimers[entry.path];
this.previewFrames[entry.path] = 0;
},
stopAllPreviews() {
Object.values(this.previewTimers).forEach((timer) => window.clearInterval(timer));
this.previewTimers = {};
this.previewActive = {};
},
preloadPreviews(entries) {
const generation = this.previewGeneration;
entries.filter((entry) => entry.kind === 'media').forEach((entry) => {
const path = entry.path;
const url = `/api/preview?path=${encodeURIComponent(path)}`;
const image = new Image();
this.previewUrls[path] = url;
this.previewStates[path] = 'loading';
this.previewImages[path] = image;
image.onload = () => {
if (generation !== this.previewGeneration) return;
this.previewStates[path] = 'ready';
delete this.previewImages[path];
if (this.previewActive[path]) this.startPreview(entry);
};
image.onerror = () => {
if (generation !== this.previewGeneration) return;
this.previewStates[path] = 'error';
delete this.previewImages[path];
};
image.src = url;
});
},
cancelPreviewLoads() {
this.previewGeneration += 1;
Object.values(this.previewImages).forEach((image) => {
image.onload = null;
image.onerror = null;
image.src = '';
});
this.previewImages = {};
this.previewFrames = {};
this.previewUrls = {};
this.previewStates = {};
},
enterFullscreen() {
if (document.fullscreenElement || document.webkitFullscreenElement) return;
const root = document.documentElement;
const request = root.requestFullscreen || root.webkitRequestFullscreen;
if (request) {
try {
const result = request.call(root);
if (result && result.catch) result.catch(() => {});
} catch (_) {}
}
},
exitFullscreen() {
if (!document.fullscreenElement && !document.webkitFullscreenElement) return;
const exit = document.exitFullscreen || document.webkitExitFullscreen;
if (exit) {
try {
const result = exit.call(document);
if (result && result.catch) result.catch(() => {});
} catch (_) {}
}
},
async closePlayer(updateLocation = true) {
this.exitFullscreen();
this.selected = null;
this.playerLoading = false;
this.playerLoadingCompact = false;
if (updateLocation) window.LanPlayLocation.write(this.currentPath, null, false);
await this.$nextTick();
if (this.playerController) this.playerController.stop();
}
}));
});