Files
nanocode/audiobook-player/static/js/app.js
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

253 lines
6.4 KiB
JavaScript

/**
* Audiobook Player - App JavaScript
* Minimalist immersive reading experience
*/
// Theme Management
function initTheme() {
const savedTheme = localStorage.getItem('theme') || 'light';
if (savedTheme === 'dark') {
document.body.classList.remove('light-mode');
document.body.classList.add('dark-mode');
updateThemeIcon(true);
}
}
function toggleTheme() {
const isDark = document.body.classList.contains('dark-mode');
if (isDark) {
document.body.classList.remove('dark-mode');
document.body.classList.add('light-mode');
localStorage.setItem('theme', 'light');
} else {
document.body.classList.remove('light-mode');
document.body.classList.add('dark-mode');
localStorage.setItem('theme', 'dark');
}
updateThemeIcon(!isDark);
}
function updateThemeIcon(isDark) {
const toggle = document.querySelector('.theme-toggle');
if (toggle) {
toggle.textContent = isDark ? '☀️' : '🌙';
}
}
// Navigation
function goBack() {
window.history.back();
}
function closePlayer() {
// Override in player page
window.history.back();
}
// Save and Restore Reading Position
function saveReadingPosition(storyId, scrollPosition) {
const positions = JSON.parse(localStorage.getItem('readingPositions') || '{}');
positions[storyId] = {
position: scrollPosition,
timestamp: Date.now()
};
localStorage.setItem('readingPositions', JSON.stringify(positions));
}
function getReadingPosition(storyId) {
const positions = JSON.parse(localStorage.getItem('readingPositions') || '{}');
return positions[storyId]?.position || 0;
}
// Smooth Scroll
function scrollToPosition(position) {
window.scrollTo({
top: position,
behavior: 'smooth'
});
}
// Touch Gestures (for mobile)
let touchStartX = 0;
let touchEndX = 0;
function initTouchGestures() {
document.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
}, { passive: true });
document.addEventListener('touchend', (e) => {
touchEndX = e.changedTouches[0].screenX;
handleSwipe();
}, { passive: true });
}
function handleSwipe() {
const swipeThreshold = 100;
const diff = touchStartX - touchEndX;
// Swipe right (go back)
if (diff < -swipeThreshold) {
// Only if we're not on the home page
if (window.location.pathname !== '/') {
goBack();
}
}
}
// Keyboard Shortcuts
function initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Space: Play/Pause (on player page)
if (e.code === 'Space' && document.getElementById('playBtn')) {
e.preventDefault();
togglePlay();
}
// Escape: Go back
if (e.code === 'Escape') {
if (window.location.pathname !== '/') {
goBack();
}
}
});
}
// Audio Player Enhancements
function initAudioPlayer() {
const audio = document.getElementById('audioPlayer');
if (!audio) return;
// Handle errors
audio.addEventListener('error', (e) => {
console.error('Audio error:', e);
showNotification('播放出错,请重试');
});
// Handle ended
audio.addEventListener('ended', () => {
isPlaying = false;
updatePlayButton();
});
// Handle loading
audio.addEventListener('loadstart', () => {
showNotification('加载中...', 1000);
});
}
// Notification System
function showNotification(message, duration = 3000) {
// Remove existing notification
const existing = document.querySelector('.notification');
if (existing) {
existing.remove();
}
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
notification.style.cssText = `
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: var(--bg-card);
color: var(--text-primary);
padding: 12px 24px;
border-radius: 24px;
box-shadow: 0 4px 16px var(--shadow);
z-index: 9999;
font-size: 0.9rem;
animation: slideUp 0.3s ease;
`;
document.body.appendChild(notification);
if (duration > 0) {
setTimeout(() => {
notification.style.animation = 'slideDown 0.3s ease';
setTimeout(() => notification.remove(), 300);
}, duration);
}
return notification;
}
// Add animation styles
const style = document.createElement('style');
style.textContent = `
@keyframes slideUp {
from {
opacity: 0;
transform: translateX(-50%) translateY(20px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
}
@keyframes slideDown {
from {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
to {
opacity: 0;
transform: translateX(-50%) translateY(20px);
}
}
`;
document.head.appendChild(style);
// Prefetch next chunk (optimization)
function prefetchNextChunk(currentIndex) {
// Implementation for preloading next audio chunk
// This would be implemented in player.html
}
// Performance: Debounce scroll events
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', () => {
initTheme();
initTouchGestures();
initKeyboardShortcuts();
initAudioPlayer();
});
// Handle page visibility change (pause when switching tabs)
document.addEventListener('visibilitychange', () => {
const audio = document.getElementById('audioPlayer');
if (document.hidden && audio && !audio.paused) {
// Optional: pause when switching tabs
// audio.pause();
// isPlaying = false;
// updatePlayButton();
}
});
// Prevent zoom on double tap (mobile)
let lastTouchEnd = 0;
document.addEventListener('touchend', (event) => {
const now = Date.now();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, { passive: false });