Read the statement by Michael Teeuw here.
MP3 Player
-
@sdetweil sorry to mess with your head but I have been working on this for the last 3 hours and have come up with the following that should show the folders on the front end and when any folder is clicked it should reveal the mp3 files. the issue I am facing is that I am having no errors whatsoever but the module will just not show on the front end. I am pulling my hair out now !
these are my files:
node_helper.js:
var NodeHelper = require('node_helper'); const Fs = require('fs'); module.exports = NodeHelper.create({ start: function() { console.log("Loaded MP3Player node_helper"); }, socketNotificationReceived: function(notification, payload) { var self = this; if (notification === 'SOURCE_MUSIC') { const musicPath = payload.musicPath; const extensions = payload.extensions; Fs.readdir(musicPath, (err, folders) => { if (err) { console.error("Error reading music directory:", err); self.sendSocketNotification('ERROR', {message: "Error reading music directory"}); } else { const musicData = folders.map(folder => ({ folderName: folder, songs: Fs.readdirSync(`${musicPath}/${folder}`).filter(file => self.checkExt(file, extensions)) })); self.sendSocketNotification("RETURNED_MUSIC", { musicData: musicData }); } }); } }, checkExt: function(file, ext) { return ext.some(extension => file.toLowerCase().endsWith(extension)); } });MMM-MP3Player.js:
var MP3; var substr; Module.register("MMM-MP3Player", { defaults: { songs: [], musicPath: "modules/MMM-MP3Player/music", extensions: ["mp3", "wma", "acc", "ogg"], songs: null, autoPlay: false, random: false, }, audio: null, songTitle: null, mediaPlayer: null, dataAvailable: true, curSong :0, curLength : 0, time: null, play: null, firstTime: true, substr: null, getStyles: function(){ return ["MMM-MP3Player.css", "font-awesome.css"]; }, start: function(){ MP3 = this; console.log("autoPlay configuration:", MP3.config.autoPlay); Log.info("Starting module: " + MP3.name); }, getDom: function() { var wrapper = document.createElement("div"); if (MP3.config.musicData) { const musicList = MP3.createElement("ul", "musicList", "musicList"); MP3.config.musicData.forEach(folderData => { // Folder item const folderItem = MP3.createElement("li", "folderItem", `folderItem-${folderData.folderName}`); folderItem.innerHTML = ` <span class="folderName">${folderData.folderName}</span> <i class="fa fa-chevron-down"></i> `; // Songs list within the folder const songsList = MP3.createElement("ul", "songsList", `songsList-${folderData.folderName}`); songsList.style.display = 'none'; // Initially hide the songs list folderData.songs.forEach(song => { const songItem = MP3.createElement("li", "songItem", `songItem-${song}`); songItem.innerHTML = song.substr(0, song.length - 4); songsList.appendChild(songItem); }); // Click event listeners folderItem.addEventListener('click', () => { songsList.style.display = songsList.style.display === 'none' ? 'block' : 'none'; // Toggle display folderItem.querySelector('.fa').classList.toggle('fa-chevron-down'); folderItem.querySelector('.fa').classList.toggle('fa-chevron-up'); }); songsList.addEventListener('click', (event) => { const clickedSongItem = event.target; if (clickedSongItem.classList.contains('songItem')) { const songName = clickedSongItem.innerText; const folderName = folderData.folderName; MP3.playSong(folderName, songName); } }); folderItem.appendChild(songsList); musicList.appendChild(folderItem); }); wrapper.appendChild(musicList); // Add the rest of the existing code... MP3.mediaPlayer = MP3.createElement("div", "mediaPlayer", "mediaPlayer"); MP3.audio = MP3.createElement("audio", "audioPlayer", "audioPlayer"); MP3.audio.addEventListener("loadeddata", () => { MP3.dataAvailable = true; MP3.curLength = MP3.audio.duration; MP3.updateDurationLabel(); }), MP3.audio.addEventListener("ended", () => { Log.log(" play ended") MP3.audio.currentTime = 0; if(MP3.config.autoPlay) { MP3.loadNext(MP3.config.random) } else MP3.mediaPlayer.classList.toggle("play"); }), MP3.audio.addEventListener("timeupdate", () => { MP3.updateDurationLabel(); }), MP3.mediaPlayer.appendChild(MP3.audio); // Add the rest of the controls to MP3.mediaPlayer var controls = MP3.createElement("div", "controls", false); MP3.songTitle = MP3.createElement("span", "title", "songTitle"); MP3.setCurrentSong(MP3.curSong); controls.appendChild(MP3.songTitle); var discArea = MP3.createElement("div", "discarea", false); discArea.appendChild(MP3.createElement("div", "disc", false)); var stylus = MP3.createElement("div", "stylus", false); stylus.appendChild(MP3.createElement("div", "pivot", false)); stylus.appendChild(MP3.createElement("div", "arm", false)); stylus.appendChild(MP3.createElement("div", "head", false)); discArea.appendChild(stylus); MP3.mediaPlayer.appendChild(discArea); var buttons = MP3.createElement("div", "buttons", false); // Previous Button var prev = MP3.createButton("back", "prevButton", "fa fa-backward"); prev.addEventListener("click", () => { MP3.mediaPlayer.classList.toggle("play"); MP3.dataAvailable = false; MP3.loadNext(MP3.config.random); MP3.audio.play(); MP3.play.getElementsByTagName('i')[0].className = "fa fa-pause"; }, false), buttons.appendChild(prev); // Play Button MP3.play = MP3.createButton("play", "playButton", "fa fa-play"); MP3.play.addEventListener("click", () => { MP3.mediaPlayer.classList.toggle("play"); if (MP3.audio.paused) { setTimeout(() => { MP3.audio.play(); }, 300); MP3.play.getElementsByTagName('i')[0].className = "fa fa-pause"; MP3.timer = setInterval(MP3.updateDurationLabel, 100); } else { //MP3.loadNext(MP3.config.random); MP3.play.getElementsByTagName('i')[0].className = "fa fa-play"; clearInterval(MP3.timer); MP3.audio.pause(); } }, false); buttons.appendChild(MP3.play); // Stop Button var stop = MP3.createButton("stop", "stopButton", "fa fa-stop"); stop.addEventListener("click", () => { MP3.mediaPlayer.classList.remove("play"); MP3.audio.pause(); MP3.audio.currentTime = 0; MP3.play.getElementsByTagName('i')[0].className = "fa fa-play"; MP3.updateDurationLabel(); }, false); buttons.appendChild(stop); // Next Button var next = MP3.createButton("next", "nextButton", "fa fa-forward"); next.addEventListener("click", () => { MP3.mediaPlayer.classList.toggle("play"); MP3.dataAvailable = false; MP3.loadNext(MP3.config.random); MP3.play.getElementsByTagName('i')[0].className = "fa fa-play"; }, false); buttons.appendChild(next); controls.appendChild(buttons); var subControls = MP3.createElement("div", "subControls", false); var duration = MP3.createElement("span", "duration", "currentDuration"); duration.innerHTML = "00:00" + "   "; subControls.appendChild(duration); var volumeSlider = MP3.createElement("input", "volumeSlider", "volumeSlider"); volumeSlider.type = "range"; volumeSlider.min = "0"; volumeSlider.max = "1"; volumeSlider.step = "0.01"; volumeSlider.addEventListener("input", () => { MP3.audio.volume = parseFloat(volumeSlider.value); }, false); subControls.appendChild(volumeSlider); controls.appendChild(subControls); MP3.mediaPlayer.appendChild(controls); wrapper.appendChild(MP3.mediaPlayer); } if(MP3.firstTime && MP3.config.autoPlay){ console.log("First time and autoPlay are true. Setting firstTime to false."); MP3.firstTime=false; } return wrapper; }, playSong: function(folderName, songName) { const songPath = MP3.config.musicPath + '/' + folderName + '/' + songName; MP3.audio.src = songPath; MP3.songTitle.innerHTML = songName; MP3.audio.play(); }, createElement: function(type, className, id){ var elem = document.createElement(type); if(className) elem.className = className; if(id) elem.id = id; return elem; }, createButton: function(className, id, icon){ var button = document.createElement('button'); button.className = className; button.id = id; var ico = document.createElement("i"); ico.className = icon; button.appendChild(ico); return button; }, updateDurationLabel: function() { var duration = document.getElementById('currentDuration'); if (MP3.dataAvailable && MP3.audio.duration > 0) { duration.innerText = MP3.parseTime(MP3.audio.currentTime) + " / " + MP3.parseTime(MP3.audio.duration); } else { duration.innerText = "00:00 / 00:00"; } }, parseTime: function(time){ const minutes = Math.floor(time / 60) const seconds = Math.floor(time - minutes * 60) const secondsZero = seconds < 10 ? "0" : "" const minutesZero = minutes < 10 ? "0" : "" return minutesZero + minutes.toString() + ":" + secondsZero + seconds.toString() }, setCurrentSong: function(index){ if(MP3.audio!= undefined){ MP3.audio.src = MP3.config.musicPath + '/' + MP3.config.songs[index]; MP3.songTitle.innerHTML = MP3.config.songs[index].substr(0, MP3.config.songs[index].length - 4); MP3.curSong = index; } }, loadNext: function(next){ let index=0; console.log("loadNext: Autoplay:", MP3.config.autoPlay); // Add this line for logging MP3.audio.pause(); if(next) index= (MP3.curSong + 1) % MP3.config.songs.length; else index = (MP3.curSong - 1) < 0 ? MP3.config.songs.length - 1 : MP3.curSong - 1; MP3.setCurrentSong(index); MP3.audio.play(); }, notificationReceived: function (notification, payload) { if(notification === "ALL_MODULES_STARTED") MP3.sendSocketNotification('SOURCE_MUSIC', MP3.config); }, socketNotificationReceived: function(notification, payload){ if(notification === "RETURNED_MUSIC") MP3.config.songs = payload.songs; // set the initial song index MP3.setCurrentSong(0); // paint the player MP3.updateDom(2); }, });MMM-MP3Player.css:
@import url("https://fonts.googleapis.com/css?family=Roboto"); /* Simpler, modern font */ .MMM-MP3Player .songInfo { flex: 1; /* Fill half of the available space */ padding: 20px; font-family: 'Roboto', sans-serif; } .MMM-MP3Player .songTitle { font-size: 4em; font-weight: bold; color: #333; /* Darker text */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .MMM-MP3Player .container { position: relative; /* Add this if not present */ /* ...other styles... */ } .MMM-MP3Player .songsList { position: absolute; left: 10px; /* Adjust to align correctly, can be in px or % */ top: -360px; /* Adjust to align correctly, can be in px or % */ width: 50%; /* Adjust to not exceed the container width */ max-width: calc(100% - 40px); /* Subtract total horizontal padding */ align-items: center; padding: 20px; box-sizing: border-box; font-size: 2em; font-weight: bold; color: #FFF; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; z-index: 1000; background-image: linear-gradient( to bottom, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.75) 25%, rgba(255, 255, 255, 0.6) 50%, rgba(255, 255, 255, 0.4) 75%, rgba(255, 255, 255, 0.3) 100% ), url('images/qsonglist.png'); background-size: cover; background-position: center; border-radius: 20px; border: 1px solid rgba(255, 255, 255, 0.3); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); } .MMM-MP3Player .mediaPlayer { position: absolute; right: 0; /* Adjust as needed to align to the right */ top: -130px; /* Aligns to the top of the container */ width: 50%; /* Or whatever width you require */ align-items: center; padding: 20px; box-sizing: border-box; justify-content: center; z-index: 1000; } .MMM-MP3Player .controls { width: 80%; margin-top: 20px; } .MMM-MP3Player .buttons button { background-color: #4CAF50; /* Example: Green for modern feel */ color: white; border: none; border-radius: 50%; margin-right: 10px; font-size: 1.2em; width: 50px; height: 50px; box-shadow: 0 2px 5px rgba(0,0,0,0.3); /* Adds depth */ } .MMM-MP3Player .buttons button:hover { background-color: #3e8e41; /* Darker green on hover */ cursor: pointer; }in my config.js:
I have added it this way:
{ module: "MMM-MP3Player", position: "bottom_center", "pages": {"Audio": "bottom_center"}, config: { musicPath: "modules/MMM-MP3Player/music", autoPlay: false, random: false, loopList: false, } },Can you tell me what am I doing wrong? thanks
-
@bachoo786 can you please make a git repo of this.
-
@bachoo786 what shows in the developers window?
-
@sdetweil I will .
Nothing says everything is loaded including the mp3 player module.
-
@bachoo786 well, code and data don’t match
config.js
musicPath: "modules/MMM-MP3Player/music",defaults
musicPath: "modules/MMM-MP3Player/music",getDom()
if (MP3.config.musicData) { (and other places)if you use the developers window you can step thru the code
-
@sdetweil here is the repo:
-
@bachoo786 you don’t set
if (MP3.config.musicData) { anywhere.. so getDom() returns an empty divI ‘assume’ you meant for the node_helper to send this back
but yours socketNotifcationReceived does this
if(notification === "RETURNED_MUSIC") MP3.config.songs = payload.songs; <--- songs is undefined, but musicData is there -
@sdetweil so I made a mistake, i wanted my code to look for the mp3 files automatically in the folders within the music directory.
currently I am getting an error in the developer console:
Uncaught SyntaxError: Unexpected token ',' (at MMM-MP3Player.js:184:2)its the “,” at the end of
return wrapperI am trying to have one ‘music’ folder with subfolders for each artist containing their songs. This way, the module would display artists on the frontend, and clicking an artist’s name would show the songs in their folder, and click on the song would then play the mp3 file.
feel like giving up really even though I think I am close
-
@bachoo786 what did you change?
this is a missing } probably
-
@bachoo786 well, coding is a challenge sometimes
you changed the data coming back from the node helper
the code is looking for songs
but its a nested struct in musicDataso, fix the first thing (the socketNotificationReceived)
and then the code crashes later cause you are expecting the data in songs…computers do things fast… they don’t know WHY they are doing it, so can’t ‘fix’ themselves
-
I changed from this:
if (MP3.config.musicData) { const musicList = MP3.createElement("ul", "musicList", "musicList"); MP3.config.musicData.forEach(folderData => { // Folder item const folderItem = MP3.createElement("li", "folderItem", `folderItem-${folderData.folderName}`); folderItem.innerHTML = ` <span class="folderName">${folderData.folderName}</span> <i class="fa fa-chevron-down"></i> `; // Songs list within the folder const songsList = MP3.createElement("ul", "songsList", `songsList-${folderData.folderName}`); songsList.style.display = 'none'; // Initially hide the songs list folderData.songs.forEach(song => { const songItem = MP3.createElement("li", "songItem", `songItem-${song}`); songItem.innerHTML = song.substr(0, song.length - 4); songsList.appendChild(songItem); }); // Click event listeners folderItem.addEventListener('click', () => { songsList.style.display = songsList.style.display === 'none' ? 'block' : 'none'; // Toggle display folderItem.querySelector('.fa').classList.toggle('fa-chevron-down'); folderItem.querySelector('.fa').classList.toggle('fa-chevron-up'); }); songsList.addEventListener('click', (event) => { const clickedSongItem = event.target; if (clickedSongItem.classList.contains('songItem')) { const songName = clickedSongItem.innerText; const folderName = folderData.folderName; MP3.playSong(folderName, songName); } }); folderItem.appendChild(songsList); musicList.appendChild(folderItem); });to this:
if (MP3.config.musicData) { const fs = require('fs'); const path = require('path'); const musicFolder = path.resolve(MP3.config.musicData.musicPath); const supportedExtensions = this.defaults.extensions; // Use module's default extensions fs.readdir(musicFolder, (err, files) => { if (err) { console.error("Error reading music directory:", err); // Handle the error - display message to user, etc. } else { const musicFiles = files.filter(file => supportedExtensions.includes(path.extname(file).toLowerCase())); if (musicFiles.length > 0) { const musicList = MP3.createElement("ul", "musicList"); musicFiles.forEach(musicFile => { const songItem = MP3.createElement('li', 'songItem'); songItem.innerHTML = musicFile.substr(0, musicFile.length - 4); songItem.addEventListener('click', () => { MP3.playSong(musicFile); // Assuming you want to play the song directly }); musicList.appendChild(songItem); }); -
@bachoo786 you could say, I have updated the repo, git pull to get the changes…
you need to
git add,
git commit -m and
git push
to update the repo -
@bachoo786 said in MP3 Player:
fs.readdir(musicFolder, (err, files) => {
the modulename.js that runs in the browser cannot read files directly, due to security restrictions ( any script could read ALL your files without you knowing)
this is why the node_helper exists…
-
@sdetweil said in MP3 Player:
@bachoo786 said in MP3 Player:
fs.readdir(musicFolder, (err, files) => {
the modulename.js that runs in the browser cannot read files directly, due to security restrictions ( any script could read ALL your files without you knowing)
this is why the node_helper exists…
so whats the alternative? I had this MMM-QuranPlayer module that was able to read mp3 files from the folders in the “public” directory which was located in the root folder of the module.
-
@bachoo786 I’m pretty sure the module used a node_helper
now if you make it a url, then you can fetch it thru the browser… but the server side is actually reading the file
but you could make it a file url
file:///server/path to file
in MagicMirror the server home folder is the MagicMirror folder
-
Hey @bachoo786, I’ve been following your progress with the folder nesting—it’s a tricky bit of logic to get right in the node_helper, but it’ll be worth it for the cleaner UI!
One thing I noticed while setting up my own music module is that the player looks a lot better if the MP3 metadata (ID3 tags) is actually clean. If the tags are messy, the ‘Artist’ and ‘Title’ fields on the mirror usually end up looking like a jumble of underscores and file extensions.
Since you’re organizing a big library right now, I’ve been using https://editmp3tags.com/ to quickly fix the tags in the browser before dropping them into the music folder. It’s way faster than using a heavy desktop app and helps the module display everything correctly once you get that ‘better method’ logic sorted out.
Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login