WIP
This commit is contained in:
parent
9d5259767c
commit
25fcefcb50
68 changed files with 2982 additions and 307 deletions
39
raw/gui/base.json
Normal file
39
raw/gui/base.json
Normal 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!"
|
||||
}
|
||||
]
|
||||
|
|
@ -53,7 +53,8 @@
|
|||
],
|
||||
"type": "slider",
|
||||
"max": 255,
|
||||
"min": 0
|
||||
"min": 0,
|
||||
"value": 255
|
||||
},
|
||||
{
|
||||
"group": "light",
|
||||
|
|
|
|||
|
|
@ -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
73
raw/gui/wave2d.json
Normal 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!"
|
||||
}
|
||||
]
|
||||
37
raw/javascript/FileHandler.js
Normal file
37
raw/javascript/FileHandler.js
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
});
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
106
raw/javascript/playerConfigHandler.js
Normal file
106
raw/javascript/playerConfigHandler.js
Normal 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
64
raw/javascript/select.js
Normal 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
23
raw/javascript/startup.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
class Sphere extends Visual {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "Sphere";
|
||||
}
|
||||
|
||||
draw() {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
73
raw/javascript/visuals/wave2d.js
Normal file
73
raw/javascript/visuals/wave2d.js
Normal 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
54
raw/scss/_config.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -65,6 +65,8 @@ playlist {
|
|||
&-number {
|
||||
padding: 5px 10px 5px 5px;
|
||||
width: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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
4
raw/worker/app.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
const tagger = new Tagger(self),
|
||||
eventHandler = new EventHandler(self);
|
||||
|
||||
tagger.init();
|
||||
97
raw/worker/database.js
Normal file
97
raw/worker/database.js
Normal 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);
|
||||
}
|
||||
}
|
||||
30
raw/worker/eventHandler.js
Normal file
30
raw/worker/eventHandler.js
Normal 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
86
raw/worker/id3.js
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue