PageSourceSearch

https://www.kvom.com/wp-content/themes/srr-launch/assets/js/srr-player.min.js?ver=3.7.6

js kvom.com collected 2026-09-26 10:44:55 UTC 22,411 bytes, 485 lines download raw bytes

1/**
2 * This script enhances the audio elements on a webpage by adding a custom player interface.
3 * The custom player includes a play/pause button, a progress bar, and a time counter.
4 * It also handles the logic for playing, pausing, and seeking the audio.
5 */
6
7// Wait for the DOM to be fully loaded before executing the script
8document.addEventListener('DOMContentLoaded', () => {
9
10    /**
11     * Function to format time in seconds to a HH:MM:SS or MM:SS format
12     * @param {number} seconds - The time in seconds
13     * @returns {string} The formatted time
14     */
15    const formatTime = (seconds) => {
16        const hours = Math.floor(seconds / 3600);
17        const minutes = Math.floor((seconds % 3600) / 60);
18        seconds = Math.floor(seconds % 60);
19
20        if (hours > 0) {
21            return `${hours}:${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
22        } else {
23            return `${minutes < 10 ? '0' : ''}${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
24        }
25    }
26
27    /**
28     * Function to enhance audio elements on the page
29     * @param {string} selector - The CSS selector for the audio elements to enhance
30     */
31    const enhanceAudioElements = (selector) => {
32        // Get all audio elements that match the selector and haven't been enhanced yet
33        // Only exclude streamplayer's internal audio elements, not page audio elements
34        const audioElements = document.querySelectorAll('audio:not(.custom-enhanced):not(.ad-audio)');  // Revised 8/20/24 due to debug.log errors.
35
36        audioElements.forEach((audioElement) => {
37            // Get the source of the audio
38            const audioSrc = audioElement.getAttribute('src') || audioElement.querySelector('source').getAttribute('src');
39            // Get the duration if it exists from RSS feed
40            const rssDuration = audioElement.getAttribute('data-duration');
41
42            // If there's no source, skip this audio element
43            if (!audioSrc) return;
44
45            // Stop any native playback on the original element before we enhance/hide it.
46            // This prevents double playback (native controls audio + StreamPlayer/mini-player audio).
47            try {
48                audioElement.pause();
49                audioElement.currentTime = 0;
50                audioElement.muted = true;
51            } catch (err) {
52                // no-op
53            }
54
55            // Skip if this is streamplayer's internal audio element (not page content)
56            if (audioElement.closest('.streamplayer') && audioElement.hasAttribute('data-streamplayer-internal')) {
57                return;
58            }
59
60            // Create the Audio object first. The browser will resolve the src to an absolute URL.
61            const audioObj = new Audio(audioSrc);
62            const absoluteAudioSrc = audioObj.src;
63
64            // Mark the audio element as enhanced
65            audioElement.classList.add('custom-enhanced');
66            // Create a container for the custom player
67            const container = document.createElement('div');
68            container.className = 'minimal-player';
69            container.dataset.audioSrc = absoluteAudioSrc;
70
71
72            if (rssDuration) {
73                container.dataset.duration = rssDuration;
74            }
75
76            // Initial counter display - will be updated once we have duration
77            const initialTimeDisplay = '00:00 / 00:00';
78
79            // The HTML for the custom player
80            const customPlayerHTML = `
81                <button class='play-pause-btn' aria-label='Play' aria-pressed='false'><i class="srricons icon-play" aria-hidden="true"></i></button>
82                <div class='progress-container' role='progressbar' aria-valuemin='0' aria-valuemax='100' aria-valuenow='0' aria-label='Audio progress'>
83                    <div class='progress-bar'></div>
84                    <span class='play-counter'>${initialTimeDisplay}</span>
85                </div>`;
86
87            // Add the custom player to the container
88            container.innerHTML = customPlayerHTML;
89            // Hide the original audio element
90            audioElement.style.display = 'none';
91
92            // Check for metadata on a parent element and copy it to the container
93            const metadataParent = audioElement.parentNode;
94            if (metadataParent) {
95                const title = metadataParent.dataset.title;
96                const artist = metadataParent.dataset.artist;
97                const image = metadataParent.dataset.image;
98
99                if (title) container.dataset.title = title;
100                if (artist) container.dataset.artist = artist;
101                if (image) container.dataset.image = image;
102            }
103            // Insert the custom player after the original audio element
104            audioElement.parentNode.insertBefore(container, audioElement.nextSibling);
105
106            // Store the Audio object in the container
107            container.audio = audioObj;
108
109            // Update duration display once metadata is loaded
110            audioObj.addEventListener('loadedmetadata', () => {
111                const counter = container.querySelector('.play-counter');
112                if (counter) {
113                    // Check for RSS duration first, fall back to audio duration
114                    const duration = rssDuration ? convertITunesDurationToSeconds(rssDuration) : audioObj.duration;
115                    if (!isNaN(duration) && isFinite(duration)) {
116                        counter.textContent = `00:00 / ${formatTime(duration)}`;
117                    }
118                }
119            });
120        });
121    }
122
123    // Enhance all audio elements on the page
124    // enhanceAudioElements('audio');
125
126    // Variables to store the current audio being played and its play/pause button
127    let container, playPauseBtn, audio = new Audio(), progressBar, playCounter, isStreaming = false, audioEventsAttached = false;
128
129    // Function to update mini-player UI based on StreamPlayer state
130    const updateMiniPlayerUI = (isPlaying, audioSrc) => {
131        // Find all minimal players that match the current playing audio source
132        const matchingPlayers = document.querySelectorAll(`.minimal-player[data-audio-src="${audioSrc}"]`);
133
134        document.querySelectorAll('.minimal-player').forEach(player => {
135            const button = player.querySelector('.play-pause-btn');
136            const isMatch = player.dataset.audioSrc === audioSrc;
137            // Update button icon based on play state and whether this player matches current audio
138            if (isMatch && isPlaying) {
139                button.innerHTML = '<i class="srricons icon-pause" aria-hidden="true"></i>'; button.setAttribute('aria-label', 'Pause'); button.setAttribute('aria-pressed', 'true');
140            } else {
141                button.innerHTML = '<i class="srricons icon-play" aria-hidden="true"></i>'; button.setAttribute('aria-label', 'Play'); button.setAttribute('aria-pressed', 'false');
142            }
143        });
144    };
145
146    // Function to update progress bar in mini-players
147    const updateMiniPlayerProgress = (audioSrc, currentTime, duration) => {
148        if (!audioSrc || currentTime === undefined || duration === undefined) return;
149
150        // Find all mini-players with matching source
151        document.querySelectorAll(`.minimal-player[data-audio-src="${audioSrc}"]`).forEach(player => {
152            const progressBar = player.querySelector('.progress-bar');
153            const counter = player.querySelector('.play-counter');
154
155            if (progressBar) {
156                if (!isNaN(duration) && isFinite(duration) && duration > 0) {
157                    const progress = Math.min(100, Math.max(0, (currentTime / duration) * 100));
158                    progressBar.style.width = `${progress}%`;
159
160                    if (counter) {
161                        counter.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
162                    }
163                } else if (duration === Infinity || isNaN(duration)) {
164                    // Handle streaming audio (live)
165                    progressBar.style.width = '100%';
166                    if (counter) {
167                        counter.textContent = 'Live';
168                    }
169                }
170            }
171        });
172    };
173
174    // Listen for StreamPlayer state changes
175    document.addEventListener('streamPlayerStateChanged', (e) => {
176        if (e.detail) {
177            const { isPlaying, audioSrc, currentTime, duration, isLoading } = e.detail;
178
179            // Update play/pause button state
180            updateMiniPlayerUI(isPlaying, audioSrc);
181
182            // Update progress bar if we have time information
183            if (audioSrc && currentTime !== undefined) {
184                updateMiniPlayerProgress(audioSrc, currentTime, duration);
185            }
186
187            // If loading, add loading indicator
188            if (isLoading) {
189                document.querySelectorAll(`.minimal-player[data-audio-src="${audioSrc}"]`).forEach(player => {
190                    player.classList.add('loading');
191                });
192            } else {
193                document.querySelectorAll(`.minimal-player[data-audio-src="${audioSrc}"]`).forEach(player => {
194                    player.classList.remove('loading');
195                });
196            }
197        }
198    });
199
200    // Handle click events on the page
201    document.addEventListener('click', (e) => {
202
203        // If the click was on a play/pause button
204        if (e.target.closest('.play-pause-btn')) {
205            container = e.target.closest('.minimal-player');
206            playPauseBtn = container.querySelector('.play-pause-btn');
207            const audioSrc = container.dataset.audioSrc;
208            const isStreamPlayerActive = document.querySelector('.streamplayer') !== null;
209
210            // If StreamPlayer is active, ensure the minimal player'
210s own Audio instance is not playing.
211            // Without this, the page-level audio (container.audio) can keep playing while StreamPlayer
212            // starts a second audio instance for the same source.
213            if (isStreamPlayerActive && container && container.audio && !container.audio.paused) {
214                try {
215                    container.audio.pause();
216                    container.audio.currentTime = 0;
217                } catch (err) {
218                    // no-op: best-effort cleanup
219                }
220            }
221
222            // Check if this audio is currently playing in StreamPlayer
223            const isCurrentlyPlaying = window.currentlyPlaying?.src === audioSrc;
224
225            if (isStreamPlayerActive) {
226                // If StreamPlayer is active, use it to control audio
227                audio = window.currentlyPlaying;
228
229                if (audio?.paused || !isCurrentlyPlaying) {
230                    // If paused or different source, dispatch event to play in StreamPlayer
231                    document.dispatchEvent(new CustomEvent('playInStreamPlayer', {
232                        detail: {
233                            src: audioSrc,
234                            container: container,
235                            title: container.dataset.title || audioSrc.split('/').pop().split('.')[0],
236                            artist: container.dataset.artist,
237                            image: container.dataset.image,
238                            originalAudioId: container.dataset.id || null,
239                            audioElement: container.audio
240                        }
241                    }));
242
243                    // Update UI immediately for better responsiveness
244                    playPauseBtn.innerHTML = '<i class="srricons icon-pause" aria-hidden="true"></i>'; playPauseBtn.setAttribute('aria-label', 'Pause'); playPauseBtn.setAttribute('aria-pressed', 'true');
245                } else {
246                    // If playing, dispatch event to pause in StreamPlayer
247                    document.dispatchEvent(new CustomEvent('pauseInStreamPlayer', {
248                        detail: {
249                            container: container,
250                            audioElement: container.audio
251                        }
252                    }));
253
254                    // Update UI immediately for better responsiveness
255                    playPauseBtn.innerHTML = '<i class="srricons icon-play" aria-hidden="true"></i>'; playPauseBtn.setAttribute('aria-label', 'Play'); playPauseBtn.setAttribute('aria-pressed', 'false');
256                }
257            } else {
258                // If StreamPlayer is not active, use the direct audio control
259                audio = container.audio;
260                progressBar = container.querySelector('.progress-bar');
261                playCounter = container.querySelector('.play-counter');
262
263                // Attach event listeners once per audio object
264                if (!audio._srrEventsAttached) {
265                    attachAudioEvents(audio);
266                    audio._srrEventsAttached = true;
267                }
268
269                // If the audio is paused, play it (resume from current position)
270                if (audio?.paused) {
271                    if (window.currentlyPlaying && window.currentlyPlaying !== audio) {
272                        window.currentlyPlaying.pause();
273                    }
274                    window.currentlyPlaying = audio;
275                    audio?.play();
276                } else {
277                    // If the audio is playing, pause it (keeps current position)
278                    audio?.pause();
279                }
280            }
281        }
282
283        // If the click was on a progress bar
284        if (e.target.closest('.progress-container')) {
285            const progressContainer = e.target.closest('.progress-container');
286            const audioContainer = progressContainer.closest('.minimal-player');
287            const audioSrc = audioContainer.dataset.audioSrc;
288            const isStreamPlayerActive = document.querySelector('.streamplayer') !== null;
289
290            // Get click position
291            const offsetX = e.clientX - progressContainer.getBoundingClientRect().left;
292            const totalWidth = progressContainer.offsetWidth;
293            const percentage = offsetX / totalWidth;
294
295            if (isStreamPlayerActive) {
296                // If StreamPlayer is active and this is not current audio, play it first
297                if (window.currentlyPlaying?.src !== audioSrc) {
298                    audioContainer.querySelector('.play-pause-btn').click();
299
300                    // After a small delay to allow for audio loading, set the time
301                    setTimeout(() => {
302                        if (window.currentlyPlaying && !isNaN(window.currentlyPlaying.duration)) {
303                            window.currentlyPlaying.currentTime = percentage * window.currentlyPlaying.duration;
304                        }
305                    }, 100);
306                } else if (window.currentlyPlaying && !isNaN(window.currentlyPlaying.duration)) {
307                    // If already playing in StreamPlayer, just seek
308                    window.currentlyPlaying.currentTime = percentage * window.currentlyPlaying.duration;
309                }
310            } else {
311                // Use direct audio control if StreamPlayer is not active
312                const audio = audioContainer.audio;
313
314                // If this is not current audio, switch to it
315                if (window.currentlyPlaying?.src !== audioSrc) {
316                    audioContainer.querySelector('.play-pause-btn').click();
317                }
318
319                // If the audio is loaded, seek to the clicked position
320                if (audio && !isNaN(audio.duration)) {
321                    audio.currentTime = percentage * audio.duration;
322                }
323            }
324        }
325
326    })
327
328    // Attach audio event listeners once and bind to current audio/container via closures over the outer variables
329    function attachAudioEvents(audioObj) {
330        // Update the button icon when the audio is paused
331        audioObj.addEventListener('pause', () => {
332            if (playPauseBtn && container && container.audio === audioObj) {
333                playPauseBtn.innerHTML = '<i class="srricons icon-play" aria-hidden="true"></i>'; playPauseBtn.setAttribute('aria-label', 'Play'); playPauseBtn.setAttribute('aria-pressed', 'false');
334                if (isStreaming && progressBar) progressBar.style.width = '0%';
335            }
336        });
337
338        audioObj.addEventListener('play', () => {
339            // When StreamPlayer is active, do not reset all players here.
340            if (!document.querySelector('.streamplayer')) {
341                resetOtherPlayers(audioObj);
342            }
343            if (playPauseBtn && container && container.audio === audioObj) {
344                playPauseBtn.innerHTML = '<i class="srricons icon-pause" aria-hidden="true"></i>'; playPauseBtn.setAttribute('aria-label', 'Pause'); playPauseBtn.setAttribute('aria-pressed', 'true');
345            }
346        });
347
348        audioObj.addEventListener('timeupdate', () => {
349            if (!playPauseBtn || !progressBar || !playCounter) return;
350            if (!container || container.audio !== audioObj) return;
351
352            const rssDuration = container?.dataset?.duration;
353            const duration = rssDuration ? convertITunesDurationToSeconds(rssDuration) : audioObj.duration;
354
355            if ((isNaN(duration) || duration === Infinity)) {
356                isStreaming = true;
357                progressBar.style.width = '100%';
358                playCounter.textContent = 'Live';
359                return;
360            }
361
362            isStreaming = false;
363
364            if (isNaN(duration) || !isFinite(duration)) {
365                return;
366            }
367
368            const progress = (audioObj.currentTime / duration) * 100;
369            progressBar.style.width = `${progress}%`;
370            playCounter.textContent = `${formatTime(audioObj.currentTime)} / ${formatTime(duration)}`;
371
372            document.dispatchEvent(new CustomEvent('miniPlayerTimeUpdate', {
373                detail: {
374                    audioSrc: audioObj.src,
375                    currentTime: audioObj.currentTime,
376                    duration: duration
377                }
378            }));
379        });
380
381        audioObj.addEventListener('ended', () => {
382            if (container && container.audio === audioObj) {
383                var endBtn = container.querySelector('.play-pause-btn'); endBtn.innerHTML = '<i class="srricons icon-play" aria-hidden="true"></i>'; endBtn.setAttribute('aria-label', 'Play'); endBtn.setAttribute('aria-pressed', 'false');
384            }
385            audioObj.currentTime = 0;
386        });
387    }
388
389    // Reset all OTHER players (skip the one currently playing)
390    const resetOtherPlayers = (activeAudio) => {
391        document.querySelectorAll('.minimal-player').forEach((player) => {
392            // Skip the player that is currently active
393            if (player.audio === activeAudio) return;
394
395            const btn = player.querySelector('.play-pause-btn');
396            if (btn) {
397                btn.innerHTML = '<i class="srricons icon-play" aria-hidden="true"></i>'; btn.setAttribute('aria-label', 'Play'); btn.setAttribute('aria-pressed', 'false');
398            }
399
400            const bar = player.querySelector('.progress-bar');
401            const counter = player.querySelector('.play-counter');
402            const playerAudio = player.audio;
403            const rssDuration = player.dataset?.duration;
404
405            let duration = null;
406            if (rssDuration) {
407                duration = convertITunesDurationToSeconds(rssDuration);
408            } else if (playerAudio && !isNaN(playerAudio.duration) && isFinite(playerAudio.duration)) {
409                duration = playerAudio.duration;
410            }
411
412            if (bar) bar.style.width = '0%';
413            if (counter) {
414                counter.textContent = duration ? `00:00 / ${formatTime(duration)}` : '00:00 / 00:00';
415            }
416        });
417    }
418
419    // Listen for StreamPlayer events to sync mini-players
420    document.addEventListener('miniPlayerTimeUpdate', (e) => {
421        if (e.detail) {
422            const { audioSrc, currentTime, duration } = e.detail;
423
424            // Update all mini-players with matching source except the one that triggered the event
425            document.querySelectorAll(`.minimal-player[data-audio-src="${audioSrc}"]`).forEach(player => {
426                // Skip the player that initiated the event (if it's the container variable)
427                if (player === container) return;
428
429                const progressBar = player.querySelector('.progress-bar');
430                const counter = player.querySelector('.play-counter');
431
432                if (progressBar && counter) {
433                    const progress = (currentTime / duration) * 100;
434                    progressBar.style.width = `${progress}%`;
435                    counter.textContent = `${formatTime(currentTime)} / ${formatTime(duration)}`;
436                }
437            });
438        }
439    });
440
441    /**
442     * Convert iTunes duration format (HH:MM:SS or MM:SS or SS) to seconds
443     * @param {string} duration - The duration string from iTunes
444     * @returns {number} Duration in seconds
445     */
446    const convertITunesDurationToSeconds = (duration) => {
447        if (!duration) return 0;
448
449        const parts = duration.split(':').map(Number);
450
451        if (parts.length === 3) {
452            // HH:MM:SS format
453            return parts[0] * 3600 + parts[1] * 60 + parts[2];
454        } else if (parts.length === 2) {
455            // MM:SS format
456            return parts[0] * 60 + parts[1];
457        } else if (parts.length === 1) {
458            // SS format
459            return parts[0];
460        }
461
462        return 0;
463    }
464
465    const initializeScript = () => {
466        // Your existing code that was inside DOMContentLoaded
467        enhanceAudioElements('audio');
468    };
469
470// Run the function once when the DOM is fully loaded
471    document.addEventListener('DOMContentLoaded', initializeScript);
472
473// Observe the body for added nodes and initialize the script for new elements
474    const observer = new MutationObserver((mutations) => {
475        mutations.forEach((mutation) => {
476            if (mutation.addedNodes.length > 0) {
477                initializeScript();
478            }
479        });
480    });
481
482
483    // if (!document.querySelector('.streamplayer'))
484    observer.observe(document.body, {childList: true, subtree: true});
485});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.