You've already forked wp-fedistream
WP FediStream - Stream music over ActivityPub Features: - Custom post types: Artist, Album, Track, Playlist - Custom taxonomies: Genre, Mood, License - User roles: Artist, Label - Admin dashboard with statistics - Frontend templates and shortcodes - Audio player with queue management - ActivityPub integration with actor support - WooCommerce product types for albums/tracks - User library with favorites and history - Notification system (in-app and email) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
840 lines
29 KiB
JavaScript
840 lines
29 KiB
JavaScript
/**
|
|
* WP FediStream - Frontend Scripts
|
|
*
|
|
* @package WP_FediStream
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* FediStream Audio Player
|
|
* Handles audio playback, queue management, and UI updates
|
|
*/
|
|
class FediStreamPlayer {
|
|
constructor() {
|
|
this.audio = new Audio();
|
|
this.queue = [];
|
|
this.currentIndex = -1;
|
|
this.isPlaying = false;
|
|
this.isShuffle = false;
|
|
this.repeatMode = 'none'; // none, all, one
|
|
this.volume = 0.8;
|
|
this.originalQueue = [];
|
|
|
|
this.init();
|
|
}
|
|
|
|
/**
|
|
* Initialize the player
|
|
*/
|
|
init() {
|
|
this.audio.volume = this.volume;
|
|
this.bindAudioEvents();
|
|
this.bindUIEvents();
|
|
this.initNowPlayingWidgets();
|
|
this.loadVolumeFromStorage();
|
|
}
|
|
|
|
/**
|
|
* Bind audio element events
|
|
*/
|
|
bindAudioEvents() {
|
|
this.audio.addEventListener('timeupdate', () => this.onTimeUpdate());
|
|
this.audio.addEventListener('loadedmetadata', () => this.onLoadedMetadata());
|
|
this.audio.addEventListener('ended', () => this.onEnded());
|
|
this.audio.addEventListener('play', () => this.onPlay());
|
|
this.audio.addEventListener('pause', () => this.onPause());
|
|
this.audio.addEventListener('error', (e) => this.onError(e));
|
|
this.audio.addEventListener('waiting', () => this.onWaiting());
|
|
this.audio.addEventListener('canplay', () => this.onCanPlay());
|
|
}
|
|
|
|
/**
|
|
* Bind UI event listeners
|
|
*/
|
|
bindUIEvents() {
|
|
// Play buttons on cards and tracklists
|
|
document.addEventListener('click', (e) => {
|
|
const playBtn = e.target.closest('[data-track-id]');
|
|
if (playBtn && (playBtn.classList.contains('fedistream-card__play-overlay') ||
|
|
playBtn.classList.contains('fedistream-tracklist__play') ||
|
|
playBtn.classList.contains('fedistream-widget__play') ||
|
|
playBtn.classList.contains('fedistream-single__play-overlay') ||
|
|
playBtn.classList.contains('fedistream-track__play-overlay'))) {
|
|
e.preventDefault();
|
|
const trackId = playBtn.dataset.trackId;
|
|
this.playTrackById(trackId);
|
|
}
|
|
|
|
// Tracklist item click (not on play button or link)
|
|
const tracklistItem = e.target.closest('.fedistream-tracklist__item');
|
|
if (tracklistItem && !e.target.closest('a') && !e.target.closest('button')) {
|
|
e.preventDefault();
|
|
const trackId = tracklistItem.dataset.trackId;
|
|
if (trackId) {
|
|
this.playTrackById(trackId);
|
|
}
|
|
}
|
|
|
|
// Play all button
|
|
if (e.target.closest('.fedistream-btn--play-all')) {
|
|
e.preventDefault();
|
|
const btn = e.target.closest('.fedistream-btn--play-all');
|
|
const albumId = btn.dataset.albumId;
|
|
const playlistId = btn.dataset.playlistId;
|
|
if (albumId) {
|
|
this.playAlbum(albumId);
|
|
} else if (playlistId) {
|
|
this.playPlaylist(playlistId);
|
|
}
|
|
}
|
|
|
|
// Shuffle button
|
|
if (e.target.closest('.fedistream-btn--shuffle')) {
|
|
e.preventDefault();
|
|
const btn = e.target.closest('.fedistream-btn--shuffle');
|
|
const albumId = btn.dataset.albumId;
|
|
const playlistId = btn.dataset.playlistId;
|
|
if (albumId) {
|
|
this.playAlbum(albumId, true);
|
|
} else if (playlistId) {
|
|
this.playPlaylist(playlistId, true);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Player controls
|
|
document.addEventListener('click', (e) => {
|
|
// Main play/pause button
|
|
if (e.target.closest('.fedistream-player__btn--play')) {
|
|
e.preventDefault();
|
|
this.togglePlayPause();
|
|
}
|
|
|
|
// Previous button
|
|
if (e.target.closest('.fedistream-player__btn--prev')) {
|
|
e.preventDefault();
|
|
this.previous();
|
|
}
|
|
|
|
// Next button
|
|
if (e.target.closest('.fedistream-player__btn--next')) {
|
|
e.preventDefault();
|
|
this.next();
|
|
}
|
|
|
|
// Shuffle button
|
|
if (e.target.closest('.fedistream-player__btn--shuffle')) {
|
|
e.preventDefault();
|
|
this.toggleShuffle();
|
|
e.target.closest('.fedistream-player__btn--shuffle').classList.toggle('is-active', this.isShuffle);
|
|
}
|
|
|
|
// Repeat button
|
|
if (e.target.closest('.fedistream-player__btn--repeat')) {
|
|
e.preventDefault();
|
|
this.toggleRepeat();
|
|
const btn = e.target.closest('.fedistream-player__btn--repeat');
|
|
btn.classList.remove('is-active', 'is-repeat-one');
|
|
if (this.repeatMode === 'all') {
|
|
btn.classList.add('is-active');
|
|
} else if (this.repeatMode === 'one') {
|
|
btn.classList.add('is-active', 'is-repeat-one');
|
|
}
|
|
}
|
|
|
|
// Volume button (mute toggle)
|
|
if (e.target.closest('.fedistream-player__btn--volume')) {
|
|
e.preventDefault();
|
|
this.toggleMute();
|
|
}
|
|
});
|
|
|
|
// Seek slider
|
|
document.addEventListener('input', (e) => {
|
|
if (e.target.classList.contains('fedistream-player__seek')) {
|
|
const value = e.target.value;
|
|
const duration = this.audio.duration || 0;
|
|
this.audio.currentTime = (value / 100) * duration;
|
|
}
|
|
|
|
// Volume slider
|
|
if (e.target.classList.contains('fedistream-player__volume-slider')) {
|
|
const value = e.target.value / 100;
|
|
this.setVolume(value);
|
|
}
|
|
});
|
|
|
|
// Click on progress bar
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('fedistream-player__bar')) {
|
|
const rect = e.target.getBoundingClientRect();
|
|
const percent = (e.clientX - rect.left) / rect.width;
|
|
const duration = this.audio.duration || 0;
|
|
this.audio.currentTime = percent * duration;
|
|
}
|
|
});
|
|
|
|
// Queue item click (in multi-track player)
|
|
document.addEventListener('click', (e) => {
|
|
const queueItem = e.target.closest('.fedistream-tracklist--queue .fedistream-tracklist__item');
|
|
if (queueItem && !e.target.closest('a')) {
|
|
e.preventDefault();
|
|
const index = parseInt(queueItem.dataset.trackIndex, 10);
|
|
if (!isNaN(index)) {
|
|
this.playAtIndex(index);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Now playing widget controls
|
|
document.addEventListener('click', (e) => {
|
|
if (e.target.closest('.fedistream-now-playing__btn--play')) {
|
|
e.preventDefault();
|
|
this.togglePlayPause();
|
|
}
|
|
if (e.target.closest('.fedistream-now-playing__btn--prev')) {
|
|
e.preventDefault();
|
|
this.previous();
|
|
}
|
|
if (e.target.closest('.fedistream-now-playing__btn--next')) {
|
|
e.preventDefault();
|
|
this.next();
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize inline players
|
|
*/
|
|
initInlinePlayers() {
|
|
const players = document.querySelectorAll('.fedistream-player[data-track-id][data-audio-url]');
|
|
players.forEach(player => {
|
|
const trackId = player.dataset.trackId;
|
|
const audioUrl = player.dataset.audioUrl;
|
|
|
|
// Store reference for later
|
|
player._fedistream = {
|
|
trackId,
|
|
audioUrl
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize multi-track players
|
|
*/
|
|
initMultiTrackPlayers() {
|
|
const players = document.querySelectorAll('.fedistream-player--multi[data-tracks]');
|
|
players.forEach(player => {
|
|
try {
|
|
const tracks = JSON.parse(player.dataset.tracks);
|
|
player._fedistream = { tracks };
|
|
} catch (e) {
|
|
console.error('Failed to parse tracks data', e);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize now playing widgets
|
|
*/
|
|
initNowPlayingWidgets() {
|
|
this.nowPlayingWidgets = document.querySelectorAll('[data-widget="now-playing"]');
|
|
}
|
|
|
|
/**
|
|
* Play a track by ID
|
|
*/
|
|
async playTrackById(trackId) {
|
|
// Check if we have track data in the DOM
|
|
const trackElement = document.querySelector(`[data-track-id="${trackId}"]`);
|
|
let audioUrl = null;
|
|
|
|
// Try to get audio URL from inline player
|
|
const inlinePlayer = document.querySelector(`.fedistream-player[data-track-id="${trackId}"]`);
|
|
if (inlinePlayer && inlinePlayer.dataset.audioUrl) {
|
|
audioUrl = inlinePlayer.dataset.audioUrl;
|
|
}
|
|
|
|
// If no audio URL, try to fetch from API
|
|
if (!audioUrl) {
|
|
const trackData = await this.fetchTrackData(trackId);
|
|
if (trackData && trackData.audio_url) {
|
|
audioUrl = trackData.audio_url;
|
|
}
|
|
}
|
|
|
|
if (!audioUrl) {
|
|
console.error('No audio URL found for track', trackId);
|
|
return;
|
|
}
|
|
|
|
// Build track object
|
|
const track = {
|
|
id: trackId,
|
|
audio_url: audioUrl,
|
|
title: this.getTrackTitle(trackId),
|
|
artist: this.getTrackArtist(trackId),
|
|
thumbnail: this.getTrackThumbnail(trackId)
|
|
};
|
|
|
|
// Clear queue and play single track
|
|
this.clearQueue();
|
|
this.addToQueue(track);
|
|
this.playAtIndex(0);
|
|
}
|
|
|
|
/**
|
|
* Fetch track data from API
|
|
*/
|
|
async fetchTrackData(trackId) {
|
|
if (!window.wpFediStream || !window.wpFediStream.ajaxUrl) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const formData = new FormData();
|
|
formData.append('action', 'fedistream_get_track');
|
|
formData.append('track_id', trackId);
|
|
formData.append('nonce', window.wpFediStream.nonce);
|
|
|
|
const response = await fetch(window.wpFediStream.ajaxUrl, {
|
|
method: 'POST',
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
return data.data;
|
|
}
|
|
} catch (e) {
|
|
console.error('Failed to fetch track data', e);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get track title from DOM
|
|
*/
|
|
getTrackTitle(trackId) {
|
|
const el = document.querySelector(`[data-track-id="${trackId}"] .fedistream-tracklist__title, [data-track-id="${trackId}"] .fedistream-card__title`);
|
|
return el ? el.textContent.trim() : 'Unknown Track';
|
|
}
|
|
|
|
/**
|
|
* Get track artist from DOM
|
|
*/
|
|
getTrackArtist(trackId) {
|
|
const el = document.querySelector(`[data-track-id="${trackId}"] .fedistream-tracklist__artist, [data-track-id="${trackId}"] .fedistream-card__artist`);
|
|
return el ? el.textContent.trim() : 'Unknown Artist';
|
|
}
|
|
|
|
/**
|
|
* Get track thumbnail from DOM
|
|
*/
|
|
getTrackThumbnail(trackId) {
|
|
const el = document.querySelector(`[data-track-id="${trackId}"] .fedistream-tracklist__artwork, [data-track-id="${trackId}"] .fedistream-card__image img`);
|
|
return el ? el.src : '';
|
|
}
|
|
|
|
/**
|
|
* Play album
|
|
*/
|
|
async playAlbum(albumId, shuffle = false) {
|
|
const tracklist = document.querySelector(`.fedistream-btn--play-all[data-album-id="${albumId}"]`)?.closest('.fedistream-single')?.querySelector('.fedistream-tracklist');
|
|
|
|
if (tracklist) {
|
|
this.playTracklist(tracklist, shuffle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play playlist
|
|
*/
|
|
async playPlaylist(playlistId, shuffle = false) {
|
|
const tracklist = document.querySelector(`.fedistream-btn--play-all[data-playlist-id="${playlistId}"]`)?.closest('.fedistream-single')?.querySelector('.fedistream-tracklist');
|
|
|
|
if (tracklist) {
|
|
this.playTracklist(tracklist, shuffle);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play all tracks from a tracklist element
|
|
*/
|
|
playTracklist(tracklistElement, shuffle = false) {
|
|
const items = tracklistElement.querySelectorAll('.fedistream-tracklist__item[data-track-id]');
|
|
const tracks = [];
|
|
|
|
items.forEach(item => {
|
|
const trackId = item.dataset.trackId;
|
|
const titleEl = item.querySelector('.fedistream-tracklist__title');
|
|
const artistEl = item.querySelector('.fedistream-tracklist__artist');
|
|
const artworkEl = item.querySelector('.fedistream-tracklist__artwork');
|
|
|
|
tracks.push({
|
|
id: trackId,
|
|
title: titleEl ? titleEl.textContent.trim() : 'Unknown Track',
|
|
artist: artistEl ? artistEl.textContent.trim() : '',
|
|
thumbnail: artworkEl ? artworkEl.src : ''
|
|
});
|
|
});
|
|
|
|
if (tracks.length === 0) return;
|
|
|
|
this.clearQueue();
|
|
tracks.forEach(track => this.addToQueue(track));
|
|
|
|
if (shuffle) {
|
|
this.shuffleQueue();
|
|
}
|
|
|
|
this.playAtIndex(0);
|
|
}
|
|
|
|
/**
|
|
* Add track to queue
|
|
*/
|
|
addToQueue(track) {
|
|
this.queue.push(track);
|
|
this.originalQueue.push(track);
|
|
}
|
|
|
|
/**
|
|
* Clear queue
|
|
*/
|
|
clearQueue() {
|
|
this.queue = [];
|
|
this.originalQueue = [];
|
|
this.currentIndex = -1;
|
|
}
|
|
|
|
/**
|
|
* Shuffle queue
|
|
*/
|
|
shuffleQueue() {
|
|
const currentTrack = this.currentIndex >= 0 ? this.queue[this.currentIndex] : null;
|
|
|
|
// Fisher-Yates shuffle
|
|
for (let i = this.queue.length - 1; i > 0; i--) {
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
[this.queue[i], this.queue[j]] = [this.queue[j], this.queue[i]];
|
|
}
|
|
|
|
// Keep current track at current position if playing
|
|
if (currentTrack) {
|
|
const newIndex = this.queue.indexOf(currentTrack);
|
|
if (newIndex !== this.currentIndex) {
|
|
[this.queue[this.currentIndex], this.queue[newIndex]] = [this.queue[newIndex], this.queue[this.currentIndex]];
|
|
}
|
|
}
|
|
|
|
this.isShuffle = true;
|
|
}
|
|
|
|
/**
|
|
* Restore original queue order
|
|
*/
|
|
restoreQueueOrder() {
|
|
const currentTrack = this.currentIndex >= 0 ? this.queue[this.currentIndex] : null;
|
|
this.queue = [...this.originalQueue];
|
|
|
|
if (currentTrack) {
|
|
this.currentIndex = this.queue.findIndex(t => t.id === currentTrack.id);
|
|
}
|
|
|
|
this.isShuffle = false;
|
|
}
|
|
|
|
/**
|
|
* Play track at index
|
|
*/
|
|
async playAtIndex(index) {
|
|
if (index < 0 || index >= this.queue.length) return;
|
|
|
|
const track = this.queue[index];
|
|
this.currentIndex = index;
|
|
|
|
// Get audio URL if not present
|
|
if (!track.audio_url) {
|
|
const data = await this.fetchTrackData(track.id);
|
|
if (data && data.audio_url) {
|
|
track.audio_url = data.audio_url;
|
|
} else {
|
|
console.error('Could not load audio for track', track.id);
|
|
this.next();
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.audio.src = track.audio_url;
|
|
this.audio.load();
|
|
this.audio.play().catch(e => {
|
|
console.error('Playback failed', e);
|
|
});
|
|
|
|
this.updateNowPlaying(track);
|
|
this.updateTracklistHighlight();
|
|
this.recordPlay(track.id);
|
|
}
|
|
|
|
/**
|
|
* Toggle play/pause
|
|
*/
|
|
togglePlayPause() {
|
|
if (this.isPlaying) {
|
|
this.audio.pause();
|
|
} else {
|
|
if (this.audio.src) {
|
|
this.audio.play().catch(e => console.error('Playback failed', e));
|
|
} else if (this.queue.length > 0) {
|
|
this.playAtIndex(this.currentIndex >= 0 ? this.currentIndex : 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Play next track
|
|
*/
|
|
next() {
|
|
if (this.queue.length === 0) return;
|
|
|
|
let nextIndex = this.currentIndex + 1;
|
|
|
|
if (nextIndex >= this.queue.length) {
|
|
if (this.repeatMode === 'all') {
|
|
nextIndex = 0;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.playAtIndex(nextIndex);
|
|
}
|
|
|
|
/**
|
|
* Play previous track
|
|
*/
|
|
previous() {
|
|
if (this.queue.length === 0) return;
|
|
|
|
// If more than 3 seconds into track, restart it
|
|
if (this.audio.currentTime > 3) {
|
|
this.audio.currentTime = 0;
|
|
return;
|
|
}
|
|
|
|
let prevIndex = this.currentIndex - 1;
|
|
|
|
if (prevIndex < 0) {
|
|
if (this.repeatMode === 'all') {
|
|
prevIndex = this.queue.length - 1;
|
|
} else {
|
|
this.audio.currentTime = 0;
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.playAtIndex(prevIndex);
|
|
}
|
|
|
|
/**
|
|
* Toggle shuffle mode
|
|
*/
|
|
toggleShuffle() {
|
|
if (this.isShuffle) {
|
|
this.restoreQueueOrder();
|
|
} else {
|
|
this.shuffleQueue();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toggle repeat mode
|
|
*/
|
|
toggleRepeat() {
|
|
const modes = ['none', 'all', 'one'];
|
|
const currentModeIndex = modes.indexOf(this.repeatMode);
|
|
this.repeatMode = modes[(currentModeIndex + 1) % modes.length];
|
|
}
|
|
|
|
/**
|
|
* Set volume
|
|
*/
|
|
setVolume(value) {
|
|
this.volume = Math.max(0, Math.min(1, value));
|
|
this.audio.volume = this.volume;
|
|
this.audio.muted = false;
|
|
this.saveVolumeToStorage();
|
|
this.updateVolumeUI();
|
|
}
|
|
|
|
/**
|
|
* Toggle mute
|
|
*/
|
|
toggleMute() {
|
|
this.audio.muted = !this.audio.muted;
|
|
this.updateVolumeUI();
|
|
}
|
|
|
|
/**
|
|
* Save volume to localStorage
|
|
*/
|
|
saveVolumeToStorage() {
|
|
try {
|
|
localStorage.setItem('fedistream_volume', this.volume.toString());
|
|
} catch (e) {
|
|
// Storage not available
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load volume from localStorage
|
|
*/
|
|
loadVolumeFromStorage() {
|
|
try {
|
|
const stored = localStorage.getItem('fedistream_volume');
|
|
if (stored !== null) {
|
|
this.volume = parseFloat(stored);
|
|
this.audio.volume = this.volume;
|
|
this.updateVolumeUI();
|
|
}
|
|
} catch (e) {
|
|
// Storage not available
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update volume UI
|
|
*/
|
|
updateVolumeUI() {
|
|
const sliders = document.querySelectorAll('.fedistream-player__volume-slider');
|
|
sliders.forEach(slider => {
|
|
slider.value = this.audio.muted ? 0 : this.volume * 100;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Format time in mm:ss
|
|
*/
|
|
formatTime(seconds) {
|
|
if (isNaN(seconds) || !isFinite(seconds)) return '0:00';
|
|
const mins = Math.floor(seconds / 60);
|
|
const secs = Math.floor(seconds % 60);
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
}
|
|
|
|
/**
|
|
* Update now playing info in all widgets
|
|
*/
|
|
updateNowPlaying(track) {
|
|
// Update all now playing widgets
|
|
this.nowPlayingWidgets.forEach(widget => {
|
|
const idle = widget.querySelector('.fedistream-now-playing__idle');
|
|
const active = widget.querySelector('.fedistream-now-playing__active');
|
|
|
|
if (idle) idle.style.display = 'none';
|
|
if (active) active.style.display = 'block';
|
|
|
|
const artwork = widget.querySelector('.fedistream-now-playing__artwork');
|
|
const title = widget.querySelector('.fedistream-now-playing__title');
|
|
const artist = widget.querySelector('.fedistream-now-playing__artist');
|
|
|
|
if (artwork && track.thumbnail) artwork.src = track.thumbnail;
|
|
if (title) title.textContent = track.title;
|
|
if (artist) artist.textContent = track.artist;
|
|
});
|
|
|
|
// Update multi-track player current info
|
|
const multiPlayers = document.querySelectorAll('.fedistream-player--multi');
|
|
multiPlayers.forEach(player => {
|
|
const artwork = player.querySelector('.fedistream-player__artwork--current');
|
|
const title = player.querySelector('.fedistream-player__title--current');
|
|
const artist = player.querySelector('.fedistream-player__artist--current');
|
|
|
|
if (artwork && track.thumbnail) artwork.src = track.thumbnail;
|
|
if (title) title.textContent = track.title;
|
|
if (artist) artist.textContent = track.artist;
|
|
});
|
|
|
|
// Update document title
|
|
if (track.title) {
|
|
document.title = `${track.title}${track.artist ? ' - ' + track.artist : ''} | ${document.title.split('|').pop().trim()}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update tracklist item highlighting
|
|
*/
|
|
updateTracklistHighlight() {
|
|
// Remove all highlights
|
|
document.querySelectorAll('.fedistream-tracklist__item.is-playing').forEach(el => {
|
|
el.classList.remove('is-playing');
|
|
});
|
|
|
|
// Add highlight to current track
|
|
if (this.currentIndex >= 0 && this.queue[this.currentIndex]) {
|
|
const trackId = this.queue[this.currentIndex].id;
|
|
document.querySelectorAll(`.fedistream-tracklist__item[data-track-id="${trackId}"]`).forEach(el => {
|
|
el.classList.add('is-playing');
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record track play via AJAX
|
|
*/
|
|
recordPlay(trackId) {
|
|
if (!window.wpFediStream || !window.wpFediStream.ajaxUrl) return;
|
|
|
|
const formData = new FormData();
|
|
formData.append('action', 'fedistream_record_play');
|
|
formData.append('track_id', trackId);
|
|
formData.append('nonce', window.wpFediStream.nonce);
|
|
|
|
fetch(window.wpFediStream.ajaxUrl, {
|
|
method: 'POST',
|
|
body: formData
|
|
}).catch(e => console.error('Failed to record play', e));
|
|
}
|
|
|
|
// Audio event handlers
|
|
|
|
onTimeUpdate() {
|
|
const current = this.audio.currentTime;
|
|
const duration = this.audio.duration || 0;
|
|
const percent = duration ? (current / duration) * 100 : 0;
|
|
|
|
// Update progress bars
|
|
document.querySelectorAll('.fedistream-player__bar-progress').forEach(el => {
|
|
el.style.width = `${percent}%`;
|
|
});
|
|
|
|
document.querySelectorAll('.fedistream-player__seek').forEach(el => {
|
|
el.value = percent;
|
|
});
|
|
|
|
// Update time displays
|
|
document.querySelectorAll('.fedistream-player__time--current').forEach(el => {
|
|
el.textContent = this.formatTime(current);
|
|
});
|
|
|
|
// Update now playing widgets progress
|
|
this.nowPlayingWidgets.forEach(widget => {
|
|
const progress = widget.querySelector('.fedistream-now-playing__bar-progress');
|
|
const currentTime = widget.querySelector('.fedistream-now-playing__time--current');
|
|
const totalTime = widget.querySelector('.fedistream-now-playing__time--total');
|
|
|
|
if (progress) progress.style.width = `${percent}%`;
|
|
if (currentTime) currentTime.textContent = this.formatTime(current);
|
|
if (totalTime) totalTime.textContent = this.formatTime(duration);
|
|
});
|
|
}
|
|
|
|
onLoadedMetadata() {
|
|
const duration = this.audio.duration || 0;
|
|
|
|
document.querySelectorAll('.fedistream-player__time--total').forEach(el => {
|
|
el.textContent = this.formatTime(duration);
|
|
});
|
|
}
|
|
|
|
onEnded() {
|
|
if (this.repeatMode === 'one') {
|
|
this.audio.currentTime = 0;
|
|
this.audio.play().catch(e => console.error('Playback failed', e));
|
|
} else {
|
|
this.next();
|
|
}
|
|
}
|
|
|
|
onPlay() {
|
|
this.isPlaying = true;
|
|
|
|
// Update all player UIs
|
|
document.querySelectorAll('.fedistream-player').forEach(el => {
|
|
el.classList.add('is-playing');
|
|
});
|
|
|
|
document.querySelectorAll('.fedistream-now-playing').forEach(el => {
|
|
el.classList.add('is-playing');
|
|
});
|
|
}
|
|
|
|
onPause() {
|
|
this.isPlaying = false;
|
|
|
|
document.querySelectorAll('.fedistream-player').forEach(el => {
|
|
el.classList.remove('is-playing');
|
|
});
|
|
|
|
document.querySelectorAll('.fedistream-now-playing').forEach(el => {
|
|
el.classList.remove('is-playing');
|
|
});
|
|
}
|
|
|
|
onError(e) {
|
|
console.error('Audio error', e);
|
|
// Try next track on error
|
|
if (this.queue.length > 1) {
|
|
this.next();
|
|
}
|
|
}
|
|
|
|
onWaiting() {
|
|
document.querySelectorAll('.fedistream-player').forEach(el => {
|
|
el.classList.add('is-loading');
|
|
});
|
|
}
|
|
|
|
onCanPlay() {
|
|
document.querySelectorAll('.fedistream-player').forEach(el => {
|
|
el.classList.remove('is-loading');
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize when DOM is ready
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize the global player
|
|
window.fediStreamPlayer = new FediStreamPlayer();
|
|
|
|
// Initialize any inline players
|
|
window.fediStreamPlayer.initInlinePlayers();
|
|
window.fediStreamPlayer.initMultiTrackPlayers();
|
|
});
|
|
|
|
// Media Session API for system controls
|
|
if ('mediaSession' in navigator) {
|
|
navigator.mediaSession.setActionHandler('play', () => {
|
|
if (window.fediStreamPlayer) {
|
|
window.fediStreamPlayer.togglePlayPause();
|
|
}
|
|
});
|
|
|
|
navigator.mediaSession.setActionHandler('pause', () => {
|
|
if (window.fediStreamPlayer) {
|
|
window.fediStreamPlayer.togglePlayPause();
|
|
}
|
|
});
|
|
|
|
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
|
if (window.fediStreamPlayer) {
|
|
window.fediStreamPlayer.previous();
|
|
}
|
|
});
|
|
|
|
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
|
if (window.fediStreamPlayer) {
|
|
window.fediStreamPlayer.next();
|
|
}
|
|
});
|
|
}
|
|
|
|
})();
|