{"version":3,"sources":["jsdelivr-header.js","/gh/tipmisle/waveflow-global@1.1.3/player.js"],"names":[],"mappings":"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","file":"/gh/tipmisle/waveflow-global@1.1.3/player.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.37.0.\n * Original file: /gh/tipmisle/waveflow-global@1.1.3/player.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","function loadHowler() {\n    return new Promise((resolve, reject) => {\n        const script = document.createElement('script');\n        script.src = \"https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.4/howler.min.js\";\n        script.onload = resolve;\n        script.onerror = reject;\n        document.head.appendChild(script);\n    });\n}\n\nclass AudioPlayer {\n    constructor(container) {\n        this.container = container;\n        \n        // Initialize controls\n        this.playButton = container.querySelector('[data-control=\"play\"]');\n        this.pauseButton = container.querySelector('[data-control=\"pause\"]');\n        this.muteButton = container.querySelector('[data-control=\"mute\"]');\n        this.unmuteButton = container.querySelector('[data-control=\"unmute\"]');\n        \n        // Progress controls\n        this.progressContainer = container.querySelector('[data-control=\"progress\"]');\n        this.progressBar = container.querySelector('[data-progress=\"bar\"]');\n        this.progressHandle = container.querySelector('[data-progress=\"handle\"]');\n        \n        // Volume controls\n        this.volumeContainer = container.querySelector('[data-control=\"volume\"]');\n        this.volumeBar = container.querySelector('[data-volume=\"bar\"]');\n        this.volumeHandle = container.querySelector('[data-volume=\"handle\"]');\n        \n        // Time display elements\n        this.currentTimeDisplay = container.querySelector('[data-audio-current-time]');\n        this.durationDisplay = container.querySelector('[data-audio-duration]');\n        \n        // Audio source - handle both single source and playlist cases\n        const sourceElement = container.querySelector('[data-track-url]');\n        this.audioSource = sourceElement ? sourceElement.getAttribute('data-track-url') : null;\n        \n        // State variables\n        this.isDragging = false;\n        this.isDraggingVolume = false;\n        this.lastVolume = 1;\n        this.isInitialized = false;\n        this.progressUpdateFrame = null;\n        \n        this.initializeHowl();\n        this.bindEvents();\n    }\n\n    formatTime(seconds) {\n        if (isNaN(seconds) || seconds < 0) seconds = 0;\n        seconds = Math.floor(seconds);\n        const minutes = Math.floor(seconds / 60);\n        seconds = seconds % 60;\n        return `${minutes}:${seconds.toString().padStart(2, '0')}`;\n    }\n\n    initializeHowl() {\n        // Only initialize if we have a source\n        if (!this.audioSource) return;\n        \n        try {\n            this.sound = new Howl({\n                src: [this.audioSource],\n                html5: true,\n                volume: 1,\n                preload: true,\n                onload: () => {\n                    this.isInitialized = true;\n                    this.enableControls();\n                    this.updateVolumeBar(1);\n                    \n                    // Set initial duration\n                    const duration = this.sound.duration();\n                    if (this.durationDisplay) {\n                        this.durationDisplay.textContent = this.formatTime(duration);\n                    }\n                    \n                    // Reset progress bar\n                    this.resetProgressBar();\n                    \n                    // Update progress immediately to show initial position\n                    this.updateProgressDisplay();\n                },\n                onplay: () => {\n                    // Start progress update animation loop\n                    this.startProgressUpdate();\n                    \n                    // Update button states\n                    this.updatePlayPauseButtons(true);\n                },\n                onpause: () => {\n                    // Update button states\n                    this.updatePlayPauseButtons(false);\n                    \n                    // Update progress one more time\n                    this.updateProgressDisplay();\n                },\n                onseek: () => {\n                    // Update UI when seeking completes\n                    this.updateProgressDisplay();\n                },\n                onstop: () => {\n                    // Reset UI when stopped\n                    this.resetProgressBar();\n                    this.updatePlayPauseButtons(false);\n                }\n            });\n        } catch (error) {\n            console.error('Error initializing Howl:', error);\n        }\n    }\n\n    enableControls() {\n        if (this.playButton) this.playButton.disabled = false;\n        if (this.pauseButton) this.pauseButton.disabled = false;\n        if (this.muteButton) this.muteButton.disabled = false;\n        if (this.unmuteButton) this.unmuteButton.disabled = false;\n    }\n\n    updatePlayPauseButtons(isPlaying) {\n        if (isPlaying) {\n            this.playButton.classList.add('hidden');\n            this.pauseButton.classList.remove('hidden');\n        } else {\n            this.pauseButton.classList.add('hidden');\n            this.playButton.classList.remove('hidden');\n        }\n    }\n\n    resetProgressBar() {\n        if (this.progressBar) this.progressBar.style.width = '0%';\n        if (this.progressHandle) this.progressHandle.style.left = '0%';\n        if (this.currentTimeDisplay) this.currentTimeDisplay.textContent = this.formatTime(0);\n    }\n\n    updateVolume(percentage) {\n        if (!this.sound) return;\n        \n        try {\n            this.sound.volume(percentage);\n            this.updateVolumeBar(percentage);\n            this.lastVolume = percentage;\n            \n            // Update mute/unmute button states\n            if (percentage === 0) {\n                if (this.muteButton) this.muteButton.classList.add('hidden');\n                if (this.unmuteButton) this.unmuteButton.classList.remove('hidden');\n            } else {\n                if (this.unmuteButton) this.unmuteButton.classList.add('hidden');\n                if (this.muteButton) this.muteButton.classList.remove('hidden');\n            }\n        } catch (error) {\n            console.error('Error updating volume:', error);\n        }\n    }\n\n    updateVolumeBar(percentage) {\n        if (this.volumeBar) this.volumeBar.style.width = `${percentage * 100}%`;\n        if (this.volumeHandle) this.volumeHandle.style.left = `${percentage * 100}%`;\n    }\n\n    // New method to update progress display without animation loop\n    updateProgressDisplay() {\n        if (!this.sound || !this.isInitialized) return;\n        \n        try {\n            let currentTime = 0;\n            try {\n                currentTime = this.sound.seek();\n                // Handle case where seek() returns the Howl object instead of time\n                if (typeof currentTime !== 'number') {\n                    currentTime = 0;\n                }\n            } catch (e) {\n                currentTime = 0;\n            }\n            \n            let duration = 1;\n            try {\n                duration = this.sound.duration() || 1;\n                if (isNaN(duration) || duration <= 0) duration = 1;\n            } catch (e) {\n                duration = 1;\n            }\n            \n            const progress = Math.min(1, Math.max(0, currentTime / duration));\n            \n            if (!this.isDragging) {\n                if (this.progressBar) this.progressBar.style.width = `${progress * 100}%`;\n                if (this.progressHandle) this.progressHandle.style.left = `${progress * 100}%`;\n                \n                // Update current time display\n                if (this.currentTimeDisplay) {\n                    this.currentTimeDisplay.textContent = this.formatTime(currentTime);\n                }\n            }\n        } catch (error) {\n            console.error('Error updating progress display:', error);\n        }\n    }\n\n    startProgressUpdate() {\n        // Clear any existing animation frame\n        if (this.progressUpdateFrame) {\n            cancelAnimationFrame(this.progressUpdateFrame);\n            this.progressUpdateFrame = null;\n        }\n        \n        const updateLoop = () => {\n            this.updateProgressDisplay();\n            \n            // Only continue if playing\n            if (this.sound && this.sound.playing()) {\n                this.progressUpdateFrame = requestAnimationFrame(updateLoop);\n            } else {\n                this.progressUpdateFrame = null;\n            }\n        };\n        \n        this.progressUpdateFrame = requestAnimationFrame(updateLoop);\n    }\n\n    bindEvents() {\n        // Play control\n        if (this.playButton) {\n            this.playButton.addEventListener('click', () => {\n                if (!this.sound || !this.isInitialized) return;\n                this.sound.play();\n            });\n        }\n\n        // Pause control\n        if (this.pauseButton) {\n            this.pauseButton.addEventListener('click', () => {\n                if (!this.sound || !this.isInitialized) return;\n                this.sound.pause();\n            });\n        }\n\n        // Mute control\n        if (this.muteButton) {\n            this.muteButton.addEventListener('click', () => {\n                if (!this.sound || !this.isInitialized) return;\n                this.lastVolume = this.sound.volume();\n                this.sound.volume(0);\n                this.updateVolumeBar(0);\n                this.muteButton.classList.add('hidden');\n                this.unmuteButton.classList.remove('hidden');\n            });\n        }\n\n        // Unmute control\n        if (this.unmuteButton) {\n            this.unmuteButton.addEventListener('click', () => {\n                if (!this.sound || !this.isInitialized) return;\n                this.sound.volume(this.lastVolume);\n                this.updateVolumeBar(this.lastVolume);\n                this.unmuteButton.classList.add('hidden');\n                this.muteButton.classList.remove('hidden');\n            });\n        }\n\n        // Volume control click\n        if (this.volumeContainer) {\n            this.volumeContainer.addEventListener('click', (e) => {\n                if (!this.sound || !this.isInitialized || this.isDraggingVolume) return;\n                \n                const rect = this.volumeContainer.getBoundingClientRect();\n                const x = e.clientX - rect.left;\n                const percentage = Math.max(0, Math.min(1, x / rect.width));\n                this.updateVolume(percentage);\n            });\n        }\n\n        // Volume control drag\n        if (this.volumeHandle) {\n            this.volumeHandle.addEventListener('mousedown', (e) => {\n                this.isDraggingVolume = true;\n                e.preventDefault(); // Prevent text selection\n            });\n            \n            // Add touch support\n            this.volumeHandle.addEventListener('touchstart', (e) => {\n                this.isDraggingVolume = true;\n                e.preventDefault(); // Prevent scrolling\n            });\n        }\n\n        // Progress bar click\n        if (this.progressContainer) {\n            this.progressContainer.addEventListener('click', (e) => {\n                if (!this.sound || !this.isInitialized || this.isDragging) return;\n                \n                const rect = this.progressContainer.getBoundingClientRect();\n                const x = e.clientX - rect.left;\n                const percentage = Math.max(0, Math.min(1, x / rect.width));\n                \n                // Update UI immediately for better feedback\n                if (this.progressBar) this.progressBar.style.width = `${percentage * 100}%`;\n                if (this.progressHandle) this.progressHandle.style.left = `${percentage * 100}%`;\n                \n                // Calculate and display time\n                try {\n                    const duration = this.sound.duration() || 0;\n                    const seekTime = percentage * duration;\n                    \n                    if (this.currentTimeDisplay) {\n                        this.currentTimeDisplay.textContent = this.formatTime(seekTime);\n                    }\n                    \n                    // Then seek the audio\n                    this.sound.seek(seekTime);\n                } catch (error) {\n                    console.error('Error seeking:', error);\n                }\n            });\n        }\n\n        // Progress bar drag\n        if (this.progressHandle) {\n            this.progressHandle.addEventListener('mousedown', (e) => {\n                if (!this.sound || !this.isInitialized) return;\n                this.isDragging = true;\n                e.preventDefault(); // Prevent text selection\n            });\n            \n            // Add touch support\n            this.progressHandle.addEventListener('touchstart', (e) => {\n                if (!this.sound || !this.isInitialized) return;\n                this.isDragging = true;\n                e.preventDefault(); // Prevent scrolling\n            });\n        }\n\n        // Global mouse/touch move handler\n        const handleMove = (e) => {\n            const clientX = e.clientX || (e.touches && e.touches[0] ? e.touches[0].clientX : null);\n            if (clientX === null) return;\n            \n            // Handle volume dragging\n            if (this.isDraggingVolume && this.volumeContainer) {\n                const rect = this.volumeContainer.getBoundingClientRect();\n                let x = clientX - rect.left;\n                const percentage = Math.max(0, Math.min(1, x / rect.width));\n                \n                // Update volume immediately, not just visual indicator\n                this.updateVolume(percentage);\n            }\n            \n            // Handle progress dragging\n            if (this.isDragging && this.sound && this.progressContainer) {\n                const rect = this.progressContainer.getBoundingClientRect();\n                let x = clientX - rect.left;\n                x = Math.max(0, Math.min(x, rect.width));\n                const percentage = x / rect.width;\n                \n                if (this.progressBar) this.progressBar.style.width = `${percentage * 100}%`;\n                if (this.progressHandle) this.progressHandle.style.left = `${percentage * 100}%`;\n                \n                // Update time display while dragging\n                if (this.currentTimeDisplay && this.sound) {\n                    try {\n                        const duration = this.sound.duration() || 0;\n                        const seekTime = percentage * duration;\n                        this.currentTimeDisplay.textContent = this.formatTime(seekTime);\n                    } catch (error) {\n                        console.error('Error updating time during drag:', error);\n                    }\n                }\n            }\n        };\n        \n        document.addEventListener('mousemove', handleMove);\n        document.addEventListener('touchmove', handleMove, { passive: false });\n\n        // Global mouse/touch up handler\n        const handleEnd = () => {\n            // Handle progress drag end\n            if (this.isDragging && this.sound && this.isInitialized) {\n                try {\n                    const percentage = parseFloat(this.progressBar.style.width) / 100 || 0;\n                    const duration = this.sound.duration() || 0;\n                    const seekTime = percentage * duration;\n                    this.sound.seek(seekTime);\n                    \n                    // Force an update of the display after seeking\n                    setTimeout(() => this.updateProgressDisplay(), 50);\n                } catch (error) {\n                    console.error('Error seeking audio:', error);\n                }\n                this.isDragging = false;\n            }\n            \n            // Just reset the dragging state for volume\n            // We don't need to update the volume again since we did it in real-time\n            this.isDraggingVolume = false;\n        };\n        \n        document.addEventListener('mouseup', handleEnd);\n        document.addEventListener('touchend', handleEnd);\n        document.addEventListener('touchcancel', handleEnd);\n    }\n}\n\nclass PlaylistAudioPlayer extends AudioPlayer {\n    constructor(container) {\n        super(container);\n        \n        // Stop and destroy the initial Howl instance since we'll create our own\n        if (this.sound) {\n            this.sound.unload();\n            this.sound = null;\n            this.isInitialized = false;\n        }\n        \n        // Additional controls for playlist\n        this.prevButton = container.querySelector('[data-control=\"prev\"]');\n        this.nextButton = container.querySelector('[data-control=\"next\"]');\n        this.playlistContainer = container.querySelector('[data-playlist-container]');\n        this.playlistTitle = container.querySelector('[data-playlist-title]');\n        \n        // Get playlist items\n        this.playlistItems = Array.from(this.playlistContainer?.querySelectorAll('[data-track-url]') || []);\n        this.currentTrackIndex = 0;\n        \n        if (this.playlistItems.length > 0) {\n            this.initializePlaylist();\n            this.bindPlaylistEvents();\n        }\n    }\n\n    initializePlaylist() {\n        // Set active class on first item\n        if (this.playlistItems.length > 0) {\n            this.playlistItems.forEach(item => item.classList.remove('active'));\n            this.playlistItems[0].classList.add('active');\n            // Set initial track\n            this.setTrack(0, false);\n        }\n    }\n\n    setTrack(index, autoplay = false) {\n        if (index < 0 || index >= this.playlistItems.length) return false;\n        \n        // Update current track index\n        this.currentTrackIndex = index;\n        \n        // Update playlist UI\n        this.playlistItems.forEach((item, i) => {\n            item.classList.toggle('active', i === index);\n        });\n        \n        // Get current track item\n        const currentItem = this.playlistItems[index];\n        if (!currentItem) return false;\n        \n        // Update player title\n        if (this.playlistTitle) {\n            this.playlistTitle.textContent = currentItem.textContent.trim();\n        }\n        \n        // Get the playing state before changing track\n        const wasPlaying = this.sound && this.sound.playing();\n        \n        // Clean up previous track\n        if (this.sound) {\n            this.sound.unload();\n            this.sound = null;\n        }\n        \n        // Cancel any ongoing animation frame\n        if (this.progressUpdateFrame) {\n            cancelAnimationFrame(this.progressUpdateFrame);\n            this.progressUpdateFrame = null;\n        }\n        \n        // Reset UI\n        this.resetProgressBar();\n        this.isInitialized = false;\n        \n        // Initialize new track with URL from data attribute\n        this.audioSource = currentItem.getAttribute('data-track-url');\n\n        // Store current volume before creating new Howl instance\n        const currentVolume = this.sound ? this.sound.volume() : (this.lastVolume || 1);\n\n        // Initialize the new Howl instance\n        this.initializeHowl();\n\n        // After initialization, set the volume to match the previous volume\n        if (this.sound) {\n            // Apply the volume once the sound is loaded\n            this.sound.once('load', () => {\n                this.sound.volume(currentVolume);\n                this.updateVolumeBar(currentVolume);\n            });\n        }\n\n        // If autoplay is true or was previously playing, play the new track\n        if (autoplay || wasPlaying) {\n            // We need to wait for the track to load before playing\n            const checkAndPlay = () => {\n                if (this.isInitialized && this.sound) {\n                    this.sound.play();\n                    return true;\n                }\n                return false;\n            };\n            \n            if (!checkAndPlay()) {\n                const playInterval = setInterval(() => {\n                    if (checkAndPlay()) {\n                        clearInterval(playInterval);\n                    }\n                }, 100);\n                \n                // Safety timeout to prevent infinite interval\n                setTimeout(() => clearInterval(playInterval), 5000);\n            }\n        } else {\n            // Make sure the play/pause buttons are in the correct state\n            this.updatePlayPauseButtons(false);\n        }\n        \n        return true;\n    }\n\n    bindPlaylistEvents() {\n        // Previous track\n        if (this.prevButton) {\n            this.prevButton.addEventListener('click', () => {\n                const newIndex = (this.currentTrackIndex - 1 + this.playlistItems.length) % this.playlistItems.length;\n                this.setTrack(newIndex, true);\n            });\n        }\n        \n        // Next track\n        if (this.nextButton) {\n            this.nextButton.addEventListener('click', () => {\n                const newIndex = (this.currentTrackIndex + 1) % this.playlistItems.length;\n                this.setTrack(newIndex, true);\n            });\n        }\n        \n        // Playlist item click\n        if (this.playlistContainer) {\n            this.playlistContainer.addEventListener('click', (e) => {\n                const item = e.target.closest('[data-track-url]');\n                if (item) {\n                    const index = this.playlistItems.indexOf(item);\n                    if (index !== -1) {\n                        this.setTrack(index, true); // Always play when clicking directly\n                    }\n                }\n            });\n        }\n    }\n    \n    // Override initializeHowl to add the end event binding\n    initializeHowl() {\n        // Store volume before creating new instance\n        const currentVolume = this.sound ? this.sound.volume() : (this.lastVolume || 1);\n        \n        // Call parent's initializeHowl\n        super.initializeHowl();\n        \n        // Add end event handler for auto-advancing playlist\n        if (this.sound) {\n            this.sound.on('end', () => {\n                const newIndex = (this.currentTrackIndex + 1) % this.playlistItems.length;\n                this.setTrack(newIndex, true); // Auto-play next track\n            });\n            \n            // Apply volume with timeouts to ensure it works\n            this.sound.once('load', () => {\n                // Set volume immediately\n                this.sound.volume(currentVolume);\n                \n                // Update UI immediately\n                this.updateVolumeBar(currentVolume);\n                \n                // And also with a small delay to ensure UI is updated\n                setTimeout(() => {\n                    this.sound.volume(currentVolume);\n                    this.updateVolumeBar(currentVolume);\n                    this.lastVolume = currentVolume; // Also update lastVolume for consistency\n                }, 10);\n            });\n        }\n    }\n}\n\n// Initialize all audio players when Howler is loaded\nloadHowler().then(() => {\n    // Initialize single audio players\n    document.querySelectorAll('[data-audio-player]').forEach(container => {\n        new AudioPlayer(container);\n    });\n    \n    // Initialize playlist audio players\n    document.querySelectorAll('[data-audio-player-multiple]').forEach(container => {\n        new PlaylistAudioPlayer(container);\n    });\n}).catch(error => {\n    console.error('Error loading Howler.js:', error);\n});"]}