audio_player-1.0.x-dev/js/single-audio/skin-seventeen.js
js/single-audio/skin-seventeen.js
(function ($, Drupal, drupalSettings) {
'use strict';
// --- Common Helper Functions (moved outside the loop) ---
/**
* Formats time from seconds to MM:SS format.
* @param {number} seconds - The time in seconds.
* @returns {string} Formatted time string (MM:SS).
*/
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${minutes}:${secs < 10 ? '0' : ''}${secs}`;
}
// --- Drupal Behavior ---
Drupal.behaviors.audio_player_skin_seventeen = {
attach: function (context, settings) {
once('audio_player_skin_seventeen', '.audio-player.skin-seventeen', context).forEach(function (playerElement) {
const $player = $(playerElement);
// Select elements using jQuery
const $audio = $player.find('.audio-player-audio');
const audio = $audio[0]; // Get the native DOM element for media events and properties
const $playPauseBtn = $player.find('.audio-player-play-pause-btn');
const $playIcon = $playPauseBtn.find('.audio-player-play-icon');
const $pauseIcon = $playPauseBtn.find('.audio-player-pause-icon');
const $rewindBtn = $player.find('.audio-player-rewind-btn');
const $fastForwardBtn = $player.find('.audio-player-fast-forward-btn');
const $muteUnmuteBtn = $player.find('.audio-player-mute-unmute-btn');
const $volumeUpIcon = $muteUnmuteBtn.find('.audio-player-volume-up-icon');
const $volumeMuteIcon = $muteUnmuteBtn.find('.audio-player-volume-mute-icon');
const $volumeSlider = $player.find('.audio-player-volume-slider');
const $progressBar = $player.find('.audio-player-progress-bar');
const $bufferedBar = $player.find('.audio-player-buffered-bar');
const $progressContainer = $player.find('.audio-player-progress-container');
const $currentTimeSpan = $player.find('.audio-player-current-time');
const $totalTimeSpan = $player.find('.audio-player-total-time');
const $songNameText = $player.find('.audio-player-song-name');
const $playbackSpeedSelect = $player.find('.audio-player-playback-speed-select');
// Visualization elements
const $visualizationContainer = $player.find('.audio-player-visualization-container');
const $visualizationCanvas = $player.find('.audio-player-visualization-canvas');
const visualizationCanvas = $visualizationCanvas[0]; // Get native DOM element for canvas context
const visualizationCtx = visualizationCanvas ? visualizationCanvas.getContext('2d') : undefined;
const element = $player.find('.audio-player-progress-bar')[0]; // get the first element directly
let primaryColor = ''; // Declare primaryColor
if (element) {
primaryColor = window.getComputedStyle(element).backgroundColor;
}
let audioContext;
let sourceNode;
let analyser;
let frequencyDataArray;
let bufferLength;
let isPlaying = 0;
let initialVolume = audio.volume;
let isSeeking = 0;
let animationFrameId; // For audio visualization
// let rainAnimationId; // For rain effect
// --- Helper Functions ---
// formatTime function is already defined outside the behavior and is fine as is.
function updateBufferedBar() {
const duration = audio.duration;
if (!isNaN(duration) && duration > 0) {
if (audio.buffered.length > 0) {
const bufferedEnd = audio.buffered.end(audio.buffered.length - 1);
const bufferedPercent = (bufferedEnd / duration) * 100;
$bufferedBar.css('width', bufferedPercent + '%');
} else {
$bufferedBar.css('width', '0%');
}
} else {
$bufferedBar.css('width', '0%');
}
}
// --- Web Audio API Setup ---
function setupAudioContext() {
if (!audioContext && visualizationCanvas) { // Ensure visualizationCanvas exists
audioContext = new(window.AudioContext || window.webkitAudioContext)();
sourceNode = audioContext.createMediaElementSource(audio);
analyser = audioContext.createAnalyser();
analyser.fftSize = 1024; // More data points for a smoother visual
analyser.minDecibels = -90;
analyser.maxDecibels = -10;
analyser.smoothingTimeConstant = 0.8; // Adjust for smoothness vs. responsiveness
bufferLength = analyser.frequencyBinCount; // This will be 512 for fftSize 1024
frequencyDataArray = new Uint8Array(bufferLength);
sourceNode.connect(analyser);
analyser.connect(audioContext.destination);
}
}
// --- NEW: Dynamic Bricks Visualization Function ---
function drawDynamicBricksViz() {
if (!visualizationCtx || !analyser || !frequencyDataArray) {
return; // Exit if context or analyser not set up
}
analyser.getByteFrequencyData(frequencyDataArray);
visualizationCtx.clearRect(0, 0, visualizationCanvas.width, visualizationCanvas.height);
// Number of bricks (reduce for chunkier look, increase for finer)
const numBricks = 64;
const brickWidth = visualizationCanvas.width / numBricks;
const minBrickHeight = 2; // Minimum height for a brick
// Aggregate frequency data for fewer, wider bricks
const step = Math.floor(bufferLength / numBricks);
for (let i = 0; i < numBricks; i++) {
let sum = 0;
for (let j = 0; j < step; j++) {
sum += frequencyDataArray[i * step + j] || 0;
}
const average = sum / step;
// Scale height from 0-255 to canvas height, ensuring minimum height
const brickHeight = Math.max(minBrickHeight, (average / 255) * visualizationCanvas.height);
// Calculate opacity based on intensity
const opacity = 0.3 + (average / 255) * 0.7; // From 0.3 to 1.0
// Create a gradient for each brick for a more dynamic look
const gradient = visualizationCtx.createLinearGradient(0, visualizationCanvas.height, 0, visualizationCanvas.height - brickHeight);
gradient.addColorStop(0, primaryColor); // Solid color at the base
gradient.addColorStop(1, primaryColor); // Fading to transparent green at the top
visualizationCtx.fillStyle = gradient;
// Draw from the bottom up, with a small gap
visualizationCtx.fillRect(i * brickWidth, visualizationCanvas.height - brickHeight, brickWidth - 2, brickHeight);
}
}
// --- Main Animation Loop (for audio viz) ---
function animateVisualization() {
animationFrameId = requestAnimationFrame(animateVisualization);
if (isPlaying) {
drawDynamicBricksViz(); // Call the new visualization function
} else {
if (visualizationCtx) { // Check if context exists before clearing
// visualizationCtx.clearRect(0, 0, visualizationCanvas.width, visualizationCanvas.height);
}
}
}
// --- Core Playback Controls ---
function togglePlayPause() {
if (!audioContext) {
setupAudioContext();
if (visualizationCanvas) { // Check if canvas element exists
visualizationCanvas.width = visualizationCanvas.offsetWidth;
visualizationCanvas.height = visualizationCanvas.offsetHeight;
animateVisualization(); // Start audio visualization loop
}
}
if (audioContext && audioContext.state === 'suspended') {
audioContext.resume();
}
if (isPlaying) {
audio.pause();
$playIcon.show();
$pauseIcon.hide();
// $visualizationContainer.removeClass('audio-player-visible');
} else {
audio.play();
$playIcon.hide();
$pauseIcon.show();
$visualizationContainer.addClass('audio-player-visible');
}
isPlaying = !isPlaying;
}
$playPauseBtn.on('click', togglePlayPause);
$rewindBtn.on('click', () => {
audio.currentTime = Math.max(0, audio.currentTime - 10);
});
$fastForwardBtn.on('click', () => {
audio.currentTime = Math.min(audio.duration, audio.currentTime + 10);
});
function toggleMuteUnmute() {
if (audio.muted) {
audio.muted = 0;
audio.volume = initialVolume;
$volumeSlider.val(initialVolume);
$volumeUpIcon.show();
$volumeMuteIcon.hide();
} else {
initialVolume = audio.volume;
audio.muted = 1;
audio.volume = 0;
$volumeSlider.val(0);
$volumeUpIcon.hide();
$volumeMuteIcon.show();
}
}
$muteUnmuteBtn.on('click', toggleMuteUnmute);
$volumeSlider.on('input', (e) => {
audio.volume = e.target.value;
if (audio.volume == 0) {
audio.muted = 1;
$volumeUpIcon.hide();
$volumeMuteIcon.show();
} else {
audio.muted = 0;
$volumeUpIcon.show();
$volumeMuteIcon.hide();
initialVolume = audio.volume;
}
});
$audio.on('timeupdate', () => {
if (!isSeeking && !isNaN(audio.duration) && audio.duration > 0) {
const progressPercent = (audio.currentTime / audio.duration) * 100;
$progressBar.css('width', progressPercent + '%');
}
$currentTimeSpan.text(formatTime(audio.currentTime));
updateBufferedBar();
});
// Function to update metadata once loaded
const updateMetadata = () => {
$totalTimeSpan.text(formatTime(audio.duration));
const audioSrc = audio.src;
const fileName = audioSrc.substring(audioSrc.lastIndexOf('/') + 1);
$songNameText.text(decodeURIComponent(fileName.replace(/\.[^/.] + $ / , "")));
$volumeSlider.val(audio.volume);
initialVolume = audio.volume;
updateBufferedBar();
};
// Trigger when the page loads or when audio metadata is loaded
$(audio).on('loadedmetadata', updateMetadata); // When audio metadata is loaded
updateMetadata();
$audio.on('progress', updateBufferedBar);
$audio.on('loadeddata', updateBufferedBar);
$progressContainer.on('mousedown', (e) => {
isSeeking = 1;
if (isPlaying) {
audio.pause();
// $visualizationContainer.removeClass('audio-player-visible');
}
const clickX = e.offsetX;
const width = $progressContainer.outerWidth();
const seekTime = (clickX / width) * audio.duration;
if (!isNaN(seekTime) && isFinite(seekTime)) {
audio.currentTime = seekTime;
}
$progressBar.css('width', ((audio.currentTime / audio.duration) * 100) + '%');
});
$(document).on('mouseup', () => {
if (isSeeking) {
isSeeking = 0;
if (isPlaying) {
audio.play();
$visualizationContainer.addClass('audio-player-visible');
}
if (isPlaying) {
$playIcon.hide();
$pauseIcon.show();
} else {
$playIcon.show();
$pauseIcon.hide();
}
}
});
$playbackSpeedSelect.on('change', (e) => {
audio.playbackRate = parseFloat(e.target.value);
});
$audio.on('ended', () => {
isPlaying = 0;
$playIcon.show();
$pauseIcon.hide();
audio.currentTime = 0;
$progressBar.css('width', '0%');
$bufferedBar.css('width', '0%');
$currentTimeSpan.text('0:00');
// $visualizationContainer.removeClass('audio-player-visible');
if (visualizationCtx) { // Check if context exists before clearing
visualizationCtx.clearRect(0, 0, visualizationCanvas.width, visualizationCanvas.height);
}
});
// Initial setup for audio (removed rain related calls)
updateBufferedBar();
});
}
};
})(jQuery, Drupal, drupalSettings);