Music Player
- Description: Online music player
- Software: HTML, CSS, and Javascript with ChatGPT Assistance
- Test Location: Music Player 2
- Transcript: Located Here
Distributing My Music Using Apache and Reason
Over the years, I have produced a substantial collection of original instrumentals using the Digital Audio Workstation (DAW) known as Reason. This software, celebrated for its powerful virtual instruments and flexibility, has been my primary tool for music production. Despite the quality of these tracks, I have not pursued label representation or actively self-published this music. As of 2024, these creations remain a personal archive—a hidden collection of my creative journey.
Innovative Distribution with Apache
While I have not shared my work through conventional channels, I found a creative way to make it available to friends and collaborators. By using the Apache HTTP Server, I turned a simple folder of audio files into an accessible, web-like interface. This approach allowed users to browse and download my tracks effortlessly.
How It Worked
- Hosting the Files: I uploaded the music files to a folder on my Stereomedia server.
- Customizing Apache: Using Apache's configuration settings, I enabled directory browsing and applied options to make the folder appear as a functional web interface.
- Seamless Presentation: Without needing to create a custom HTML index page, the server displayed the folder contents in an organized and user-friendly format.
Reflection and Future Potential
This innovative solution exemplifies how technical know-how can enhance creative projects. It also opens doors to exciting possibilities, such as:
- Enhanced Distribution: Creating a custom web interface for streaming or downloading music.
- Automation: Using tools to generate metadata and playlists for better organization.
- AI Integration: Employing AI to categorize, remix, or optimize tracks before sharing.
The Apache Code
<Directory "/path/to/music/folder">
Options +Indexes
IndexOptions +FancyIndexing +IconsAreLinks
AllowOverride None
Require all granted
</Directory>
This project demonstrates the intersection of creativity and technical expertise, offering a glimpse into how innovative solutions can reshape the way music is shared and experienced.
# ENABLE DIRECTORY VIEWS
Options +Indexes
# SET INDEX OPTIONS
IndexOptions IgnoreCase FancyIndexing FoldersFirst NameWidth=* DescriptionWidth=* SuppressHTMLPreamble
# SET DISPLAY ORDER
IndexOrderDefault Descending Name
# SPECIFY HEADER FILE
HeaderName header.html
# SPECIFY FOOTER FILE
ReadmeName footer.html
# SPECIFIC FILE ICONS
AddIcon (MP3,arrow.png) .mp3
IndexOptions SuppressLastModified
IndexOptions SuppressSize
IndexOptions SuppressDescription
RewriteBase /
RewriteRule ^.* - [F,L]
IndexIgnore *.html *.txt *.png *.ttf *.otf *.gif
IndexIgnore ..
And the results were a page that appeared like this:
Which looked cool. Honestly, it really wasn't bad, but it didn't have any play buttons, it would shut off if the screen went to sleep, and it was, in fact, just a folder in a directory without an index file that was dressed up a little bit to look a little more cooler, by modifying the way that the server interacted with browsers.
Taking it a step further, I asked ChatGPT to create a web based music player, sort of like my own little miniature Spotify, which would only play music that was in the folder. That's in the main transcript, located here. Within that, here is some of the code.
It even made me a folder structure illustration.
/your-project-folder
│
├── public
│ ├── index.html
│ ├── styles.css
│ ├── script.js
│ └── audio
│ ├── track1.mp3
│ ├── track2.mp3
│ └── default-track.mp3
│
└── server.js
Example Code - Music Player
Prompts
- Ok so I have a folder that populates music on my server. The files are randomly named based on title. The player would have access to repeating individual tracks, and displaying all of the available tracks within the folder. Is that doable?
- i want to repeat tile the background image and include a header image as well. is that possible?
- would it be possible to include a customized background image, and is this player responsive?
- dark mode, but can it also have a visualizer element as well?
Code
const audio = document.getElementById('audio');
const trackItems = document.querySelectorAll('.track-item');
const shuffleBtn = document.getElementById('shuffleBtn');
const repeatBtn = document.getElementById('repeatBtn');
const playPauseBtn = document.getElementById('playPauseBtn');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const progressBar = document.getElementById('progressBar');
const playIcon = document.getElementById('playIcon');
const pauseIcon = document.getElementById('pauseIcon');
const loopBtn = document.getElementById('loopBtn');
let currentTrackIndex = 0;
let isShuffling = false;
let isRepeating = false;
let isLooping = false;
let wakeLock = null;
let audioContext;
// Function to request audio playback for mobile devices
function requestAudioPlayback() {
audio.play().catch(error => {
console.error("Playback failed:", error);
});
}
// Function to ensure AudioContext is active
function ensureAudioContextActive() {
if (audioContext && audioContext.state === 'suspended') {
audioContext.resume().catch(error => console.error("AudioContext resume failed:", error));
}
}
// Function to play audio with AudioContext check and Wake Lock
async function playAudio() {
ensureAudioContextActive(); // Ensure AudioContext is active
await audio.play().catch(error => console.error("Playback failed:", error));
playIcon.classList.add('hidden'); // Hide play icon
pauseIcon.classList.remove('hidden'); // Show pause icon
requestWakeLock();
}
// Function to play the selected track
function playTrack(track) {
trackItems.forEach(item => item.classList.remove('active'));
audio.src = 'audio/' + track;
requestAudioPlayback();
const currentTrackItem = Array.from(trackItems).find(item => item.textContent === track.replace('.mp3', ''));
if (currentTrackItem) currentTrackItem.classList.add('active');
currentTrackIndex = Array.from(trackItems).indexOf(currentTrackItem);
}
// Play/Pause toggle functionality with wake lock and AudioContext
playPauseBtn.addEventListener('click', () => {
if (audio.paused) {
playAudio();
} else {
audio.pause();
playIcon.classList.remove('hidden');
pauseIcon.classList.add('hidden');
releaseWakeLock();
}
});
// Move to the previous track
prevBtn.addEventListener('click', () => {
playPreviousTrack();
});
// Move to the next track
nextBtn.addEventListener('click', () => {
playNextTrack();
});
// Function to play the previous track
function playPreviousTrack() {
currentTrackIndex = (currentTrackIndex - 1 + trackItems.length) % trackItems.length;
playTrack(trackItems[currentTrackIndex].textContent + '.mp3');
}
// Function to play the next track
function playNextTrack() {
currentTrackIndex = (currentTrackIndex + 1) % trackItems.length;
playTrack(trackItems[currentTrackIndex].textContent + '.mp3');
}
// Update progress bar as the track plays
audio.addEventListener('timeupdate', () => {
progressBar.value = (audio.currentTime / audio.duration) * 100 || 0;
});
// Seek to a position in the track
progressBar.addEventListener('input', () => {
audio.currentTime = (progressBar.value / 100) * audio.duration;
});
// Repeat and shuffle functionality
repeatBtn.addEventListener('click', () => {
isRepeating = !isRepeating;
repeatBtn.classList.toggle('active', isRepeating);
});
shuffleBtn.addEventListener('click', () => {
isShuffling = !isShuffling;
shuffleBtn.classList.toggle('active', isShuffling);
});
// Loop functionality
loopBtn.addEventListener('click', () => {
isLooping = !isLooping;
audio.loop = isLooping;
loopBtn.classList.toggle('active', isLooping);
loopBtn.textContent = isLooping ? 'Disable Loop' : 'Enable Loop';
});
// Automatically play the next track after the current one ends
audio.addEventListener('ended', () => {
if (isRepeating) {
audio.currentTime = 0;
playAudio();
} else {
playNextTrack();
}
});
// Initialize AudioContext and Wake Lock
function initializeAudioContext() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
const source = audioContext.createMediaElementSource(audio);
source.connect(audioContext.destination);
}
}
// Request wake lock to keep playback alive during screen lock
async function requestWakeLock() {
if ('wakeLock' in navigator) {
try {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => console.log('Wake Lock released'));
console.log('Wake Lock is active');
} catch (err) {
console.error(`Wake Lock request failed: ${err.name}, ${err.message}`);
}
}
}
// Release the wake lock
function releaseWakeLock() {
if (wakeLock !== null) {
wakeLock.release().then(() => {
wakeLock = null;
});
}
}
// Event listener for visibility change
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
ensureAudioContextActive(); // Reactivate AudioContext if necessary
} else if (wakeLock !== null && document.visibilityState === 'hidden') {
releaseWakeLock();
}
});
// Set up AudioContext on page load
window.addEventListener('load', () => {
initializeAudioContext();
});
// Release wake lock on page unload
window.addEventListener('unload', () => {
releaseWakeLock();
});
Visualizer Code (javascript)
Prompt
All i asked it for was a cool oscilloscope style visualizer with a blue tone that would generate visuals along with whatever music was being played.Code
// Select the canvas and set up context
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const analyser = audioCtx.createAnalyser();
// Configurations
analyser.fftSize = 2048;
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const WIDTH = canvas.width;
const HEIGHT = canvas.height;
// Connect the audio element to the analyser
const audioSource = audioCtx.createMediaElementSource(audio);
audioSource.connect(analyser);
analyser.connect(audioCtx.destination);
// Function to draw the waveform
function draw() {
requestAnimationFrame(draw);
analyser.getByteTimeDomainData(dataArray);
// Clear canvas
ctx.fillStyle = "#111";
ctx.fillRect(0, 0, WIDTH, HEIGHT);
// Oscilloscope style
ctx.lineWidth = 2;
ctx.strokeStyle = "#00aaff"; // Cool blue color
ctx.beginPath();
let sliceWidth = WIDTH / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
let v = dataArray[i] / 128.0;
let y = v * HEIGHT / 2;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
x += sliceWidth;
}
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
}
// Trigger audio context on play
audio.addEventListener('play', () => {
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
draw();
});