This commit is contained in:
Maurice Grönwoldt 2020-08-06 23:44:37 +02:00
commit 25fcefcb50
68 changed files with 2982 additions and 307 deletions

39
raw/gui/base.json Normal file
View file

@ -0,0 +1,39 @@
[
{
"group": "",
"name": "showSeekbar",
"showName": "Show Seekbar",
"type": "checkbox",
"value": true,
"tooltip": "Showing SeekBar at the Bottom of the Screen"
},
{
"group": "",
"name": "showPlaying",
"showName": "Show Playing",
"type": "checkbox",
"value": true,
"tooltip": "Showing \"Now Playing\" Notification"
},
{
"group": "",
"name": "showPlayingTime",
"showName": "Now Playing Display Time",
"type": "slider",
"value": 1000,
"max": 5000,
"min": 500,
"tooltip": "How long should the Now Playing Notification shown",
"stepSize": 100,
"dataType": "int"
},
{
"group": "",
"name": "seekColor",
"showName": "SeekBar Color",
"type": "color",
"value": "#ffffff",
"dataType": "rgb",
"tooltip": "SeekBar Color!"
}
]

View file

@ -53,7 +53,8 @@
],
"type": "slider",
"max": 255,
"min": 0
"min": 0,
"value": 255
},
{
"group": "light",

View file

@ -1 +1,72 @@
{}
[
{
"group": "",
"name": "fftSize",
"showName": "FFT-Size",
"options": [
2048,
4096,
8192,
16384
],
"value": 4096,
"type": "select",
"tooltip": "How Many Items should the FFT Capture and Render",
"dataType": "int"
},
{
"group": "rotation",
"name": [
"X",
"Y",
"Z"
],
"props": [
"x",
"y",
"z"
],
"type": "slider",
"max": 360,
"min": -360,
"value": 0,
"dataType": "int"
},
{
"group": "rotation",
"name": [
"X-Inc",
"Y-Inc",
"Z-Inc"
],
"props": [
"x-inc",
"y-inc",
"z-inc"
],
"type": "slider",
"max": 1,
"min": -1,
"value": 0,
"stepSize": 0.01,
"dataType": "float"
},
{
"group": "",
"name": "baseColor",
"showName": "Base Color",
"type": "color",
"value": "#0089ff",
"dataType": "rgb",
"tooltip": "Base Color!"
},
{
"group": "",
"name": "gradientToColor",
"showName": "Second Color",
"type": "color",
"value": "#ff0000",
"dataType": "rgb",
"tooltip": "Second Color!"
}
]

73
raw/gui/wave2d.json Normal file
View file

@ -0,0 +1,73 @@
[
{
"group": "",
"name": "fftSize",
"showName": "FFT-Size",
"options": [
2048,
4096,
8192,
16384,
32768
],
"value": 16384,
"type": "select",
"tooltip": "How Many Items should the FFT Capture and Render",
"dataType": "int"
},
{
"group": "rotation",
"name": [
"X",
"Y",
"Z"
],
"props": [
"x",
"y",
"z"
],
"type": "slider",
"max": 360,
"min": -360,
"value": 0,
"dataType": "int"
},
{
"group": "rotation",
"name": [
"X-Inc",
"Y-Inc",
"Z-Inc"
],
"props": [
"x-inc",
"y-inc",
"z-inc"
],
"type": "slider",
"max": 1,
"min": -1,
"value": 0,
"stepSize": 0.01,
"dataType": "float"
},
{
"group": "",
"name": "baseColor",
"showName": "Base Color",
"type": "color",
"value": "#0089ff",
"dataType": "rgb",
"tooltip": "Base Color!"
},
{
"group": "",
"name": "gradientToColor",
"showName": "Second Color",
"type": "color",
"value": "#ff0000",
"dataType": "rgb",
"tooltip": "Second Color!"
}
]

View file

@ -0,0 +1,37 @@
// Handler around the Playlist file to keep track on the ID3 and more!
class AudioPlayerFile {
constructor(file, index) {
this.file = file;
this.name = this.getName();
this.id3 = null;
this.index = index;
}
getName() {
let name = this.file.name.split(".");
name.pop();
name = name.join(".");
return name;
}
getID3Tag(force) {
if (!force && this.id3 !== null) {
return this.id3;
}
eventHandler.sendData('getData', {
file: this.file,
name: this.name,
index: this.index,
force: force === true
});
return {
title: this.name,
artist: 'VA',
}
}
getAudioName() {
let tag = this.getID3Tag();
return template.parseTemplate('audio-information', tag);
}
}

View file

@ -4,32 +4,51 @@ const shaderHandler = new ShaderHandler(null),
visual = new VisualDrawer(),
template = new Template(),
player = new Player(),
vConf = new Config(),
pConf = new Config();
vConf = new Config("visual"),
pConf = new Config("player"),
worker = new Worker('/out/js/worker.min.js'),
startup = new Startup(),
eventHandler = new EventHandler(),
playerConf = new PlayerConfigHandler();
let c = null,
gl = null;
let c, gl, cInfo, ctx;
worker.addEventListener('message', e => {
if (e.data.status === 'startup') {
startup.moduleLoaded(e.data.cmd);
return;
}
eventHandler.handleEvent(e);
});
window.addEventListener('startupFin', e => {
setTimeout(e => {
$('.loading-screen').remove();
}, 100)
})
async function startUP() {
pConf.loadConfigByName('default');
c = document.body.querySelector('#c'),
gl = c.getContext("webgl2");
c = $('#c'),
gl = c.getContext("webgl2"),
cInfo = $('#cInfo'),
ctx = cInfo.getContext('2d');
if (!gl) {
alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2");
return false;
}
shaderHandler.setGL(gl)
await shaderHandler.loadArray(["wave", "sphere", "water"], 'shaders/');
await shaderHandler.loadArray(["wave", "sphere", "water", "wave2d"], 'shaders/');
await NotificationHandler.instance.init();
await audioHandler.init();
await player.init();
await visual.init();
await gui.init();
await imageUploader.init();
await playerConf.init();
await initHandler();
}
startUP().then(r => {
setTimeout(e => {
$('.loading-screen').remove();
}, 100)
startup.moduleLoaded('startup');
});

View file

@ -45,8 +45,13 @@ class AudioHandler {
this.analyser.smoothingTimeConstant = float;
}
loadSong(src) {
let self = this;
loadSong(file) {
if (!file) {
NotificationHandler.createNotification("Sorry!<br> Currently no Song is uploaded!", "error", 2000);
return false;
}
let self = this,
src = file.file;
if (self.lastSong) {
URL.revokeObjectURL(self.lastSong);
}
@ -55,8 +60,12 @@ class AudioHandler {
this.start().catch(alert);
}
this.audioFile.play().then(e => {
if (pConf.get("showPlaying", "true")) {
NotificationHandler.createNotification("<span class='now-playing'>Now Playing:</span>" + file.getAudioName(), "info", pConf.get("showPlayingTime", 1000));
}
window.dispatchEvent(new CustomEvent('playSong'));
}).catch(e => {
NotificationHandler.createNotification(e.message, "error", 1000);
player.nextSong();
});
}

View file

@ -1,7 +1,11 @@
class Config {
constructor() {
static allConfigs = {};
constructor(type) {
this.config = {};
this.name = ''
this.type = type;
Config.allConfigs[type] = this;
}
loadConfigByName(name) {
@ -35,4 +39,10 @@ class Config {
}
return value;
}
reset() {
NotificationHandler.createNotification(`CONFIG REQUEST SUCCESS FOR ${this.type}`, "success", 2000);
this.config = {};
this.save();
}
}

View file

@ -1,3 +1,31 @@
class EventHandler {
constructor() {
this.events = {};
}
addEvent(name, cb) {
this.events[name] = cb;
}
sendData(name, data) {
worker.postMessage({
cmd: name,
data: data
});
}
handleEvent(event) {
let data = event.data;
if (!data.cmd) {
return;
}
if (this.events[data.cmd]) {
this.events[data.cmd](data.data);
}
}
}
async function initHandler() {
let body = $('body');
$('.playlist.menu-icon').addEventListener('click', e => {
@ -31,6 +59,72 @@ async function initHandler() {
});
window.addEventListener('playSong', setActiveOnPlaylist);
$('.upload-image').addEventListener('click', imageUploader.renderModal.bind(imageUploader));
body.addDelegatedEventListener('click', '.readAll', e => {
let playlist = player.playlist.list;
for (let i = 0; i < playlist.length; i++) {
playlist[i].getID3Tag(true);
}
})
body.addDelegatedEventListener('input', '.input-range input[type="range"]', (e, el) => {
let current = $('.current', el.parentNode);
current.innerText = el.value;
});
body.addDelegatedEventListener('input', 'input[type="color"]', (e, el) => {
let parent = el.parentNode;
$('.colorBlob', parent).style.backgroundColor = el.value;
})
body.addDelegatedEventListener('click', '.visual-item', (e, el) => {
visual.switch(el.dataset.id || 'wave');
$('modal-content .visuals .active').removeClass('active');
el.addClass('active');
})
body.addDelegatedEventListener('input', 'section.base input', (e, el) => {
if (el.type === 'checkbox') {
pConf.set(el.name, el.checked);
} else {
setValue(el.name, el.value, pConf, el.dataset.type);
}
pConf.save();
})
body.addDelegatedEventListener('input', 'section.visual input', (e, el) => {
if (el.type === 'checkbox') {
vConf.set(el.name, el.checked);
} else {
setValue(el.name, el.value, vConf, el.dataset.type);
}
vConf.save();
})
body.addDelegatedEventListener('click', '.button[data-action]', (e, el) => {
switch (el.dataset.action) {
case 'resetVConf':
vConf.reset();
setTimeout(e => {
playerConf.handleById();
}, 30);
break;
case 'makeModalTransparent':
$('#modal').toggleClass('lightMode')
break;
}
})
}
function setValue(name, value, conf, type) {
switch (type) {
case 'float':
value = parseFloat(value);
break;
case 'int':
value = parseInt(value);
break;
}
conf.set(name, value);
}
function setActiveOnPlaylist(e) {
@ -47,6 +141,8 @@ function setActiveOnPlaylist(e) {
function toggleShuffle() {
let active = player.playlist.isShuffle;
$('#shuffle').toggleCheck('active', active);
let status = active ? 'enabled' : 'disabled';
NotificationHandler.createNotification("Shuffle: " + status, "info", 500);
}
function togglePlayButton(status) {

View file

@ -1,5 +1,7 @@
// most of the functions are from https://webglfundamentals.org/webgl/resources/m4.js! but i doesnt want to use them all and make some adjustment to them!
class TDUtils {
static lastMatrix = {m: null};
static multiply(a, b) {
let b00 = b[0];
let b01 = b[1];
@ -93,9 +95,10 @@ class TDUtils {
return dst;
}
static xRotation(angleInRadians) {
let c = Math.cos(angleInRadians);
let s = Math.sin(angleInRadians);
static xRotation(angle) {
angle = TDUtils.degToRad(angle);
let c = Math.cos(angle);
let s = Math.sin(angle);
return [
1, 0, 0, 0,
@ -105,9 +108,10 @@ class TDUtils {
];
}
static yRotation(angleInRadians) {
let c = Math.cos(angleInRadians);
let s = Math.sin(angleInRadians);
static yRotation(angle) {
angle = TDUtils.degToRad(angle);
let c = Math.cos(angle);
let s = Math.sin(angle);
return [
c, 0, -s, 0,
@ -117,9 +121,10 @@ class TDUtils {
];
}
static zRotation(angleInRadians) {
let c = Math.cos(angleInRadians);
let s = Math.sin(angleInRadians);
static zRotation(angle) {
angle = TDUtils.degToRad(angle);
let c = Math.cos(angle);
let s = Math.sin(angle);
return [
c, s, 0, 0,
@ -300,8 +305,6 @@ class TDUtils {
]
}
static lastMatrix = {m: null};
static getMatrix(fov, aspect, near, far, camAngle, radius) {
let lMat = this.lastMatrix,
u = TDUtils;
@ -330,4 +333,14 @@ class TDUtils {
}
return true;
}
static updateRotate(rotation, def) {
let value = vConf.get(rotation, def) + vConf.get(rotation + '-inc', 0)
if (value > 360) {
value -= 360;
} else if (value < -360) {
value += 360;
}
vConf.set(rotation, value);
}
}

View file

@ -3,23 +3,21 @@ class GUI {
this.data = {};
this.modal = new Modal();
// load first vis window!
await this.loadForVis();
await template.loadArray([
'playlist-item',
'playlist',
'playlist-footer'
'playlist-footer',
'audio-information',
'inputs/color',
'inputs/input',
'inputs/slider',
'inputs/switch',
'inputs/select',
'inputs/option'
]);
this.initDropZone();
}
async loadForVis() {
let c = visual.c,
d = this.data[c];
if (d == null) {
this.data[c] = await fetch("out/gui/" + c + ".json").then((r) => r.json());
}
}
initDropZone() {
let items = 'drag dragstart dragend dragover dragenter dragleave drop'.split(' ');
items.forEach(el => {
@ -39,6 +37,109 @@ class GUI {
};
}
// create config Inputs from JSON
class GUIHelper {
static fromJSON(json, conf) {
let data = [];
for (let item of json) {
switch (item.type) {
case 'slider':
data.push(GUIHelper.createSliders(item, conf));
break;
case 'color':
data.push(GUIHelper.createColorPicker(item, conf));
break;
case 'checkbox':
data.push(GUIHelper.createCheckbox(item, conf));
break;
case 'input':
data.push(GUIHelper.createInputField(item, conf));
break;
case 'select':
data.push(GUIHelper.createSelect(item, conf));
break;
case 'button':
data.push(GUIHelper.createButton(item, conf));
break;
default:
console.error(`Unknown Type: ${item.type}`);
}
}
return data.join(" ");
}
static createSliders(data, conf) {
let content = "";
if (typeof data.name === "object") {
for (let i = 0; i < data.name.length; i++) {
let newData = {};
Object.assign(newData, data);
newData.showName = data.name[i];
newData.name = GUIHelper.richName(data, data.props[i]);
content += GUIHelper.createSlider(newData, conf);
}
} else {
content = GUIHelper.createSlider(data, conf);
}
return content;
}
static createSlider(data, conf) {
let newData = {};
Object.assign(newData, data);
newData.value = conf.get(newData.name, newData.value);
return template.parseTemplate('inputs/slider', newData)
}
static createColorPicker(data, conf) {
let newData = {};
Object.assign(newData, data);
newData.value = conf.get(newData.name, newData.value);
return template.parseTemplate('inputs/color', newData)
}
static createCheckbox(data, conf) {
let newData = {};
Object.assign(newData, data);
newData.value = conf.get(newData.name, newData.value) ? 'checked' : '';
return template.parseTemplate('inputs/switch', newData)
}
static createInputField(data, conf) {
let newData = {};
Object.assign(newData, data);
newData.value = conf.get(newData.name, newData.value);
return template.parseTemplate('inputs/input', newData)
}
static createSelect(data, conf) {
let newData = {};
Object.assign(newData, data);
newData.value = conf.get(newData.name, newData.value);
let options = '';
for (let i = 0; i < newData.options.length; i++) {
options += template.parseTemplate('inputs/option', {
value: newData.options[i]
})
}
newData.options = options;
newData.event = 'visualConf';
newData.conf = conf.type;
return template.parseTemplate('inputs/select', newData)
}
static richName(data, name) {
if (data.group !== "") {
return data.group + "-" + name
}
return name;
}
static createButton(item, conf) {
return `<div class='button spaced' data-action="${item.action}">${item.name}</div>`
}
}
class Modal {
constructor() {
let self = this;
@ -53,6 +154,7 @@ class Modal {
}
renderModal(title, content, footer) {
$('#modal').removeClass('lightMode')
this.currentModal = title;
this.renderHeader(title);
this.renderContent(content);
@ -70,8 +172,8 @@ class Modal {
}
renderFooter(footer) {
let con = $('modal-footer', this.modal);
con.innerHTML = footer;
let con = $('modal-footer .inner', this.modal);
con.innerHTML = footer || "by VersusTuneZ";
}
closeModal() {

View file

@ -53,10 +53,6 @@ class ImageUploader {
let body = $('body');
body.style.backgroundImage = 'url(' + this.image + ')';
body.style.backgroundColor = this.color;
let blob = $('#colorBlob');
if (blob) {
blob.style.backgroundColor = this.color;
}
}
getRealImage() {

View file

@ -1,5 +1,77 @@
class Notification {
constructor() {
class NotificationHandler {
static instance = new NotificationHandler();
constructor() {
this.outer = $('.notification');
this.notifications = [];
}
async init() {
await template.loadTemplate('notification');
}
static createNotification(message, type, time) {
time = parseInt(time || "3000");
let handler = NotificationHandler.instance,
not = new Notification(message, type, time);
handler.notifications.push(not);
not.show();
return not;
}
}
class Notification {
constructor(message, type, time) {
this.outer = NotificationHandler.instance.outer;
this.message = message;
this.type = type;
this.time = time;
this.isRemoved = false;
}
async show() {
let self = this,
endless = self.time === -1;
self.item = create('div');
self.item.addClass('notification-item, ' + self.type);
if (endless) {
self.type += ' endless';
}
self.updateContent(self.message);
this.outer.appendChild(self.item);
if (!endless) {
setTimeout(this.remove.bind(this), self.time)
}
}
async remove() {
if (this.isRemoved) {
return;
}
this.isRemoved = true;
this.outer.removeChild(this.item);
let not = NotificationHandler.instance.notifications,
index = not.indexOf(this);
not.splice(index, 1);
delete this;
}
updateContent(message) {
let self = this,
isEndless = self.time === -1,
data = {
message: message,
time: isEndless ? 1000 : self.time + 1,
type: self.type,
}
this.item.innerHTML = template.parseTemplate('notification', data);
}
updateMessageOnly(message) {
let item = $('.message', this.item);
if (item) {
item.innerHTML = message;
}
}
}

View file

@ -5,18 +5,18 @@ class Player {
nextSong() {
let next = this.playlist.getNext();
audioHandler.loadSong(next.file);
audioHandler.loadSong(next);
}
prevSong() {
let next = this.playlist.getPrevious();
audioHandler.loadSong(next.file);
audioHandler.loadSong(next);
}
playStop() {
if (!audioHandler.lastSong) {
let next = this.playlist.getCurrent();
audioHandler.loadSong(next.file);
audioHandler.loadSong(next);
return;
}
let audioFile = audioHandler.audioFile;
@ -31,7 +31,7 @@ class Player {
playByID(number) {
this.playlist.index = number;
let next = this.playlist.getCurrent();
audioHandler.loadSong(next.file);
audioHandler.loadSong(next);
}
}
@ -46,13 +46,16 @@ class Playlist {
this.isShuffle = false;
$('body').addDelegatedEventListener('change', 'input[type="file"]', this.changeFiles.bind(this));
$('body').addDelegatedEventListener('click', '.pagination .item', this.handlePagination.bind(this));
eventHandler.addEvent('id3-request', this.handle.bind(this));
eventHandler.addEvent('id3-request-force', this.forceID3.bind(this));
}
shuffle() {
// only shuffle if more then 2 elements are in
let len = this.list.length;
if (len < 3) {
this.shuffled = this.list;
this.shuffled = [0, 1, 2];
return;
}
// the current-list need to be shuffled...
for (let i = 0; i < len; i++) {
@ -62,40 +65,40 @@ class Playlist {
}
swap(a, b) {
this.shuffled[a] = this.list[b];
this.shuffled[b] = this.list[a];
this.shuffled[a] = b;
this.shuffled[b] = a;
}
getNext() {
let items = this.isShuffle ? this.shuffled : this.list,
let items = this.list,
len = items.length - 1,
next = this.index + 1;
if (next > len) {
next = 0;
}
this.index = next;
return items[next];
return items[this.getRealIndex()];
}
getPrevious() {
let items = this.isShuffle ? this.shuffled : this.list,
let items = this.list,
len = items.length - 1,
next = this.index - 1;
if (next < 0) {
next = len;
}
this.index = next;
return items[next];
return items[this.getRealIndex()];
}
getCurrent() {
let items = this.isShuffle ? this.shuffled : this.list;
return items[this.index];
return this.list[this.getRealIndex()];
}
// on new upload... this has to be an array!
setPlaylist(files) {
this.index = 0;
this.forceData = undefined;
this.list = files;
this.shuffle();
}
@ -112,6 +115,9 @@ class Playlist {
}
renderPagination(page) {
if (page === undefined) {
page = this.page;
}
let length = this.list.length,
maxSite = Math.ceil(length / PAGINATIONLIMIT) - 1;
if (page < 0) {
@ -128,12 +134,12 @@ class Playlist {
e = length;
}
if (length > 0) {
let items = this.isShuffle ? this.shuffled : this.list;
let items = this.list;
for (let i = s; i < e; i++) {
let obj = {
index: i.toString(),
nr: i + 1,
title: items[i].name,
title: items[this.getRealIndex(i)].getAudioName(),
active: !audioHandler.audioFile.paused && i === this.index ? 'active' : ''
}
data += template.parseTemplate("playlist-item", obj);
@ -165,21 +171,57 @@ class Playlist {
let i = 0;
for (let file of el.files) {
if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null) {
let name = file.name.split(".");
name.pop();
name = name.join(".");
files.push({
file: file,
name: name,
index: i++
});
let audioFile = new AudioPlayerFile(file, i++);
files.push(audioFile);
}
}
this.setPlaylist(files);
if (files.length > 0) {
NotificationHandler.createNotification("Songs added successfully!<br> Songs: " + files.length, "success", 3000);
this.renderPagination(0);
} else {
alert("No Valid AudioFiles found!");
NotificationHandler.createNotification("File Upload failed!", "error", 3000);
}
}
getRealIndex(index) {
if (index === undefined) {
index = this.index;
}
if (this.isShuffle) {
return this.shuffled[index];
}
return index;
}
handle(data) {
let index = data.index;
if (data.status === "waiting") {
return;
}
this.list[index].id3 = data;
if (this.timeout) {
window.clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.renderPagination.bind(this), 100);
}
forceID3(data) {
let self = this;
if (!self.forceData) {
self.forceData = {};
self.forceNotification = NotificationHandler.createNotification("TagReader -> 0 / " + self.list.length, "info", -1);
}
let index = data.index;
if (data.status === "waiting") {
return;
}
self.list[index].id3 = data;
self.forceData[index] = true;
let leng = Object.keys(self.forceData).length;
this.forceNotification.updateMessageOnly("TagReader -> " + leng + " / " + self.list.length);
if (leng === self.list.length) {
self.forceNotification.remove()
}
}
}

View file

@ -0,0 +1,106 @@
class PlayerConfigHandler {
async init() {
await template.loadArray([
'config/nav',
'config/content',
'config/visualitem'
]);
this.last = 'base';
$('.settings-icon').addEventListener('click', this.open.bind(this));
$('modal-content').addDelegatedEventListener('click', '.config-nav .item', this.navHandler.bind(this));
}
open() {
if (this.content === undefined) {
let content = template.parseTemplate('config/nav', {});
content += template.parseTemplate('config/content', {content: ""});
this.content = content;
}
gui.modal.renderModal('Settings', this.content, "by VersusTuneZ");
this.handleById();
gui.modal.showModal();
}
navHandler(e, el) {
this.last = el.dataset.id;
this.handleById();
}
handleById() {
let id = this.last;
new VisualConfig(id === 'visual', id === 'base');
let active = $('.config-nav .item.active'),
current = $('.config-nav .item[data-id="' + id + '"]');
if (active) {
active.removeClass('active');
}
if (current) {
current.addClass('active');
}
}
}
class VisualConfig {
static visualTemplates = {};
constructor(showVisual, renderBase) {
this.content = $('modal-content .config-content');
if (showVisual) {
this.renderVisualConfig(visual.c);
} else {
if (renderBase) {
this.renderBase();
} else {
this.renderVisuals();
}
}
}
renderVisuals() {
let keys = Object.keys(visual.visuals),
content = '<section class="visuals">';
for (let i = 0; i < keys.length; i++) {
content += template.parseTemplate('config/visualitem', {
title: visual.visuals[keys[i]].name,
id: keys[i],
active: keys[i] === visual.c ? 'active' : ''
})
}
content += '</div>';
this.content.innerHTML = content;
}
async renderBase() {
let data = await this.loadVisualConfig('base'),
div = create('section');
div.addClass('base');
div.innerHTML = GUIHelper.fromJSON(data, pConf);
this.content.innerHTML = div.outerHTML;
}
// the name loads the json and handle it!
async renderVisualConfig(name) {
let data = await this.loadVisualConfig(name, vConf),
div = create('section');
div.addClass('visual');
div.innerHTML = GUIHelper.fromJSON(data, vConf);
div.innerHTML += GUIHelper.createButton({
action: "resetVConf",
name: "Reset Visual Config"
});
div.innerHTML += GUIHelper.createButton({
action: "makeModalTransparent",
name: "toggle Modal Opacity"
})
this.content.innerHTML = div.outerHTML;
}
async loadVisualConfig(name) {
let tem = VisualConfig.visualTemplates;
if (!tem[name]) {
//load config and save it
tem[name] = await fetch('/out/gui/' + name + ".json").then((res) => res.json());
}
return tem[name];
}
}

64
raw/javascript/select.js Normal file
View file

@ -0,0 +1,64 @@
(function () {
const body = $('body');
body.addDelegatedEventListener('click', 'custom-select .label', (e, el) => {
let parent = el.parentNode;
let options = $$('custom-option', parent),
optionsDiv = $('custom-options', parent);
if (parent.hasClass('open')) {
optionsDiv.style.maxHeight = '';
parent.removeClass('open');
} else {
let sum = 0;
options.forEach(function (element) {
sum += element.offsetHeight;
});
optionsDiv.style.maxHeight = sum + 'px';
parent.addClass('open');
}
})
body.addDelegatedEventListener('click', 'custom-select custom-option', (e, el) => {
let select = el.closest('custom-select'),
input = $('input', select);
$$('custom-option.active').forEach(activeEl => {
activeEl.removeClass('active');
})
el.addClass('active');
if (input) {
input.value = el.dataset.value || el.innerText;
$('.label', select).innerText = el.innerText;
select.removeClass('open');
el.parentNode.style.maxHeight = '';
window.dispatchEvent(new CustomEvent('selectChanged', {
detail: {
select: select,
event: select.dataset.event,
value: input.value,
name: input.name
}
}));
}
})
window.addEventListener('selectChanged', (e) => {
if (e.detail.event === 'visualConf') {
e.preventDefault();
e.stopPropagation();
handleVisualConf(e.detail);
}
})
function handleVisualConf(e) {
try {
let value = e.value,
config = Config.allConfigs[e.select.dataset.conf];
if (e.name === 'fftSize') {
value = parseInt(e.value);
visual.visuals[visual.c].updateFFT(value);
}
config.set(e.name, value);
config.save();
} catch (e) {
console.error(e);
}
}
})()

23
raw/javascript/startup.js Normal file
View file

@ -0,0 +1,23 @@
class Startup {
constructor() {
this.modules = {
'startup': false,
'id3-ready': false
};
}
moduleLoaded(name) {
this.modules[name] = true
this.allModulesLoaded();
}
allModulesLoaded() {
for (let module in this.modules) {
if (!this.modules[module]) {
return false;
}
}
window.dispatchEvent(new CustomEvent('startupFin'));
return true;
}
}

View file

@ -28,7 +28,11 @@ class Template {
templateEx.lastIndex++;
}
let key = m[0];
d = d.replace(key, data[m[1]] || "")
let value = data[m[1]];
if (value === undefined || value === null) {
value = "";
}
d = d.replace(key, value)
}
return d;
}

View file

@ -159,7 +159,12 @@ Node.prototype.addDelegatedEventListener = function (type, aim, cb) {
} else {
let parent = target.closest(aim);
if (parent) {
cb(event, parent);
try {
cb(event, parent);
} catch (e) {
NotificationHandler.createNotification("FATAL ERROR WITHIN HANDLER!", "error", 1000);
//nothing!
}
}
}
})
@ -249,23 +254,12 @@ function append(to, array) {
}
}
function loadFromJSONToVisualData(useKeys) {
fetch('/audio-vis/out/showCase.json').then((res) => {
return res.json()
}).then(e => {
let floatArray;
if (useKeys) {
let keys = Object.keys(e);
floatArray = new Float32Array(keys.length);
for (let i = 0; i < keys.length; i++) {
floatArray[i] = e[keys[i]];
}
} else {
floatArray = new Float32Array(e.length);
for (let i = 0; i < e.length; i++) {
floatArray[i] = e[i];
}
}
visual.visuals[visual.c].data = floatArray;
})
function hexToRgb(hex) {
hex = hex.replace("#", "");
let bigint = parseInt(hex, 16),
r = (bigint >> 16) & 255,
g = (bigint >> 8) & 255,
b = bigint & 255;
return [r / 255, g / 255, b / 255];
}

View file

@ -2,10 +2,13 @@ class Visual {
constructor() {
this.data = []; //for drawing
this.dataArray = [];
this.name = "Default";
}
updateData() {
}
updateFFT(fftSize) {
}
draw() {
@ -18,14 +21,23 @@ class Visual {
class VisualDrawer {
constructor() {
this.visuals = {
"sphere": new Sphere(),
//"sphere": new Sphere(),
"wave": new Wave(),
"water": new Water()
"wave2d": new Wave2D(),
//"water": new Water()
}
this.lastMainColor = {
base: '#-1',
color: [0, 0, 0]
};
this.lastSecondColor = {
base: '#-1',
color: [0, 0, 0]
}
}
init() {
this.switch('wave');
this.switch(pConf.get("visual", "wave2d"));
this.updateLoop();
}
@ -34,20 +46,38 @@ class VisualDrawer {
this.c = visual;
vConf.loadConfigByName(this.c);
this.visuals[this.c].setup();
pConf.set("visual", this.c);
pConf.save();
}
}
updateLoop() {
let self = this;
let pro = shaderHandler.use(self.c);
let vis = self.visuals[self.c];
let pro = shaderHandler.use(self.c);
this.updateSeekbar();
this.prepare(pro);
vis.updateData();
this.prepare();
vis.draw(pro);
requestAnimationFrame(self.updateLoop.bind(self))
}
prepare() {
updateSeekbar() {
cInfo.width = window.innerWidth;
cInfo.height = window.innerHeight;
let audioFile = audioHandler.audioFile;
ctx.clearRect(0, 0, cInfo.width, cInfo.height);
if (!audioFile.paused && pConf.get("showSeekbar", true)) {
//show seekbar
let dur = audioFile.duration,
cur = audioFile.currentTime,
percent = cur / dur * cInfo.width;
ctx.fillStyle = pConf.get("seekColor", '#fff');
ctx.fillRect(0, c.height - 10, percent, c.height);
}
}
prepare(pro) {
c.width = window.innerWidth;
c.height = window.innerHeight;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
@ -56,5 +86,29 @@ class VisualDrawer {
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.enable(gl.CULL_FACE);
// u_baseColor || u_maxColor
this.setColor(pro);
}
setColor(program) {
let baseColor = gl.getUniformLocation(program, "u_baseColor"),
maxColor = gl.getUniformLocation(program, "u_maxColor"),
self = this,
mainColor = self.lastMainColor,
secondColor = self.lastSecondColor;
this.updateColor('lastMainColor', 'baseColor');
this.updateColor('lastSecondColor', 'gradientToColor');
gl.uniform3fv(baseColor, mainColor.color);
gl.uniform3fv(maxColor, secondColor.color);
}
updateColor(index, col) {
let color = this[index],
value = vConf.get(col, '#ffffff')
if(value !== color.base) {
color.color = hexToRgb(value);
color.base = value;
}
}
}

View file

@ -1,4 +1,9 @@
class Sphere extends Visual {
constructor() {
super();
this.name = "Sphere";
}
draw() {
}

View file

@ -1,5 +1,10 @@
//animate Water the way like the Audio is Coming... 256FFT-Size max!
class Water extends Visual {
constructor() {
super();
this.name = "Water";
}
draw() {
}

View file

@ -1,10 +1,11 @@
// 3D Audio-Waves -> maybe also 2D?
class Wave extends Visual {
constructor() {
super();
this.name = "3D Wave";
}
updateData() {
//only for debug! remove pls
if (window.stopUpdate) {
return;
}
let data = audioHandler.getFloatArray();
let add = 2 / data.length,
x = -1;
@ -36,6 +37,7 @@ class Wave extends Visual {
gl.enableVertexAttribArray(position);
gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0);
gl.drawArrays(vConf.get("waveForm", gl.TRIANGLES), 0, this.data.length / 3);
this.afterDraw();
}
rotate(program) {
@ -46,27 +48,36 @@ class Wave extends Visual {
0, 0, 1, 0,
0, 0, 0, 1
]
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("xRotate", 0)));
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("yRotate", 0)));
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("zRotate", 0)));
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("rotation-x", 10)));
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("rotation-y", 50)));
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("rotation-z", -30)));
let rotate = gl.getUniformLocation(program, "u_matrix");
gl.uniformMatrix4fv(rotate, false, matrix);
}
setup() {
audioHandler.fftSize(16384)
this.data = new Float32Array(16384 * 9);
vConf.get("zRotate", TDUtils.degToRad(-30));
vConf.get("yRotate", TDUtils.degToRad(50));
vConf.get("xRotate", TDUtils.degToRad(10));
this.updateFFT(vConf.get('fftSize', 4096));
vConf.get("rotation-z", -30);
vConf.get("rotation-y", 50);
vConf.get("rotation-x", 10);
}
updateFFT(fftSize) {
audioHandler.fftSize(fftSize);
this.data = new Float32Array(fftSize * 9);
}
prepare(program) {
this.position = gl.getAttribLocation(program, "a_position");
this.color = gl.getUniformLocation(program, "u_color");
let lightPos = gl.getUniformLocation(program, "u_lightPos"),
matrix = gl.getUniformLocation(program, "u_matrix");
let lightPos = gl.getUniformLocation(program, "u_lightPos");
gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56]));
//gl.uniformMatrix4fv(matrix, false, TDUtils.getMatrix(90, c.width / c.height, 1, 2000, 200, 200));
}
afterDraw() {
TDUtils.updateRotate('rotation-x', 10);
TDUtils.updateRotate('rotation-y', 50);
TDUtils.updateRotate('rotation-z', -30);
vConf.save();
}
}

View file

@ -0,0 +1,73 @@
class Wave2D extends Visual {
constructor() {
super();
this.name = "2D Wave";
}
updateData() {
let data = audioHandler.getFloatArray();
let add = 2 / data.length,
x = -1;
let outerLoop = 0;
for (let i = 0; i < data.length; i++) {
//first
this.data[outerLoop] = x;
this.data[outerLoop + 1] = data[i];
this.data[outerLoop + 2] = data[i];
outerLoop += 3;
x += add;
}
}
draw(program) {
this.prepare(program);
let position = this.position,
positionBuffer = gl.createBuffer();
this.rotate(program);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.data), gl.DYNAMIC_DRAW);
let vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(position);
gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0);
gl.drawArrays(vConf.get("waveForm", gl.LINE_STRIP), 0, this.data.length / 3);
this.afterDraw();
}
rotate(program) {
let matrix = [
1, 0, 0, 0,
0, 0.6, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("rotation-x", 0)));
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("rotation-y", 0)));
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("rotation-z", 0)));
let rotate = gl.getUniformLocation(program, "u_matrix");
gl.uniformMatrix4fv(rotate, false, matrix);
}
setup() {
this.updateFFT(vConf.get('fftSize', 16384));
}
updateFFT(fftSize) {
audioHandler.fftSize(fftSize);
this.data = new Float32Array(fftSize * 3);
}
prepare(program) {
this.position = gl.getAttribLocation(program, "a_position");
this.color = gl.getUniformLocation(program, "u_color");
let lightPos = gl.getUniformLocation(program, "u_lightPos");
gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56]));
}
afterDraw() {
TDUtils.updateRotate('rotation-x', 0);
TDUtils.updateRotate('rotation-y', 0);
TDUtils.updateRotate('rotation-z', 0);
vConf.save();
}
}

54
raw/scss/_config.scss Normal file
View file

@ -0,0 +1,54 @@
.config-nav {
display: flex;
flex-direction: row;
background-color: $nav;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
div {
padding: 10px 5px;
cursor: pointer;
user-select: none;
border-bottom: 1px solid transparent;
&.active {
border-color: $active;
}
&:hover {
background-color: rgba(0, 0, 0, .4);
}
}
}
.config-content {
padding: 1em;
min-height: 200px;
}
.config-content .visuals {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
.visual-item {
display: flex;
align-items: center;
justify-content: center;
padding: 1em;
min-width: 100px;
min-height: 100px;
background-color: $darker;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
transition: .5s;
cursor: pointer;
&:hover {
background-color: $nav;
}
&.active {
border: 1px solid $primary;
}
}
}

View file

@ -1,6 +1,10 @@
#c {
width: 100%;
height: 100%;
canvas {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
pointer-events: none;
}
group {
@ -118,15 +122,45 @@ group-input {
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
&.hide {
display: none !important;
}
}
modal-footer playlist {
flex-direction: row-reverse;
flex-wrap: wrap;
.pagination {
margin-left: auto;
}
}
#image-upload form {
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
width: 90%;
margin: 10px auto;
}
.audio-item {
display: flex;
flex-direction: column;
.artist {
font-size: .75em;
color: #dcdcdc;
}
}
.now-playing {
font-size: .8em;
display: block;
margin-bottom: 10px;
}
.menus {
z-index: 10;
}

View file

@ -98,11 +98,15 @@
}
.input {
width: 90%;
width: 100%;
position: relative;
display: inline-block;
margin-top: 1rem;
background-color: transparent;
&-range {
margin-bottom: 20px;
}
}
input:focus + .input:after {
@ -148,20 +152,20 @@ input:focus + .input:after {
.floating-label input:focus ~ .input-label,
.floating-label input:valid ~ .input-label {
transform: translateY(-0.72rem);
color: #ff0089;
color: $active;
font-size: .7rem;
}
.floating-label input:valid ~ .input-label {
transform: translateY(-0.72rem);
color: #3949ab;
color: $second;
font-size: .7rem;
}
.focus {
content: '';
width: 0;
background-color: #ff0a8e;
background-color: $second;
position: absolute;
bottom: 0;
left: 0;
@ -180,6 +184,10 @@ input:focus ~ .focus {
}
switch {
display: flex;
flex-direction: row;
padding: 5px;
input {
position: absolute;
appearance: none;
@ -190,6 +198,10 @@ switch {
}
}
span {
margin-left: 10px;
}
label {
display: block;
border-radius: 10px;
@ -202,7 +214,7 @@ switch {
&:after {
content: '';
background-color: #ff3232;
background-color: $primary;
position: absolute;
top: 2px;
left: 2px;
@ -234,7 +246,7 @@ input[type="color"] {
}
}
#colorBlob {
.colorBlob {
display: block;
width: 100%;
height: 20px;
@ -242,12 +254,124 @@ input[type="color"] {
}
.button {
text-align: center;
user-select: none;
border-radius: 5px;
border: 1px solid $primary;
padding: 0.5em 1em;
cursor: pointer;
transition: .5s;
&.spaced {
margin-bottom: 10px;
}
&:hover {
border-color: $second;
border-radius: 7px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
}
}
.input {
.max, .min, .current {
font-size: .8em;
color: $second;
position: absolute;
bottom: -1rem;
}
.current {
display: block;
width: 100%;
text-align: center;
}
.min {
left: 0;
}
.max {
right: 0;
}
}
custom-select {
width: auto;
min-width: 200px;
display: block;
label {
color: #ff0089;
font-size: .7rem;
}
input {
display: none;
}
.label {
padding: 10px 30px 10px 10px;
cursor: pointer;
position: relative;
transition: all .3s;
box-shadow: inset 0 0 3px rgba(94, 94, 94, 1);
&::after {
content: '';
border: solid #dcdcdc;
border-width: 0 3px 3px 0;
display: inline-block;
padding: 3px;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transition: all .3s ease-in-out;
position: absolute;
right: 10px;
top: calc(50% - 6px);
}
}
custom-options {
max-height: 0;
overflow: hidden;
transition: all .5s ease;
font-size: .9em;
display: block;
border: 1px solid transparent;
}
custom-option {
display: block;
width: 100%;
padding: 10px 5px;
cursor: pointer;
box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, .08);
&:hover {
background-color: $primary;
color: #fff;
}
}
custom-option.active {
font-weight: bold;
}
}
custom-select.open {
.label {
&::after {
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
top: calc(50% - 3px);
}
background-color: rgba(0, 0, 0, .1);
}
custom-options {
max-height: none;
border: 1px solid $nav !important;
}
}

View file

@ -7,6 +7,10 @@
border-radius: 15px;
overflow: hidden;
&.lightMode {
background-color: rgba(0, 0, 0, .1);
}
div {
position: unset;
}
@ -45,5 +49,11 @@
modal-footer {
display: block;
box-shadow: 0 -3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
.inner {
padding: 5px;
width: calc(100% - 40px);
margin: 0 auto;
}
}
}

View file

@ -1,6 +1,117 @@
.notification {
right: 0;
bottom: 0;
right: 10px;
top: 0;
height: 100%;
display: flex;
width: 320px;
flex-direction: column-reverse;
width: 90%;
max-width: 400px;
pointer-events: none;
.notification-item {
margin-top: 10px;
}
div {
position: unset;
padding: unset;
}
div.notification-item {
position: relative;
}
}
.notification-item {
width: 100%;
border-radius: 5px;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
overflow: hidden;
&.success {
background-color: $success;
}
&.error {
background-color: $error;
}
&.warning {
background-color: $warning;
}
&.info {
background-color: $info;
}
.message {
padding: 1em;
}
}
.notification-item .fade-bar {
animation: fadeOut ease-in-out 3000ms;
height: 100%;
width: 100%;
position: absolute;
top: 0;
z-index: -1;
opacity: 0.4;
transform-origin: left;
&:after {
content: '';
z-index: 1;
bottom: 0;
height: 4px;
width: 100%;
position: absolute;
background-color: #fff;
}
&.endless {
animation: endlessFade ease-in-out 500ms infinite;
}
&.success {
background-color: $successBorder;
}
&.error {
background-color: $errorBorder;
}
&.warning {
background-color: $warningBorder;
}
&.info {
background-color: $primary;
}
}
@keyframes fadeOut {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
@keyframes endlessFade {
0% {
transform: scaleX(1);
transform-origin: right;
}
49% {
transform-origin: right;
}
50% {
transform: scaleX(0);
transform-origin: left;
}
100% {
transform: scaleX(1);
}
}

View file

@ -65,6 +65,8 @@ playlist {
&-number {
padding: 5px 10px 5px 5px;
width: 50px;
display: flex;
align-items: center;
}
&:hover {

View file

@ -1,6 +1,16 @@
$bg: #303030;
$darker: #212121;
$nav: #1b1b1b;
$primary: #3949ab;
$second: #ff0089;
$active: #5ff507;
$active: #5ff507;
$info: rgba(71,73,171,.6);
$success: rgba(74, 177, 11,.6);
$error: rgba(255,50,50,.6);
$warning: rgba(255, 177, 89, 0.6);
$successBorder: #60ff00;
$errorBorder: #fa0000;
$warningBorder: #ff7700;

View file

@ -19,6 +19,8 @@ html, body {
background-repeat: no-repeat;
background-position: center;
background-size: cover;
width: 100vw;
height: 100vh;
}
div {
@ -50,4 +52,5 @@ div {
@import "controls";
@import "playlist";
@import "modal";
@import "notification";
@import "notification";
@import "config";

4
raw/worker/app.js Normal file
View file

@ -0,0 +1,4 @@
const tagger = new Tagger(self),
eventHandler = new EventHandler(self);
tagger.init();

97
raw/worker/database.js Normal file
View file

@ -0,0 +1,97 @@
class Database {
constructor(name, version) {
this.isInit = false;
this.name = name;
this.version = version;
this.errored = false;
this.db;
this.prepareDB();
}
async prepareDB() {
if (this.isInit || this.errored) {
return;
}
let req = this.db = indexedDB.open(this.name, this.version);
req.onerror = DatabaseHandler.onError.bind(this);
req.onsuccess = DatabaseHandler.onSuccess.bind(this);
req.onupgradeneeded = DatabaseHandler.onUpgrade.bind(this);
req.onblocked = DatabaseHandler.onBlocked.bind(this);
}
async set(key, data, store) {
data['key'] = key;
return await this.run('put', data, store);
}
async get(key, store) {
return await this.run('get', key, store);
}
async remove(key, store) {
return await this.run('delete', key, store);
}
check() {
return !(!this.isInit || this.errored);
}
async getTX(store) {
return await this.db.transaction([store], "readwrite")
}
async getObjectStore(tx, store) {
return await tx.objectStore(store)
}
async run(action, key, store) {
if (this.check()) {
let tx = await this.getTX(store);
let obj = await this.getObjectStore(tx, store);
let data = await this.request(obj[action](key));
await tx.complete
return await data;
}
return null;
}
request(req) {
return new Promise((resolve, reject) => {
req.onsuccess = () => resolve(req.result);
req.onerror = () => reject(req.error);
});
}
}
class DatabaseHandler {
static onError(e) {
this.errored = true;
eventHandler.sendData("databaseError", "error", e.message);
}
static onSuccess(e) {
this.db = this.db.result;
this.isInit = true;
eventHandler.sendData("databaseCreated", "success", "");
eventHandler.handleEvent({
data: {
cmd: 'dbReady-' + this.name,
data: this.db
}
});
}
static onUpgrade(e) {
eventHandler.sendData("databaseUpgradeNeeded", "info", e.message);
eventHandler.handleEvent({
data: {
cmd: 'dbUpgrade-' + this.name,
data: this.db
}
});
}
static onBlocked(e) {
eventHandler.sendData("databaseBlocked", "error", e.message);
}
}

View file

@ -0,0 +1,30 @@
class EventHandler {
constructor(worker) {
this.events = {};
this.worker = worker;
this.worker.addEventListener('message', this.handleEvent.bind(this));
}
addEvent(name, cb) {
this.events[name] = cb;
}
sendData(name, status, data) {
this.worker.postMessage({
cmd: name,
status: status,
data: data
});
}
handleEvent(event) {
let data = event.data;
if (!data.cmd) {
return;
}
if (this.events[data.cmd]) {
this.events[data.cmd](data.data);
}
}
}

86
raw/worker/id3.js Normal file
View file

@ -0,0 +1,86 @@
self.importScripts('jsmediatags.min.js');
class Tagger {
constructor(worker) {
this.db = new Database("SongLib", 1);
}
static prepareName(data) {
let name = data.name || '';
return name.replace(/[^\w\s]/gi, '').split(" ").join("")
}
init() {
eventHandler.addEvent('getData', this.getData.bind(this));
eventHandler.addEvent('removeData', this.getData.bind(this));
eventHandler.addEvent('setData', this.getData.bind(this));
eventHandler.addEvent('dbReady-SongLib', this.ready.bind(this));
eventHandler.addEvent('dbUpgrade-SongLib', this.upgrade.bind(this));
}
async getData(data) {
let key = Tagger.prepareName(data),
newData = await this.db.get(key, 'songs'),
handlerName = data.force ? 'id3-request-force' : 'id3-request';
if (newData) {
newData['index'] = data['index'];
eventHandler.sendData(handlerName, 'success', newData);
} else {
this.parseData(data, key).then(r => {
r['index'] = data['index'];
eventHandler.sendData(handlerName, 'success', r);
});
eventHandler.sendData(handlerName, 'waiting', data);
}
}
async removeData(data) {
let key = Tagger.prepareName(data),
newData = await this.db.remove(key, 'songs');
eventHandler.sendData('id3-remove', 'success', newData);
}
async setData(data, key) {
let newData = await this.db.set(key, data, 'songs');
eventHandler.sendData('id3-set', 'success', newData);
}
ready(data) {
console.log("[ID3] > Song Database Ready");
eventHandler.sendData('id3-ready', "startup", "");
}
upgrade(data) {
let db = data.result,
songs = db.createObjectStore("songs", {keyPath: 'key'});
songs.createIndex("name", "name", {unique: false});
}
//if not found in key-value storage read it! this take some time so this is async!
async parseData(data, key) {
let tag = await new Promise((resolve, reject) => {
new jsmediatags.Reader(data.file)
.read({
onSuccess: (tag) => {
resolve(tag);
},
onError: (error) => {
console.log(`[ID3] > Error Parsing Data!`);
resolve({
tags: {}
});
}
});
})
let tags = tag.tags,
values = {
title: tags.title || data.name,
artist: tags.artist || 'VA',
genre: tags.genre || 'Unknown',
year: tags.year || 1970,
key: key
};
await this.setData(values, key);
return values;
}
}