253 lines
6.4 KiB
JavaScript
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 });
|