/**
* Blogspot Audio Player Module
* HTML5 Audio API implementation for Rawtracks template
* Compatible with Blogger/Blogspot platform
*/
(function(window) {
'use strict';
class BlogspotAudioPlayer {
constructor(options = {}) {
this.options = {
container: null,
playlist: [],
autoplay: false,
volume: 0.7,
loop: false,
preload: 'metadata',
...options
};
this.audio = null;
this.currentTrack = 0;
this.isPlaying = false;
this.isLoading = false;
this.playlist = [...this.options.playlist];
this.volume = this.options.volume;
this.loop = this.options.loop;
this.init();
}
init() {
if (!this.options.container) {
console.error('BlogspotAudioPlayer: Container element is required');
return;
}
this.createAudioElement();
this.bindEvents();
this.setupControls();
}
createAudioElement() {
this.audio = document.createElement('audio');
this.audio.preload = this.options.preload;
this.audio.volume = this.volume;
// Insert audio element into container
this.options.container.appendChild(this.audio);
}
loadPlaylist(playlist) {
this.playlist = [...playlist];
this.currentTrack = 0;
if (this.playlist.length > 0) {
this.loadTrack(0);
}
}
loadTrack(index) {
if (index < 0 || index >= this.playlist.length) {
return false;
}
this.currentTrack = index;
const track = this.playlist[index];
if (!track || !track.mp3) {
console.error('Invalid track data:', track);
return false;
}
this.isLoading = true;
this.audio.src = track.mp3;
this.audio.load();
// Trigger loading event
this.trigger('trackLoading', { track: track, index: index });
return true;
}
play(index = null) {
if (typeof index === 'number' && index !== this.currentTrack) {
if (!this.loadTrack(index)) return;
}
const playPromise = this.audio.play();
if (playPromise !== undefined) {
playPromise.then(() => {
this.isPlaying = true;
this.trigger('play', { track: this.getCurrentTrack(), index: this.currentTrack });
}).catch(error => {
console.error('Playback failed:', error);
this.trigger('error', { error: error, track: this.getCurrentTrack() });
});
}
}
pause() {
if (this.isPlaying) {
this.audio.pause();
this.isPlaying = false;
this.trigger('pause', { track: this.getCurrentTrack(), index: this.currentTrack });
}
}
stop() {
this.pause();
this.audio.currentTime = 0;
this.trigger('stop', { track: this.getCurrentTrack(), index: this.currentTrack });
}
next() {
const nextIndex = this.loop ? (this.currentTrack + 1) % this.playlist.length : this.currentTrack + 1;
if (nextIndex < this.playlist.length) {
this.play(nextIndex);
} else {
this.stop();
this.trigger('playlistEnd');
}
}
previous() {
const prevIndex = this.currentTrack - 1;
if (prevIndex >= 0) {
this.play(prevIndex);
}
}
seek(time) {
if (this.audio.duration) {
this.audio.currentTime = Math.max(0, Math.min(time, this.audio.duration));
}
}
setVolume(volume) {
this.volume = Math.max(0, Math.min(1, volume));
this.audio.volume = this.volume;
this.trigger('volumeChange', { volume: this.volume });
}
toggleMute() {
this.audio.muted = !this.audio.muted;
this.trigger('muteChange', { muted: this.audio.muted });
}
getCurrentTrack() {
return this.playlist[this.currentTrack] || null;
}
getCurrentTime() {
return this.audio.currentTime || 0;
}
getDuration() {
return this.audio.duration || 0;
}
getProgress() {
const duration = this.getDuration();
return duration > 0 ? (this.getCurrentTime() / duration) * 100 : 0;
}
bindEvents() {
// Audio element events
this.audio.addEventListener('loadstart', () => {
this.isLoading = true;
this.trigger('loadStart');
});
this.audio.addEventListener('canplay', () => {
this.isLoading = false;
this.trigger('canPlay');
});
this.audio.addEventListener('loadedmetadata', () => {
this.trigger('loadedMetadata', {
duration: this.getDuration(),
track: this.getCurrentTrack()
});
});
this.audio.addEventListener('timeupdate', () => {
this.trigger('timeUpdate', {
currentTime: this.getCurrentTime(),
duration: this.getDuration(),
progress: this.getProgress()
});
});
this.audio.addEventListener('ended', () => {
this.isPlaying = false;
this.trigger('ended', { track: this.getCurrentTrack(), index: this.currentTrack });
if (!this.loop) {
this.next();
}
});
this.audio.addEventListener('error', (e) => {
this.isLoading = false;
this.trigger('error', {
error: e.target.error,
track: this.getCurrentTrack()
});
});
// Handle page visibility changes for autoplay
document.addEventListener('visibilitychange', () => {
if (document.hidden && this.isPlaying) {
this.pause();
}
});
}
setupControls() {
// Add keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
switch(e.keyCode) {
case 32: // Spacebar
e.preventDefault();
this.isPlaying ? this.pause() : this.play();
break;
case 39: // Right arrow
e.preventDefault();
this.seek(this.getCurrentTime() + 10);
break;
case 37: // Left arrow
e.preventDefault();
this.seek(this.getCurrentTime() - 10);
break;
case 38: // Up arrow
e.preventDefault();
this.setVolume(Math.min(1, this.volume + 0.1));
break;
case 40: // Down arrow
e.preventDefault();
this.setVolume(Math.max(0, this.volume - 0.1));
break;
}
});
}
trigger(eventName, data = {}) {
const event = new CustomEvent('blogspotAudioPlayer:' + eventName, {
detail: { player: this, ...data }
});
this.options.container.dispatchEvent(event);
}
destroy() {
if (this.audio) {
this.audio.pause();
this.audio.src = '';
this.audio.load();
this.options.container.removeChild(this.audio);
}
this.playlist = [];
this.currentTrack = 0;
this.isPlaying = false;
}
}
// Playlist functionality for album players
class BlogspotAudioPlaylist extends BlogspotAudioPlayer {
constructor(options = {}) {
super(options);
this.playlistVisible = true;
}
togglePlaylist() {
this.playlistVisible = !this.playlistVisible;
this.trigger('playlistToggle', { visible: this.playlistVisible });
}
addTrack(track, index = null) {
if (index === null) {
this.playlist.push(track);
} else {
this.playlist.splice(index, 0, track);
}
this.trigger('trackAdded', { track: track, index: index });
}
removeTrack(index) {
if (index >= 0 && index < this.playlist.length) {
const removedTrack = this.playlist.splice(index, 0, 1)[0];
this.trigger('trackRemoved', { track: removedTrack, index: index });
if (index <= this.currentTrack) {
this.currentTrack = Math.max(0, this.currentTrack - 1);
}
}
}
reorderTrack(fromIndex, toIndex) {
if (fromIndex >= 0 && fromIndex < this.playlist.length &&
toIndex >= 0 && toIndex < this.playlist.length) {
const track = this.playlist.splice(fromIndex, 1)[0];
this.playlist.splice(toIndex, 0, track);
if (this.currentTrack === fromIndex) {
this.currentTrack = toIndex;
} else if (fromIndex < this.currentTrack && toIndex >= this.currentTrack) {
this.currentTrack--;
} else if (fromIndex > this.currentTrack && toIndex <= this.currentTrack) {
this.currentTrack++;
}
this.trigger('trackReordered', {
fromIndex: fromIndex,
toIndex: toIndex,
track: track
});
}
}
}
// Make classes globally available for Blogspot
window.BlogspotAudioPlayer = BlogspotAudioPlayer;
window.BlogspotAudioPlaylist = BlogspotAudioPlaylist;
})(window);