- Created complete documentation in docs/ directory - Added PROJECT_OVERVIEW.md with feature highlights and getting started guide - Added ARCHITECTURE.md with system design and technical details - Added SECURITY.md with comprehensive security implementation guide - Added DEVELOPMENT.md with development workflows and best practices - Added DEPLOYMENT.md with production deployment instructions - Added API.md with complete REST API documentation - Added CONTRIBUTING.md with contribution guidelines - Added CHANGELOG.md with version history and migration notes - Reorganized all documentation files into docs/ directory for better organization - Updated README.md with proper documentation links and quick navigation - Enhanced project structure with professional documentation standards
181 lines
6.0 KiB
JavaScript
181 lines
6.0 KiB
JavaScript
/**
|
|
* EasyStream Playlist Enhancements
|
|
* Adds shuffle, loop, and autoplay features to playlists
|
|
*/
|
|
|
|
class PlaylistEnhancer {
|
|
constructor(playlistId, options = {}) {
|
|
this.playlistId = playlistId;
|
|
this.currentIndex = 0;
|
|
this.items = [];
|
|
this.options = {
|
|
shuffle: options.shuffle || false,
|
|
loop: options.loop || false,
|
|
autoplay: options.autoplay !== false,
|
|
...options
|
|
};
|
|
this.originalOrder = [];
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
await this.loadPlaylistItems();
|
|
this.setupControls();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
async loadPlaylistItems() {
|
|
try {
|
|
const response = await fetch(`/api/playlists/${this.playlistId}/items`);
|
|
const data = await response.json();
|
|
this.items = data.items || [];
|
|
this.originalOrder = [...this.items];
|
|
} catch (error) {
|
|
console.error('Error loading playlist:', error);
|
|
}
|
|
}
|
|
|
|
setupControls() {
|
|
const controls = document.createElement('div');
|
|
controls.className = 'playlist-controls';
|
|
controls.innerHTML = `
|
|
<button id="playlistShuffle" class="${this.options.shuffle ? 'active' : ''}" title="Shuffle">
|
|
<svg width="20" height="20" viewBox="0 0 20 20"><path d="M15 4v2h-2.5c-.9 0-1.7.3-2.4.9l-5.2 5.2c-.6.6-1.5.9-2.4.9H0v2h2.5c1.5 0 2.9-.6 4-1.6l5.1-5.1c.3-.3.7-.4 1.1-.4H15v2l4-3-4-3zm0 12v-2h-2.5c-.4 0-.8-.1-1.1-.4L11 13.2l-1.4 1.4 1.4 1.4c1.1 1 2.5 1.6 4 1.6H15v2l4-3-4-3zM6.4 11.1l1.4-1.4L6.4 8.3 5 9.7l1.4 1.4z"/></svg>
|
|
</button>
|
|
<button id="playlistLoop" class="${this.options.loop ? 'active' : ''}" title="Loop">
|
|
<svg width="20" height="20" viewBox="0 0 20 20"><path d="M14 7l-5 5-5-5H8V0h3v7h3zm1 6v3H3V9H0l5-5 5 5H7v4h7v-1h3z"/></svg>
|
|
</button>
|
|
<button id="playlistAutoplay" class="${this.options.autoplay ? 'active' : ''}" title="Autoplay">
|
|
<svg width="20" height="20" viewBox="0 0 20 20"><path d="M5 4l10 6-10 6V4z"/></svg>
|
|
</button>
|
|
`;
|
|
|
|
const playlistContainer = document.querySelector('.playlist-container');
|
|
if (playlistContainer) {
|
|
playlistContainer.insertBefore(controls, playlistContainer.firstChild);
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
document.getElementById('playlistShuffle')?.addEventListener('click', () => this.toggleShuffle());
|
|
document.getElementById('playlistLoop')?.addEventListener('click', () => this.toggleLoop());
|
|
document.getElementById('playlistAutoplay')?.addEventListener('click', () => this.toggleAutoplay());
|
|
|
|
// Listen for video end event
|
|
const video = document.querySelector('video');
|
|
if (video) {
|
|
video.addEventListener('ended', () => this.onVideoEnded());
|
|
}
|
|
}
|
|
|
|
toggleShuffle() {
|
|
this.options.shuffle = !this.options.shuffle;
|
|
document.getElementById('playlistShuffle').classList.toggle('active');
|
|
|
|
if (this.options.shuffle) {
|
|
this.shufflePlaylist();
|
|
} else {
|
|
this.items = [...this.originalOrder];
|
|
}
|
|
|
|
this.savePreferences();
|
|
}
|
|
|
|
toggleLoop() {
|
|
this.options.loop = !this.options.loop;
|
|
document.getElementById('playlistLoop').classList.toggle('active');
|
|
this.savePreferences();
|
|
}
|
|
|
|
toggleAutoplay() {
|
|
this.options.autoplay = !this.options.autoplay;
|
|
document.getElementById('playlistAutoplay').classList.toggle('active');
|
|
this.savePreferences();
|
|
}
|
|
|
|
shufflePlaylist() {
|
|
const currentItem = this.items[this.currentIndex];
|
|
|
|
// Fisher-Yates shuffle
|
|
for (let i = this.items.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[this.items[i], this.items[j]] = [this.items[j], this.items[i]];
|
|
}
|
|
|
|
// Keep current item at current position
|
|
if (currentItem) {
|
|
const newIndex = this.items.indexOf(currentItem);
|
|
[this.items[this.currentIndex], this.items[newIndex]] = [this.items[newIndex], this.items[this.currentIndex]];
|
|
}
|
|
|
|
this.updatePlaylistUI();
|
|
}
|
|
|
|
playNext() {
|
|
if (this.currentIndex < this.items.length - 1) {
|
|
this.currentIndex++;
|
|
} else if (this.options.loop) {
|
|
this.currentIndex = 0;
|
|
} else {
|
|
return; // End of playlist
|
|
}
|
|
|
|
this.playItem(this.currentIndex);
|
|
}
|
|
|
|
playPrevious() {
|
|
if (this.currentIndex > 0) {
|
|
this.currentIndex--;
|
|
} else if (this.options.loop) {
|
|
this.currentIndex = this.items.length - 1;
|
|
} else {
|
|
return; // Start of playlist
|
|
}
|
|
|
|
this.playItem(this.currentIndex);
|
|
}
|
|
|
|
playItem(index) {
|
|
if (index < 0 || index >= this.items.length) return;
|
|
|
|
this.currentIndex = index;
|
|
const item = this.items[index];
|
|
|
|
// Navigate to video
|
|
window.location.href = `/watch?v=${item.file_key}&list=${this.playlistId}`;
|
|
}
|
|
|
|
onVideoEnded() {
|
|
if (this.options.autoplay) {
|
|
setTimeout(() => this.playNext(), 1000); // 1 second delay
|
|
}
|
|
}
|
|
|
|
updatePlaylistUI() {
|
|
const playlistItems = document.querySelectorAll('.playlist-item');
|
|
playlistItems.forEach((item, index) => {
|
|
item.dataset.index = index;
|
|
item.classList.toggle('active', index === this.currentIndex);
|
|
});
|
|
}
|
|
|
|
savePreferences() {
|
|
localStorage.setItem('playlistPreferences', JSON.stringify({
|
|
shuffle: this.options.shuffle,
|
|
loop: this.options.loop,
|
|
autoplay: this.options.autoplay
|
|
}));
|
|
}
|
|
|
|
loadPreferences() {
|
|
const prefs = localStorage.getItem('playlistPreferences');
|
|
if (prefs) {
|
|
const parsed = JSON.parse(prefs);
|
|
this.options = { ...this.options, ...parsed };
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export for global use
|
|
window.PlaylistEnhancer = PlaylistEnhancer;
|