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

View file

@ -2,19 +2,22 @@ const gulp = require('gulp'),
spriteBuild = require('./task/spriteBuilder').buildIconSprites,
scss = require('./task/scss').buildCSS,
js = require('./task/js').build,
gui = require('./task/jsonMinifier').build;
gui = require('./task/jsonMinifier').build,
worker = require('./task/worker').build;
gulp.task('scss', scss);
gulp.task('js', js);
gulp.task('sprite', spriteBuild);
gulp.task('gui', gui);
gulp.task('workerJS', worker);
gulp.task('watchMe', () => {
gulp.watch('./../raw/javascript/**/*.js', gulp.series('js'));
gulp.watch('./../raw/worker/**/*.js', gulp.series('workerJS'));
gulp.watch('./../raw/scss/**/*.scss', gulp.series('scss'));
gulp.watch('./../raw/gui/**/*.json', gulp.series('gui'));
});
gulp.task('default', gulp.parallel('js', 'scss', 'sprite', 'gui'));
gulp.task('default', gulp.parallel('js', 'scss', 'sprite', 'gui', 'workerJS'));
gulp.task('watch', gulp.parallel('js', 'scss', 'sprite', 'gui', 'watchMe'));
gulp.task('watch', gulp.parallel('js', 'scss', 'sprite', 'gui', 'workerJS', 'watchMe'));

View file

@ -8,6 +8,7 @@ const visualPath = basePath + 'visuals/';
const visuals = [
visualPath + 'sphere.js',
visualPath + 'wave.js',
visualPath + 'wave2d.js',
visualPath + 'water.js',
//visualPath + 'experimental.js',
]
@ -18,6 +19,8 @@ const config = {
basePath + 'template.js',
basePath + 'gl/handler.js',
basePath + 'audio.js',
basePath + 'FileHandler.js',
basePath + 'playerConfigHandler.js',
basePath + 'player.js',
basePath + 'gui.js',
basePath + 'visual.js',
@ -26,6 +29,8 @@ const config = {
basePath + 'config.js',
...visuals,
basePath + 'eventHandler.js',
basePath + 'select.js',
basePath + 'startup.js',
basePath + 'app.js'
],
dest: __dirname + '/../../out/js'

26
build/task/worker.js Normal file
View file

@ -0,0 +1,26 @@
const terser = require('gulp-terser'),
concat = require('gulp-concat'),
rename = require('gulp-rename'),
gulp = require('gulp');
const basePath = __dirname + '/../../raw/worker/';
const config = {
src: [
basePath + 'eventHandler.js',
basePath + 'database.js',
basePath + 'id3.js',
basePath + 'app.js',
],
dest: __dirname + '/../../out/js'
};
function build() {
return gulp.src(config.src)
.pipe(concat('worker.js'))
.pipe(gulp.dest(config.dest))
.pipe(rename('worker.min.js'))
.pipe(terser())
.pipe(gulp.dest(config.dest));
}
module.exports.build = build;

View file

@ -11,57 +11,60 @@
<loader class="delay"></loader>
<span>Loading</span>
</div>
<div class="top-menu-left">
<div class="settings-icon menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-cogs"></use>
</svg>
</div>
<div class="upload-image menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-file-image"></use>
</svg>
</div>
<div class="upload menu-icon">
<label for="upload-dir">
<div class="menus">
<div class="top-menu-left">
<div class="settings-icon menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-folder-upload"></use>
<use href="out/icon-sprite.svg#fal-fa-cogs"></use>
</svg>
</label>
<input type="file" multiple directory webkitdirectory accept="audio/*" id="upload-dir">
</div>
<div class="upload-image menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-file-image"></use>
</svg>
</div>
<div class="upload menu-icon">
<label for="upload-dir">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-folder-upload"></use>
</svg>
</label>
<input type="file" multiple directory webkitdirectory accept="audio/*" id="upload-dir">
</div>
<div class="playlist menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-list-music"></use>
</svg>
</div>
</div>
<div class="playlist menu-icon">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-list-music"></use>
</svg>
<div class="controls">
<button id="previous">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-left"></use>
</svg>
</button>
<button id="play">
<svg role="img" data-name="pause" class="pause icon hide">
<use href="out/icon-sprite.svg#fal-fa-pause"></use>
</svg>
<svg role="img" data-name="play" class="icon">
<use href="out/icon-sprite.svg#fal-fa-play"></use>
</svg>
</button>
<button id="next">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-right"></use>
</svg>
</button>
<button id="shuffle">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-random"></use>
</svg>
</button>
</div>
</div>
<div class="controls">
<button id="previous">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-left"></use>
</svg>
</button>
<button id="play">
<svg role="img" data-name="pause" class="pause icon hide">
<use href="out/icon-sprite.svg#fal-fa-pause"></use>
</svg>
<svg role="img" data-name="play" class="icon">
<use href="out/icon-sprite.svg#fal-fa-play"></use>
</svg>
</button>
<button id="next">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-caret-right"></use>
</svg>
</button>
<button id="shuffle">
<svg role="img" class="icon">
<use href="out/icon-sprite.svg#fal-fa-random"></use>
</svg>
</button>
</div>
<canvas id="c"></canvas>
<canvas id="cInfo"></canvas>
<div class="grey-screen hide">
<div id="modal">
<header>
@ -77,13 +80,15 @@
sea takimata sanctus est Lorem ipsum dolor sit amet.
</modal-content>
<modal-footer>
<div class="inner">
</div>
</modal-footer>
</div>
</div>
<div class="notification">
</div>
<script src="out/js/scripts.min.js"></script>
<script src="out/js/scripts.min.js?v=1"></script>
</body>
</html>

View file

@ -196,9 +196,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,
@ -208,9 +209,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,
@ -220,9 +222,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,

1
out/gui/base.json Normal file
View file

@ -0,0 +1 @@
[{"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

@ -1 +1 @@
[{"group":"rotation","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":360,"min":0},{"group":"rotation","name":["X-Inc","Y-Inc","Z-Inc"],"props":[0,1,2],"type":"slider","max":1,"min":0,"stepSize":0.01},{"group":"rotation","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Rotate the Sphere based by Beat <br> Low-Freq = Y, Mid-Freq = Z, High-Freq = X"},{"group":"color","name":["R","G","B"],"props":["r","g","b"],"type":"slider","max":255,"min":0},{"group":"light","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":1,"min":-1,"value":0,"stepSize":0.1},{"group":"light","name":"Light","type":"slider","max":1,"min":0,"stepSize":0.05},{"group":"color","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Draw The Color Strength based on the Low-Freq"},{"group":"draw","name":"DrawMode","type":"slider","max":6,"min":0},{"group":"draw","name":"Form","type":"slider","max":4,"min":0},{"group":"draw","name":"Radius","type":"slider","max":1500,"min":20},{"group":"draw","name":"Total","type":"slider","max":200,"min":20},{"group":"draw","name":"PointSize","type":"slider","max":10,"min":1,"stepSize":0.2},{"group":"draw","name":"Dirty","type":"checkbox","value":false,"tooltip":"Full Draw or with less Points"}]
[{"group":"rotation","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":360,"min":0},{"group":"rotation","name":["X-Inc","Y-Inc","Z-Inc"],"props":[0,1,2],"type":"slider","max":1,"min":0,"stepSize":0.01},{"group":"rotation","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Rotate the Sphere based by Beat <br> Low-Freq = Y, Mid-Freq = Z, High-Freq = X"},{"group":"color","name":["R","G","B"],"props":["r","g","b"],"type":"slider","max":255,"min":0,"value":255},{"group":"light","name":["X","Y","Z"],"props":[0,1,2],"type":"slider","max":1,"min":-1,"value":0,"stepSize":0.1},{"group":"light","name":"Light","type":"slider","max":1,"min":0,"stepSize":0.05},{"group":"color","name":"by-Beat","type":"checkbox","value":true,"tooltip":"Draw The Color Strength based on the Low-Freq"},{"group":"draw","name":"DrawMode","type":"slider","max":6,"min":0},{"group":"draw","name":"Form","type":"slider","max":4,"min":0},{"group":"draw","name":"Radius","type":"slider","max":1500,"min":20},{"group":"draw","name":"Total","type":"slider","max":200,"min":20},{"group":"draw","name":"PointSize","type":"slider","max":10,"min":1,"stepSize":0.2},{"group":"draw","name":"Dirty","type":"checkbox","value":false,"tooltip":"Full Draw or with less Points"}]

View file

@ -1 +1 @@
{}
[{"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!"}]

1
out/gui/wave2d.json Normal file
View file

@ -0,0 +1 @@
[{"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!"}]

85
out/js/jsmediatags.min.js vendored Executable file
View file

@ -0,0 +1,85 @@
(function(y){"object"===typeof exports&&"undefined"!==typeof module?module.exports=y():"function"===typeof define&&define.amd?define([],y):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).jsmediatags=y()})(function(){return function f(p,q,m){function l(c,a){if(!q[c]){if(!p[c]){var b="function"==typeof require&&require;if(!a&&b)return b(c,!0);if(e)return e(c,!0);a=Error("Cannot find module '"+c+"'");throw a.code="MODULE_NOT_FOUND",a;}a=q[c]=
{exports:{}};p[c][0].call(a.exports,function(a){var b=p[c][1][a];return l(b?b:a)},a,a.exports,f,p,q,m)}return q[c].exports}for(var e="function"==typeof require&&require,d=0;d<m.length;d++)l(m[d]);return l}({1:[function(f,p,q){},{}],2:[function(f,p,q){p.exports=XMLHttpRequest},{}],3:[function(f,p,q){function m(e,d){if("function"!==typeof d&&null!==d)throw new TypeError("Super expression must either be null or a function, not "+typeof d);e.prototype=Object.create(d&&d.prototype,{constructor:{value:e,
enumerable:!1,writable:!0,configurable:!0}});d&&(Object.setPrototypeOf?Object.setPrototypeOf(e,d):e.__proto__=d)}var l=function(){function e(d,c){for(var a=0;a<c.length;a++){var b=c[a];b.enumerable=b.enumerable||!1;b.configurable=!0;"value"in b&&(b.writable=!0);Object.defineProperty(d,b.key,b)}}return function(d,c,a){c&&e(d.prototype,c);a&&e(d,a);return d}}();f=function(e){function d(c){if(!(this instanceof d))throw new TypeError("Cannot call a class as a function");var a=(d.__proto__||Object.getPrototypeOf(d)).call(this);
if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");a=!a||"object"!==typeof a&&"function"!==typeof a?this:a;a._array=c;a._size=c.length;a._isInitialized=!0;return a}m(d,e);l(d,[{key:"init",value:function(c){setTimeout(c.onSuccess,0)}},{key:"loadRange",value:function(c,a){setTimeout(a.onSuccess,0)}},{key:"getByteAt",value:function(c){if(c>=this._array.length)throw Error("Offset "+c+" hasn't been loaded yet.");return this._array[c]}}],[{key:"canReadFile",value:function(c){return Array.isArray(c)||
"function"===typeof Buffer&&Buffer.isBuffer(c)}}]);return d}(f("./MediaFileReader"));p.exports=f},{"./MediaFileReader":11}],4:[function(f,p,q){function m(d,c){if("function"!==typeof c&&null!==c)throw new TypeError("Super expression must either be null or a function, not "+typeof c);d.prototype=Object.create(c&&c.prototype,{constructor:{value:d,enumerable:!1,writable:!0,configurable:!0}});c&&(Object.setPrototypeOf?Object.setPrototypeOf(d,c):d.__proto__=c)}var l=function(){function d(c,a){for(var b=
0;b<a.length;b++){var d=a[b];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(c,d.key,d)}}return function(c,a,b){a&&d(c.prototype,a);b&&d(c,b);return c}}(),e=f("./ChunkedFileData");f=function(d){function c(a){if(!(this instanceof c))throw new TypeError("Cannot call a class as a function");var b=(c.__proto__||Object.getPrototypeOf(c)).call(this);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");b=!b||"object"!==
typeof b&&"function"!==typeof b?this:b;b._blob=a;b._fileData=new e;return b}m(c,d);l(c,[{key:"_init",value:function(a){this._size=this._blob.size;setTimeout(a.onSuccess,1)}},{key:"loadRange",value:function(a,b){var d=this,h=(this._blob.slice||this._blob.mozSlice||this._blob.webkitSlice).call(this._blob,a[0],a[1]+1),c=new FileReader;c.onloadend=function(h){h=new Uint8Array(c.result);d._fileData.addData(a[0],h);b.onSuccess()};c.onerror=c.onabort=function(a){if(b.onError)b.onError({type:"blob",info:c.error})};
c.readAsArrayBuffer(h)}},{key:"getByteAt",value:function(a){return this._fileData.getByteAt(a)}}],[{key:"canReadFile",value:function(a){return"undefined"!==typeof Blob&&a instanceof Blob||"undefined"!==typeof File&&a instanceof File}}]);return c}(f("./MediaFileReader"));p.exports=f},{"./ChunkedFileData":5,"./MediaFileReader":11}],5:[function(f,p,q){var m=function(){function f(e,d){for(var c=0;c<d.length;c++){var a=d[c];a.enumerable=a.enumerable||!1;a.configurable=!0;"value"in a&&(a.writable=!0);Object.defineProperty(e,
a.key,a)}}return function(e,d,c){d&&f(e.prototype,d);c&&f(e,c);return e}}();f=function(){function f(){if(!(this instanceof f))throw new TypeError("Cannot call a class as a function");this._fileData=[]}m(f,null,[{key:"NOT_FOUND",get:function(){return-1}}]);m(f,[{key:"addData",value:function(e,d){var c=e+d.length-1,a=this._getChunkRange(e,c);if(-1===a.startIx)this._fileData.splice(a.insertIx||0,0,{offset:e,data:d});else{var b=this._fileData[a.startIx],g=this._fileData[a.endIx];c=c<g.offset+g.data.length-
1;var h={offset:Math.min(e,b.offset),data:d};e>b.offset&&(e=this._sliceData(b.data,0,e-b.offset),h.data=this._concatData(e,d));c&&(e=this._sliceData(h.data,0,g.offset-h.offset),h.data=this._concatData(e,g.data));this._fileData.splice(a.startIx,a.endIx-a.startIx+1,h)}}},{key:"_concatData",value:function(e,d){if("undefined"!==typeof ArrayBuffer&&ArrayBuffer.isView&&ArrayBuffer.isView(e)){var c=new e.constructor(e.length+d.length);c.set(e,0);c.set(d,e.length);return c}return e.concat(d)}},{key:"_sliceData",
value:function(e,d,c){return e.slice?e.slice(d,c):e.subarray(d,c)}},{key:"_getChunkRange",value:function(e,d){for(var c,a,b=-1,g=-1,h=0,k=0;k<this._fileData.length;k++,h=k){a=this._fileData[k].offset;c=a+this._fileData[k].data.length;if(d<a-1)break;if(e<=c+1&&d>=a-1){b=k;break}}if(-1===b)return{startIx:-1,endIx:-1,insertIx:h};for(k=b;k<this._fileData.length&&!(a=this._fileData[k].offset,c=a+this._fileData[k].data.length,d>=a-1&&(g=k),d<=c+1);k++);-1===g&&(g=b);return{startIx:b,endIx:g}}},{key:"hasDataRange",
value:function(e,d){for(var c=0;c<this._fileData.length;c++){var a=this._fileData[c];if(d<a.offset)break;if(e>=a.offset&&d<a.offset+a.data.length)return!0}return!1}},{key:"getByteAt",value:function(e){for(var d,c=0;c<this._fileData.length;c++){var a=this._fileData[c].offset,b=a+this._fileData[c].data.length-1;if(e>=a&&e<=b){d=this._fileData[c];break}}if(d)return d.data[e-d.offset];throw Error("Offset "+e+" hasn't been loaded yet.");}}]);return f}();p.exports=f},{}],6:[function(f,p,q){function m(a,
b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}var l=function(){function a(a,c){for(var b=0;b<c.length;b++){var d=c[b];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(b,
d,h){d&&a(b.prototype,d);h&&a(b,h);return b}}(),e=[4,132],d=[6,134],c="Other;32x32 pixels 'file icon' (PNG only);Other file icon;Cover (front);Cover (back);Leaflet page;Media (e.g. label side of CD);Lead artist/lead performer/soloist;Artist/performer;Conductor;Band/Orchestra;Composer;Lyricist/text writer;Recording Location;During recording;During performance;Movie/video screen capture;A bright coloured fish;Illustration;Band/artist logotype;Publisher/Studio logotype".split(";");f=function(a){function b(){if(!(this instanceof
b))throw new TypeError("Cannot call a class as a function");var a=(b.__proto__||Object.getPrototypeOf(b)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!a||"object"!==typeof a&&"function"!==typeof a?this:a}m(b,a);l(b,[{key:"_loadData",value:function(a,b){var h=this;a.loadRange([4,7],{onSuccess:function(){h._loadBlock(a,4,b)}})}},{key:"_loadBlock",value:function(a,b,c){var h=this,g=a.getByteAt(b),k=a.getInteger24At(b+1,!0);
if(-1!==e.indexOf(g)){var n=b+4;a.loadRange([n,n+k],{onSuccess:function(){h._commentOffset=n;h._nextBlock(a,b,g,k,c)}})}else-1!==d.indexOf(g)?(n=b+4,a.loadRange([n,n+k],{onSuccess:function(){h._pictureOffset=n;h._nextBlock(a,b,g,k,c)}})):h._nextBlock(a,b,g,k,c)}},{key:"_nextBlock",value:function(a,b,d,c,e){var h=this;if(127<d)if(h._commentOffset)e.onSuccess();else e.onError({type:"loadData",info:"Comment block could not be found."});else a.loadRange([b+4+c,b+4+4+c],{onSuccess:function(){h._loadBlock(a,
b+4+c,e)}})}},{key:"_parseData",value:function(a,b){var h=a.getLongAt(this._commentOffset,!1)+(this._commentOffset+4);b=a.getLongAt(h,!1);h+=4;for(var d,g,e,n,x,f,t=0;t<b;t++){var w=a.getLongAt(h,!1),l=a.getStringWithCharsetAt(h+4,w,"utf-8").toString(),m=l.indexOf("=");l=[l.slice(0,m),l.slice(m+1)];switch(l[0]){case "TITLE":d=l[1];break;case "ARTIST":g=l[1];break;case "ALBUM":e=l[1];break;case "TRACKNUMBER":n=l[1];break;case "GENRE":x=l[1]}h+=4+w}this._pictureOffset&&(f=a.getLongAt(this._pictureOffset,
!0),b=this._pictureOffset+4,h=a.getLongAt(b,!0),t=b+4,b=a.getStringAt(t,h),h=t+h,t=a.getLongAt(h,!0),w=h+4,h=a.getStringWithCharsetAt(w,t,"utf-8").toString(),t=w+t+16,w=a.getLongAt(t,!0),a=a.getBytesAt(t+4,w,!0),f={format:b,type:c[f],description:h,data:a});return{type:"FLAC",version:"1",tags:{title:d,artist:g,album:e,track:n,genre:x,picture:f}}}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:4}}},{key:"canReadTagFormat",value:function(a){return"fLaC"===String.fromCharCode.apply(String,
a.slice(0,4))}}]);return b}(f("./MediaTagReader"));p.exports=f},{"./MediaTagReader":12}],7:[function(f,p,q){function m(d,c){if("function"!==typeof c&&null!==c)throw new TypeError("Super expression must either be null or a function, not "+typeof c);d.prototype=Object.create(c&&c.prototype,{constructor:{value:d,enumerable:!1,writable:!0,configurable:!0}});c&&(Object.setPrototypeOf?Object.setPrototypeOf(d,c):d.__proto__=c)}var l=function(){function d(d,a){for(var b=0;b<a.length;b++){var c=a[b];c.enumerable=
c.enumerable||!1;c.configurable=!0;"value"in c&&(c.writable=!0);Object.defineProperty(d,c.key,c)}}return function(c,a,b){a&&d(c.prototype,a);b&&d(c,b);return c}}();q=f("./MediaTagReader");f("./MediaFileReader");f=function(d){function c(){if(!(this instanceof c))throw new TypeError("Cannot call a class as a function");var a=(c.__proto__||Object.getPrototypeOf(c)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!a||"object"!==
typeof a&&"function"!==typeof a?this:a}m(c,d);l(c,[{key:"_loadData",value:function(a,b){var d=a.getSize();a.loadRange([d-128,d-1],b)}},{key:"_parseData",value:function(a,b){var d=a.getSize()-128,h=a.getStringWithCharsetAt(d+3,30).toString(),c=a.getStringWithCharsetAt(d+33,30).toString(),r=a.getStringWithCharsetAt(d+63,30).toString(),u=a.getStringWithCharsetAt(d+93,4).toString();var f=a.getByteAt(d+97+28);b=a.getByteAt(d+97+29);if(0==f&&0!=b){var n="1.1";f=a.getStringWithCharsetAt(d+97,28).toString()}else n=
"1.0",f=a.getStringWithCharsetAt(d+97,30).toString(),b=0;a=a.getByteAt(d+97+30);a={type:"ID3",version:n,tags:{title:h,artist:c,album:r,year:u,comment:f,genre:255>a?e[a]:""}};b&&(a.tags.track=b);return a}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:-128,length:128}}},{key:"canReadTagFormat",value:function(a){return"TAG"===String.fromCharCode.apply(String,a.slice(0,3))}}]);return c}(q);var e="Blues;Classic Rock;Country;Dance;Disco;Funk;Grunge;Hip-Hop;Jazz;Metal;New Age;Oldies;Other;Pop;R&B;Rap;Reggae;Rock;Techno;Industrial;Alternative;Ska;Death Metal;Pranks;Soundtrack;Euro-Techno;Ambient;Trip-Hop;Vocal;Jazz+Funk;Fusion;Trance;Classical;Instrumental;Acid;House;Game;Sound Clip;Gospel;Noise;AlternRock;Bass;Soul;Punk;Space;Meditative;Instrumental Pop;Instrumental Rock;Ethnic;Gothic;Darkwave;Techno-Industrial;Electronic;Pop-Folk;Eurodance;Dream;Southern Rock;Comedy;Cult;Gangsta;Top 40;Christian Rap;Pop/Funk;Jungle;Native American;Cabaret;New Wave;Psychadelic;Rave;Showtunes;Trailer;Lo-Fi;Tribal;Acid Punk;Acid Jazz;Polka;Retro;Musical;Rock & Roll;Hard Rock;Folk;Folk-Rock;National Folk;Swing;Fast Fusion;Bebob;Latin;Revival;Celtic;Bluegrass;Avantgarde;Gothic Rock;Progressive Rock;Psychedelic Rock;Symphonic Rock;Slow Rock;Big Band;Chorus;Easy Listening;Acoustic;Humour;Speech;Chanson;Opera;Chamber Music;Sonata;Symphony;Booty Bass;Primus;Porn Groove;Satire;Slow Jam;Club;Tango;Samba;Folklore;Ballad;Power Ballad;Rhythmic Soul;Freestyle;Duet;Punk Rock;Drum Solo;Acapella;Euro-House;Dance Hall".split(";");
p.exports=f},{"./MediaFileReader":11,"./MediaTagReader":12}],8:[function(f,p,q){function m(a){switch(a){case 0:a="iso-8859-1";break;case 1:a="utf-16";break;case 2:a="utf-16be";break;case 3:a="utf-8";break;default:a="iso-8859-1"}return a}function l(a,b,d,c){c=d.getStringWithCharsetAt(a+1,b-1,c);a=d.getStringWithCharsetAt(a+1+c.bytesReadCount,b-1-c.bytesReadCount);return{user_description:c.toString(),data:a.toString()}}var e=function(){function a(a,b){for(var d=0;d<b.length;d++){var c=b[d];c.enumerable=
c.enumerable||!1;c.configurable=!0;"value"in c&&(c.writable=!0);Object.defineProperty(a,c.key,c)}}return function(b,d,c){d&&a(b.prototype,d);c&&a(b,c);return b}}();f("./MediaFileReader");var d=f("./StringUtils"),c=f("./ArrayFileReader"),a={BUF:"Recommended buffer size",CNT:"Play counter",COM:"Comments",CRA:"Audio encryption",CRM:"Encrypted meta frame",ETC:"Event timing codes",EQU:"Equalization",GEO:"General encapsulated object",IPL:"Involved people list",LNK:"Linked information",MCI:"Music CD Identifier",
MLL:"MPEG location lookup table",PIC:"Attached picture",POP:"Popularimeter",REV:"Reverb",RVA:"Relative volume adjustment",SLT:"Synchronized lyric/text",STC:"Synced tempo codes",TAL:"Album/Movie/Show title",TBP:"BPM (Beats Per Minute)",TCM:"Composer",TCO:"Content type",TCR:"Copyright message",TDA:"Date",TDY:"Playlist delay",TEN:"Encoded by",TFT:"File type",TIM:"Time",TKE:"Initial key",TLA:"Language(s)",TLE:"Length",TMT:"Media type",TOA:"Original artist(s)/performer(s)",TOF:"Original filename",TOL:"Original Lyricist(s)/text writer(s)",
TOR:"Original release year",TOT:"Original album/Movie/Show title",TP1:"Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group",TP2:"Band/Orchestra/Accompaniment",TP3:"Conductor/Performer refinement",TP4:"Interpreted, remixed, or otherwise modified by",TPA:"Part of a set",TPB:"Publisher",TRC:"ISRC (International Standard Recording Code)",TRD:"Recording dates",TRK:"Track number/Position in set",TSI:"Size",TSS:"Software/hardware and settings used for encoding",TT1:"Content group description",TT2:"Title/Songname/Content description",
TT3:"Subtitle/Description refinement",TXT:"Lyricist/text writer",TXX:"User defined text information frame",TYE:"Year",UFI:"Unique file identifier",ULT:"Unsychronized lyric/text transcription",WAF:"Official audio file webpage",WAR:"Official artist/performer webpage",WAS:"Official audio source webpage",WCM:"Commercial information",WCP:"Copyright/Legal information",WPB:"Publishers official webpage",WXX:"User defined URL link frame",AENC:"Audio encryption",APIC:"Attached picture",ASPI:"Audio seek point index",
CHAP:"Chapter",CTOC:"Table of contents",COMM:"Comments",COMR:"Commercial frame",ENCR:"Encryption method registration",EQU2:"Equalisation (2)",EQUA:"Equalization",ETCO:"Event timing codes",GEOB:"General encapsulated object",GRID:"Group identification registration",IPLS:"Involved people list",LINK:"Linked information",MCDI:"Music CD identifier",MLLT:"MPEG location lookup table",OWNE:"Ownership frame",PRIV:"Private frame",PCNT:"Play counter",POPM:"Popularimeter",POSS:"Position synchronisation frame",
RBUF:"Recommended buffer size",RVA2:"Relative volume adjustment (2)",RVAD:"Relative volume adjustment",RVRB:"Reverb",SEEK:"Seek frame",SYLT:"Synchronized lyric/text",SYTC:"Synchronized tempo codes",TALB:"Album/Movie/Show title",TBPM:"BPM (beats per minute)",TCOM:"Composer",TCON:"Content type",TCOP:"Copyright message",TDAT:"Date",TDLY:"Playlist delay",TDRC:"Recording time",TDRL:"Release time",TDTG:"Tagging time",TENC:"Encoded by",TEXT:"Lyricist/Text writer",TFLT:"File type",TIME:"Time",TIPL:"Involved people list",
TIT1:"Content group description",TIT2:"Title/songname/content description",TIT3:"Subtitle/Description refinement",TKEY:"Initial key",TLAN:"Language(s)",TLEN:"Length",TMCL:"Musician credits list",TMED:"Media type",TMOO:"Mood",TOAL:"Original album/movie/show title",TOFN:"Original filename",TOLY:"Original lyricist(s)/text writer(s)",TOPE:"Original artist(s)/performer(s)",TORY:"Original release year",TOWN:"File owner/licensee",TPE1:"Lead performer(s)/Soloist(s)",TPE2:"Band/orchestra/accompaniment",TPE3:"Conductor/performer refinement",
TPE4:"Interpreted, remixed, or otherwise modified by",TPOS:"Part of a set",TPRO:"Produced notice",TPUB:"Publisher",TRCK:"Track number/Position in set",TRDA:"Recording dates",TRSN:"Internet radio station name",TRSO:"Internet radio station owner",TSOA:"Album sort order",TSOP:"Performer sort order",TSOT:"Title sort order",TSIZ:"Size",TSRC:"ISRC (international standard recording code)",TSSE:"Software/Hardware and settings used for encoding",TSST:"Set subtitle",TYER:"Year",TXXX:"User defined text information frame",
UFID:"Unique file identifier",USER:"Terms of use",USLT:"Unsychronized lyric/text transcription",WCOM:"Commercial information",WCOP:"Copyright/Legal information",WOAF:"Official audio file webpage",WOAR:"Official artist/performer webpage",WOAS:"Official audio source webpage",WORS:"Official internet radio station homepage",WPAY:"Payment",WPUB:"Publishers official webpage",WXXX:"User defined URL link frame"};f=function(){function d(){if(!(this instanceof d))throw new TypeError("Cannot call a class as a function");
}e(d,null,[{key:"getFrameReaderFunction",value:function(a){return a in b?b[a]:"T"===a[0]?b["T*"]:"W"===a[0]?b["W*"]:null}},{key:"readFrames",value:function(a,b,c,h,g){for(var k={},e=this._getFrameHeaderSize(h);a<b-e;){var r=this._readFrameHeader(c,a,h),n=r.id;if(!n)break;var f=r.flags,u=r.size,v=a+r.headerSize,l=c;a+=r.headerSize+r.size;if(!g||-1!==g.indexOf(n)){if("MP3e"===n||"\x00MP3"===n||"\x00\x00MP"===n||" MP3"===n)break;f&&f.format.unsynchronisation&&(l=this.getUnsyncFileReader(l,v,u),v=0,u=
l.getSize());f&&f.format.data_length_indicator&&(v+=4,u-=4);f=(r=d.getFrameReaderFunction(n))?r.apply(this,[v,u,l,f,h]):null;v=this._getFrameDescription(n);u={id:n,size:u,description:v,data:f};n in k?(k[n].id&&(k[n]=[k[n]]),k[n].push(u)):k[n]=u}}return k}},{key:"_getFrameHeaderSize",value:function(a){a=a.major;return 2==a?6:3==a||4==a?10:0}},{key:"_readFrameHeader",value:function(a,b,d){var c=d.major,h=null;d=this._getFrameHeaderSize(d);switch(c){case 2:var g=a.getStringAt(b,3);var k=a.getInteger24At(b+
3,!0);break;case 3:g=a.getStringAt(b,4);k=a.getLongAt(b+4,!0);break;case 4:g=a.getStringAt(b,4),k=a.getSynchsafeInteger32At(b+4)}if(g==String.fromCharCode(0,0,0)||g==String.fromCharCode(0,0,0,0))g="";g&&2<c&&(h=this._readFrameFlags(a,b+8));return{id:g||"",size:k||0,headerSize:d||0,flags:h}}},{key:"_readFrameFlags",value:function(a,b){return{message:{tag_alter_preservation:a.isBitSetAt(b,6),file_alter_preservation:a.isBitSetAt(b,5),read_only:a.isBitSetAt(b,4)},format:{grouping_identity:a.isBitSetAt(b+
1,7),compression:a.isBitSetAt(b+1,3),encryption:a.isBitSetAt(b+1,2),unsynchronisation:a.isBitSetAt(b+1,1),data_length_indicator:a.isBitSetAt(b+1,0)}}}},{key:"_getFrameDescription",value:function(b){return b in a?a[b]:"Unknown"}},{key:"getUnsyncFileReader",value:function(a,b,d){a=a.getBytesAt(b,d);for(b=0;b<a.length-1;b++)255===a[b]&&0===a[b+1]&&a.splice(b+1,1);return new c(a)}}]);return d}();var b={APIC:function(a,b,d,c,e){c=a;var h=m(d.getByteAt(a));switch(e&&e.major){case 2:e=d.getStringAt(a+1,
3);a+=4;break;case 3:case 4:e=d.getStringWithCharsetAt(a+1,b-1);a+=1+e.bytesReadCount;break;default:throw Error("Couldn't read ID3v2 major version.");}var k=d.getByteAt(a);k=g[k];h=d.getStringWithCharsetAt(a+1,b-(a-c)-1,h);a+=1+h.bytesReadCount;return{format:e.toString(),type:k,description:h.toString(),data:d.getBytesAt(a,c+b-a)}},CHAP:function(a,b,c,g,e){g=a;var h={},k=d.readNullTerminatedString(c.getBytesAt(a,b));h.id=k.toString();a+=k.bytesReadCount;h.startTime=c.getLongAt(a,!0);a+=4;h.endTime=
c.getLongAt(a,!0);a+=4;h.startOffset=c.getLongAt(a,!0);a+=4;h.endOffset=c.getLongAt(a,!0);a+=4;h.subFrames=this.readFrames(a,a+(b-(a-g)),c,e);return h},CTOC:function(a,b,c,g,e){g=a;var h={childElementIds:[],id:void 0,topLevel:void 0,ordered:void 0,entryCount:void 0,subFrames:void 0},k=d.readNullTerminatedString(c.getBytesAt(a,b));h.id=k.toString();a+=k.bytesReadCount;h.topLevel=c.isBitSetAt(a,1);h.ordered=c.isBitSetAt(a,0);a++;h.entryCount=c.getByteAt(a);a++;for(k=0;k<h.entryCount;k++){var f=d.readNullTerminatedString(c.getBytesAt(a,
b-(a-g)));h.childElementIds.push(f.toString());a+=f.bytesReadCount}h.subFrames=this.readFrames(a,a+(b-(a-g)),c,e);return h},COMM:function(a,b,c,d,g){var h=a,e=m(c.getByteAt(a));d=c.getStringAt(a+1,3);g=c.getStringWithCharsetAt(a+4,b-4,e);a+=4+g.bytesReadCount;a=c.getStringWithCharsetAt(a,h+b-a,e);return{language:d,short_description:g.toString(),text:a.toString()}}};b.COM=b.COMM;b.PIC=function(a,c,d,g,e){return b.APIC(a,c,d,g,e)};b.PCNT=function(a,b,c,d,g){return c.getLongAt(a,!1)};b.CNT=b.PCNT;b["T*"]=
function(a,b,c,d,g){d=m(c.getByteAt(a));return c.getStringWithCharsetAt(a+1,b-1,d).toString()};b.TXXX=function(a,b,c,d,g){d=m(c.getByteAt(a));return l(a,b,c,d)};b.WXXX=function(a,b,c,d,g){if(0===b)return null;d=m(c.getByteAt(a));return l(a,b,c,d)};b["W*"]=function(a,b,c,d,g){return 0===b?null:c.getStringWithCharsetAt(a,b,"iso-8859-1").toString()};b.TCON=function(a,c,d,g){return b["T*"].apply(this,arguments).replace(/^\(\d+\)/,"")};b.TCO=b.TCON;b.USLT=function(a,b,c,d,g){var h=a,e=m(c.getByteAt(a));
d=c.getStringAt(a+1,3);g=c.getStringWithCharsetAt(a+4,b-4,e);a+=4+g.bytesReadCount;a=c.getStringWithCharsetAt(a,h+b-a,e);return{language:d,descriptor:g.toString(),lyrics:a.toString()}};b.ULT=b.USLT;b.UFID=function(a,b,c,g,e){g=d.readNullTerminatedString(c.getBytesAt(a,b));a+=g.bytesReadCount;a=c.getBytesAt(a,b-g.bytesReadCount);return{ownerIdentifier:g.toString(),identifier:a}};var g="Other;32x32 pixels 'file icon' (PNG only);Other file icon;Cover (front);Cover (back);Leaflet page;Media (e.g. label side of CD);Lead artist/lead performer/soloist;Artist/performer;Conductor;Band/Orchestra;Composer;Lyricist/text writer;Recording Location;During recording;During performance;Movie/video screen capture;A bright coloured fish;Illustration;Band/artist logotype;Publisher/Studio logotype".split(";");
p.exports=f},{"./ArrayFileReader":3,"./MediaFileReader":11,"./StringUtils":13}],9:[function(f,p,q){function m(c,a){if("function"!==typeof a&&null!==a)throw new TypeError("Super expression must either be null or a function, not "+typeof a);c.prototype=Object.create(a&&a.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});a&&(Object.setPrototypeOf?Object.setPrototypeOf(c,a):c.__proto__=a)}var l=function(){function c(a,b){for(var c=0;c<b.length;c++){var d=b[c];d.enumerable=d.enumerable||
!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(a,b,d){b&&c(a.prototype,b);d&&c(a,d);return a}}();q=f("./MediaTagReader");f("./MediaFileReader");var e=f("./ID3v2FrameReader");f=function(c){function a(){if(!(this instanceof a))throw new TypeError("Cannot call a class as a function");var b=(a.__proto__||Object.getPrototypeOf(a)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!b||
"object"!==typeof b&&"function"!==typeof b?this:b}m(a,c);l(a,[{key:"_loadData",value:function(a,c){a.loadRange([6,9],{onSuccess:function(){a.loadRange([0,10+a.getSynchsafeInteger32At(6)-1],c)},onError:c.onError})}},{key:"_parseData",value:function(a,c){var b,g=0,f=a.getByteAt(g+3);if(4<f)return{type:"ID3",version:">2.4",tags:{}};var l=a.getByteAt(g+4),m=a.isBitSetAt(g+5,7),n=a.isBitSetAt(g+5,6),x=a.isBitSetAt(g+5,5),p=a.getSynchsafeInteger32At(g+6);g+=10;if(n)if(4===f){var t=a.getSynchsafeInteger32At(g);
g+=t}else t=a.getLongAt(g,!0),g+=t+4;t={type:"ID3",version:"2."+f+"."+l,major:f,revision:l,flags:{unsynchronisation:m,extended_header:n,experimental_indicator:x,footer_present:!1},size:p,tags:{}};c&&(b=this._expandShortcutTags(c));c=p+10;t.flags.unsynchronisation&&(a=e.getUnsyncFileReader(a,g,p),g=0,c=a.getSize());a=e.readFrames(g,c,a,t,b);for(var q in d)d.hasOwnProperty(q)&&(b=this._getFrameData(a,d[q]))&&(t.tags[q]=b);for(var z in a)a.hasOwnProperty(z)&&(t.tags[z]=a[z]);return t}},{key:"_getFrameData",
value:function(a,c){for(var b=0,d;d=c[b];b++)if(d in a)return a=a[d]instanceof Array?a[d][0]:a[d],a.data}},{key:"getShortcuts",value:function(){return d}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:10}}},{key:"canReadTagFormat",value:function(a){return"ID3"===String.fromCharCode.apply(String,a.slice(0,3))}}]);return a}(q);var d={title:["TIT2","TT2"],artist:["TPE1","TP1"],album:["TALB","TAL"],year:["TYER","TYE"],comment:["COMM","COM"],track:["TRCK","TRK"],genre:["TCON",
"TCO"],picture:["APIC","PIC"],lyrics:["USLT","ULT"]};p.exports=f},{"./ID3v2FrameReader":8,"./MediaFileReader":11,"./MediaTagReader":12}],10:[function(f,p,q){function m(a,b){if("function"!==typeof b&&null!==b)throw new TypeError("Super expression must either be null or a function, not "+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}});b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}var l=function(){function a(a,
c){for(var b=0;b<c.length;b++){var d=c[b];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(b,c,d){c&&a(b.prototype,c);d&&a(b,d);return b}}();q=f("./MediaTagReader");f("./MediaFileReader");f=function(a){function b(){if(!(this instanceof b))throw new TypeError("Cannot call a class as a function");var a=(b.__proto__||Object.getPrototypeOf(b)).apply(this,arguments);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
return!a||"object"!==typeof a&&"function"!==typeof a?this:a}m(b,a);l(b,[{key:"_loadData",value:function(a,b){var c=this;a.loadRange([0,16],{onSuccess:function(){c._loadAtom(a,0,"",b)},onError:b.onError})}},{key:"_loadAtom",value:function(a,b,c,d){if(b>=a.getSize())d.onSuccess();else{var g=this,e=a.getLongAt(b,!0);if(0==e||isNaN(e))d.onSuccess();else{var h=a.getStringAt(b+4,4);if(this._isContainerAtom(h)){"meta"==h&&(b+=4);var f=(c?c+".":"")+h;"moov.udta.meta.ilst"===f?a.loadRange([b,b+e],d):a.loadRange([b+
8,b+8+8],{onSuccess:function(){g._loadAtom(a,b+8,f,d)},onError:d.onError})}else a.loadRange([b+e,b+e+8],{onSuccess:function(){g._loadAtom(a,b+e,c,d)},onError:d.onError})}}}},{key:"_isContainerAtom",value:function(a){return 0<=["moov","udta","meta","ilst"].indexOf(a)}},{key:"_canReadAtom",value:function(a){return"----"!==a}},{key:"_parseData",value:function(a,b){var d={};b=this._expandShortcutTags(b);this._readAtom(d,a,0,a.getSize(),b);for(var e in c)c.hasOwnProperty(e)&&(b=d[c[e]])&&(d[e]="track"===
e?b.data.track:b.data);return{type:"MP4",ftyp:a.getStringAt(8,4),version:a.getLongAt(12,!0),tags:d}}},{key:"_readAtom",value:function(a,b,d,c,e,f,n){n=void 0===n?"":n+" ";for(var g=d;g<d+c;){var h=b.getLongAt(g,!0);if(0==h)break;var k=b.getStringAt(g+4,4);if(this._isContainerAtom(k)){"meta"==k&&(g+=4);this._readAtom(a,b,g+8,h-8,e,(f?f+".":"")+k,n);break}(!e||0<=e.indexOf(k))&&"moov.udta.meta.ilst"===f&&this._canReadAtom(k)&&(a[k]=this._readMetadataAtom(b,g));g+=h}}},{key:"_readMetadataAtom",value:function(a,
b){var c=a.getLongAt(b,!0),g=a.getStringAt(b+4,4),h=a.getInteger24At(b+16+1,!0);h=e[h];if("trkn"==g)var f={track:a.getByteAt(b+16+11),total:a.getByteAt(b+16+13)};else if("disk"==g)f={disk:a.getByteAt(b+16+11),total:a.getByteAt(b+16+13)};else{b+=24;var n=c-24;"covr"===g&&"uint8"===h&&(h="jpeg");switch(h){case "text":f=a.getStringWithCharsetAt(b,n,"utf-8").toString();break;case "uint8":f=a.getShortAt(b,!1);break;case "int":case "uint":f=("int"==h?1==n?a.getSByteAt:2==n?a.getSShortAt:4==n?a.getSLongAt:
a.getLongAt:1==n?a.getByteAt:2==n?a.getShortAt:a.getLongAt).call(a,b+(8==n?4:0),!0);break;case "jpeg":case "png":f={format:"image/"+h,data:a.getBytesAt(b,n)}}}return{id:g,size:c,description:d[g]||"Unknown",data:f}}},{key:"getShortcuts",value:function(){return c}}],[{key:"getTagIdentifierByteRange",value:function(){return{offset:0,length:16}}},{key:"canReadTagFormat",value:function(a){return"ftyp"===String.fromCharCode.apply(String,a.slice(4,8))}}]);return b}(q);var e={0:"uint8",1:"text",13:"jpeg",
14:"png",21:"int",22:"uint"},d={"\u00a9alb":"Album","\u00a9ART":"Artist",aART:"Album Artist","\u00a9day":"Release Date","\u00a9nam":"Title","\u00a9gen":"Genre",gnre:"Genre",trkn:"Track Number","\u00a9wrt":"Composer","\u00a9too":"Encoding Tool","\u00a9enc":"Encoded By",cprt:"Copyright",covr:"Cover Art","\u00a9grp":"Grouping",keyw:"Keywords","\u00a9lyr":"Lyrics","\u00a9cmt":"Comment",tmpo:"Tempo",cpil:"Compilation",disk:"Disc Number",tvsh:"TV Show Name",tven:"TV Episode ID",tvsn:"TV Season",tves:"TV Episode",
tvnn:"TV Network",desc:"Description",ldes:"Long Description",sonm:"Sort Name",soar:"Sort Artist",soaa:"Sort Album",soco:"Sort Composer",sosn:"Sort Show",purd:"Purchase Date",pcst:"Podcast",purl:"Podcast URL",catg:"Category",hdvd:"HD Video",stik:"Media Type",rtng:"Content Rating",pgap:"Gapless Playback",apID:"Purchase Account",sfID:"Country Code",atID:"Artist ID",cnID:"Catalog ID",plID:"Collection ID",geID:"Genre ID","xid ":"Vendor Information",flvr:"Codec Flavor"},c={title:"\u00a9nam",artist:"\u00a9ART",
album:"\u00a9alb",year:"\u00a9day",comment:"\u00a9cmt",track:"trkn",genre:"\u00a9gen",picture:"covr",lyrics:"\u00a9lyr"};p.exports=f},{"./MediaFileReader":11,"./MediaTagReader":12}],11:[function(f,p,q){var m=function(){function e(d,c){for(var a=0;a<c.length;a++){var b=c[a];b.enumerable=b.enumerable||!1;b.configurable=!0;"value"in b&&(b.writable=!0);Object.defineProperty(d,b.key,b)}}return function(d,c,a){c&&e(d.prototype,c);a&&e(d,a);return d}}(),l=f("./StringUtils");f=function(){function e(d){if(!(this instanceof
e))throw new TypeError("Cannot call a class as a function");this._isInitialized=!1;this._size=0}m(e,[{key:"init",value:function(d){var c=this;if(this._isInitialized)setTimeout(d.onSuccess,1);else return this._init({onSuccess:function(){c._isInitialized=!0;d.onSuccess()},onError:d.onError})}},{key:"_init",value:function(d){throw Error("Must implement init function");}},{key:"loadRange",value:function(d,c){throw Error("Must implement loadRange function");}},{key:"getSize",value:function(){if(!this._isInitialized)throw Error("init() must be called first.");
return this._size}},{key:"getByteAt",value:function(d){throw Error("Must implement getByteAt function");}},{key:"getBytesAt",value:function(d,c){for(var a=Array(c),b=0;b<c;b++)a[b]=this.getByteAt(d+b);return a}},{key:"isBitSetAt",value:function(d,c){return 0!=(this.getByteAt(d)&1<<c)}},{key:"getSByteAt",value:function(d){d=this.getByteAt(d);return 127<d?d-256:d}},{key:"getShortAt",value:function(d,c){d=c?(this.getByteAt(d)<<8)+this.getByteAt(d+1):(this.getByteAt(d+1)<<8)+this.getByteAt(d);0>d&&(d+=
65536);return d}},{key:"getSShortAt",value:function(d,c){d=this.getShortAt(d,c);return 32767<d?d-65536:d}},{key:"getLongAt",value:function(d,c){var a=this.getByteAt(d),b=this.getByteAt(d+1),e=this.getByteAt(d+2);d=this.getByteAt(d+3);c=c?(((a<<8)+b<<8)+e<<8)+d:(((d<<8)+e<<8)+b<<8)+a;0>c&&(c+=4294967296);return c}},{key:"getSLongAt",value:function(d,c){d=this.getLongAt(d,c);return 2147483647<d?d-4294967296:d}},{key:"getInteger24At",value:function(d,c){var a=this.getByteAt(d),b=this.getByteAt(d+1);
d=this.getByteAt(d+2);c=c?((a<<8)+b<<8)+d:((d<<8)+b<<8)+a;0>c&&(c+=16777216);return c}},{key:"getStringAt",value:function(d,c){for(var a=[],b=d,e=0;b<d+c;b++,e++)a[e]=String.fromCharCode(this.getByteAt(b));return a.join("")}},{key:"getStringWithCharsetAt",value:function(d,c,a){d=this.getBytesAt(d,c);switch((a||"").toLowerCase()){case "utf-16":case "utf-16le":case "utf-16be":a=l.readUTF16String(d,"utf-16be"===a);break;case "utf-8":a=l.readUTF8String(d);break;default:a=l.readNullTerminatedString(d)}return a}},
{key:"getCharAt",value:function(d){return String.fromCharCode(this.getByteAt(d))}},{key:"getSynchsafeInteger32At",value:function(d){var c=this.getByteAt(d),a=this.getByteAt(d+1),b=this.getByteAt(d+2);return this.getByteAt(d+3)&127|(b&127)<<7|(a&127)<<14|(c&127)<<21}}],[{key:"canReadFile",value:function(d){throw Error("Must implement canReadFile function");}}]);return e}();p.exports=f},{"./StringUtils":13}],12:[function(f,p,q){var m=function(){function f(e,d){for(var c=0;c<d.length;c++){var a=d[c];
a.enumerable=a.enumerable||!1;a.configurable=!0;"value"in a&&(a.writable=!0);Object.defineProperty(e,a.key,a)}}return function(e,d,c){d&&f(e.prototype,d);c&&f(e,c);return e}}();f("./MediaFileReader");f=function(){function f(e){if(!(this instanceof f))throw new TypeError("Cannot call a class as a function");this._mediaFileReader=e;this._tags=null}m(f,[{key:"setTagsToRead",value:function(e){this._tags=e;return this}},{key:"read",value:function(e){var d=this;this._mediaFileReader.init({onSuccess:function(){d._loadData(d._mediaFileReader,
{onSuccess:function(){try{var c=d._parseData(d._mediaFileReader,d._tags)}catch(a){if(e.onError){e.onError({type:"parseData",info:a.message});return}}e.onSuccess(c)},onError:e.onError})},onError:e.onError})}},{key:"getShortcuts",value:function(){return{}}},{key:"_loadData",value:function(e,d){throw Error("Must implement _loadData function");}},{key:"_parseData",value:function(e,d){throw Error("Must implement _parseData function");}},{key:"_expandShortcutTags",value:function(e){if(!e)return null;for(var d=
[],c=this.getShortcuts(),a=0,b;b=e[a];a++)d=d.concat(c[b]||[b]);return d}}],[{key:"getTagIdentifierByteRange",value:function(){throw Error("Must implement");}},{key:"canReadTagFormat",value:function(e){throw Error("Must implement");}}]);return f}();p.exports=f},{"./MediaFileReader":11}],13:[function(f,p,q){var m=function(){function e(d,c){for(var a=0;a<c.length;a++){var b=c[a];b.enumerable=b.enumerable||!1;b.configurable=!0;"value"in b&&(b.writable=!0);Object.defineProperty(d,b.key,b)}}return function(d,
c,a){c&&e(d.prototype,c);a&&e(d,a);return d}}(),l=function(){function e(d,c){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function");this._value=d;this.bytesReadCount=c;this.length=d.length}m(e,[{key:"toString",value:function(){return this._value}}]);return e}();p.exports={readUTF16String:function(e,d,c){var a=0,b=1,g=0;c=Math.min(c||e.length,e.length);254==e[0]&&255==e[1]?(d=!0,a=2):255==e[0]&&254==e[1]&&(d=!1,a=2);d&&(b=0,g=1);d=[];for(var h=0;a<c;h++){var f=e[a+b],m=(f<<
8)+e[a+g];a+=2;if(0==m)break;else 216>f||224<=f?d[h]=String.fromCharCode(m):(f=(e[a+b]<<8)+e[a+g],a+=2,d[h]=String.fromCharCode(m,f))}return new l(d.join(""),a)},readUTF8String:function(e,d){var c=0;d=Math.min(d||e.length,e.length);239==e[0]&&187==e[1]&&191==e[2]&&(c=3);for(var a=[],b=0;c<d;b++){var g=e[c++];if(0==g)break;else if(128>g)a[b]=String.fromCharCode(g);else if(194<=g&&224>g){var h=e[c++];a[b]=String.fromCharCode(((g&31)<<6)+(h&63))}else if(224<=g&&240>g){h=e[c++];var f=e[c++];a[b]=String.fromCharCode(((g&
255)<<12)+((h&63)<<6)+(f&63))}else if(240<=g&&245>g){h=e[c++];f=e[c++];var m=e[c++];f=((g&7)<<18)+((h&63)<<12)+((f&63)<<6)+(m&63)-65536;a[b]=String.fromCharCode((f>>10)+55296,(f&1023)+56320)}}return new l(a.join(""),c)},readNullTerminatedString:function(e,d){var c=[];d=d||e.length;for(var a=0;a<d;){var b=e[a++];if(0==b)break;c[a-1]=String.fromCharCode(b)}return new l(c.join(""),a)}}},{}],14:[function(f,p,q){function m(d,c){if("function"!==typeof c&&null!==c)throw new TypeError("Super expression must either be null or a function, not "+
typeof c);d.prototype=Object.create(c&&c.prototype,{constructor:{value:d,enumerable:!1,writable:!0,configurable:!0}});c&&(Object.setPrototypeOf?Object.setPrototypeOf(d,c):d.__proto__=c)}var l=function(){function d(d,a){for(var b=0;b<a.length;b++){var c=a[b];c.enumerable=c.enumerable||!1;c.configurable=!0;"value"in c&&(c.writable=!0);Object.defineProperty(d,c.key,c)}}return function(c,a,b){a&&d(c.prototype,a);b&&d(c,b);return c}}(),e=f("./ChunkedFileData");q=function(d){function c(a){if(!(this instanceof
c))throw new TypeError("Cannot call a class as a function");var b=(c.__proto__||Object.getPrototypeOf(c)).call(this);if(!this)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");b=!b||"object"!==typeof b&&"function"!==typeof b?this:b;b._url=a;b._fileData=new e;return b}m(c,d);l(c,[{key:"_init",value:function(a){c._config.avoidHeadRequests?this._fetchSizeWithGetRequest(a):this._fetchSizeWithHeadRequest(a)}},{key:"_fetchSizeWithHeadRequest",value:function(a){var b=
this;this._makeXHRRequest("HEAD",null,{onSuccess:function(c){(c=b._parseContentLength(c))?(b._size=c,a.onSuccess()):b._fetchSizeWithGetRequest(a)},onError:a.onError})}},{key:"_fetchSizeWithGetRequest",value:function(a){var b=this,c=this._roundRangeToChunkMultiple([0,0]);this._makeXHRRequest("GET",c,{onSuccess:function(c){var d=b._parseContentRange(c);c=b._getXhrResponseContent(c);if(d){if(null==d.instanceLength){b._fetchEntireFile(a);return}b._size=d.instanceLength}else b._size=c.length;b._fileData.addData(0,
c);a.onSuccess()},onError:a.onError})}},{key:"_fetchEntireFile",value:function(a){var b=this;this._makeXHRRequest("GET",null,{onSuccess:function(c){c=b._getXhrResponseContent(c);b._size=c.length;b._fileData.addData(0,c);a.onSuccess()},onError:a.onError})}},{key:"_getXhrResponseContent",value:function(a){return a.responseBody||a.responseText||""}},{key:"_parseContentLength",value:function(a){a=this._getResponseHeader(a,"Content-Length");return null==a?a:parseInt(a,10)}},{key:"_parseContentRange",value:function(a){if(a=
this._getResponseHeader(a,"Content-Range")){var b=a.match(/bytes (\d+)-(\d+)\/(?:(\d+)|\*)/i);if(!b)throw Error("FIXME: Unknown Content-Range syntax: "+a);return{firstBytePosition:parseInt(b[1],10),lastBytePosition:parseInt(b[2],10),instanceLength:b[3]?parseInt(b[3],10):null}}return null}},{key:"loadRange",value:function(a,b){var c=this;c._fileData.hasDataRange(a[0],Math.min(c._size,a[1]))?setTimeout(b.onSuccess,1):(a=this._roundRangeToChunkMultiple(a),a[1]=Math.min(c._size,a[1]),this._makeXHRRequest("GET",
a,{onSuccess:function(d){d=c._getXhrResponseContent(d);c._fileData.addData(a[0],d);b.onSuccess()},onError:b.onError}))}},{key:"_roundRangeToChunkMultiple",value:function(a){return[a[0],a[0]+1024*Math.ceil((a[1]-a[0]+1)/1024)-1]}},{key:"_makeXHRRequest",value:function(a,b,d){var e=this._createXHRObject();e.open(a,this._url);var f=function(){if(200===e.status||206===e.status)d.onSuccess(e);else if(d.onError)d.onError({type:"xhr",info:"Unexpected HTTP status "+e.status+".",xhr:e});e=null};"undefined"!==
typeof e.onload?(e.onload=f,e.onerror=function(){if(d.onError)d.onError({type:"xhr",info:"Generic XHR error, check xhr object.",xhr:e})}):e.onreadystatechange=function(){4===e.readyState&&f()};c._config.timeoutInSec&&(e.timeout=1E3*c._config.timeoutInSec,e.ontimeout=function(){if(d.onError)d.onError({type:"xhr",info:"Timeout after "+e.timeout/1E3+"s. Use jsmediatags.Config.setXhrTimeout to override.",xhr:e})});e.overrideMimeType("text/plain; charset=x-user-defined");b&&this._setRequestHeader(e,"Range",
"bytes="+b[0]+"-"+b[1]);this._setRequestHeader(e,"If-Modified-Since","Sat, 01 Jan 1970 00:00:00 GMT");e.send(null)}},{key:"_setRequestHeader",value:function(a,b,d){0>c._config.disallowedXhrHeaders.indexOf(b.toLowerCase())&&a.setRequestHeader(b,d)}},{key:"_hasResponseHeader",value:function(a,b){a=a.getAllResponseHeaders();if(!a)return!1;a=a.split("\r\n");for(var c=[],d=0;d<a.length;d++)c[d]=a[d].split(":")[0].toLowerCase();return 0<=c.indexOf(b.toLowerCase())}},{key:"_getResponseHeader",value:function(a,
b){return this._hasResponseHeader(a,b)?a.getResponseHeader(b):null}},{key:"getByteAt",value:function(a){return this._fileData.getByteAt(a).charCodeAt(0)&255}},{key:"_isWebWorker",value:function(){return"undefined"!==typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope}},{key:"_createXHRObject",value:function(){if("undefined"===typeof window&&!this._isWebWorker())return new (f("xhr2").XMLHttpRequest);if("undefined"!==typeof XMLHttpRequest)return new XMLHttpRequest;throw Error("XMLHttpRequest is not supported");
}}],[{key:"canReadFile",value:function(a){return"string"===typeof a&&/^[a-z]+:\/\//i.test(a)}},{key:"setConfig",value:function(a){for(var b in a)a.hasOwnProperty(b)&&(this._config[b]=a[b]);a=this._config.disallowedXhrHeaders;for(b=0;b<a.length;b++)a[b]=a[b].toLowerCase()}}]);return c}(f("./MediaFileReader"));q._config={avoidHeadRequests:!1,disallowedXhrHeaders:[],timeoutInSec:30};p.exports=q},{"./ChunkedFileData":5,"./MediaFileReader":11,xhr2:2}],15:[function(f,p,q){function m(a,b){if(!(a instanceof
b))throw new TypeError("Cannot call a class as a function");}function l(a,b){var c=0>a.offset&&(-a.offset>b||0<a.offset+a.length);return!(0<=a.offset&&a.offset+a.length>=b||c)}var e=function(){function a(a,b){for(var c=0;c<b.length;c++){var d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;"value"in d&&(d.writable=!0);Object.defineProperty(a,d.key,d)}}return function(b,c,d){c&&a(b.prototype,c);d&&a(b,d);return b}}();f("./MediaFileReader");q=f("./NodeFileReader");var d=f("./XhrFileReader"),c=
f("./BlobFileReader"),a=f("./ArrayFileReader");f("./MediaTagReader");var b=f("./ID3v1TagReader"),g=f("./ID3v2TagReader"),h=f("./MP4TagReader");f=f("./FLACTagReader");var k=[],r=[],u=function(){function a(b){m(this,a);this._file=b}e(a,[{key:"setTagsToRead",value:function(a){this._tagsToRead=a;return this}},{key:"setFileReader",value:function(a){this._fileReader=a;return this}},{key:"setTagReader",value:function(a){this._tagReader=a;return this}},{key:"read",value:function(a){var b=new (this._getFileReader())(this._file),
c=this;b.init({onSuccess:function(){c._getTagReader(b,{onSuccess:function(d){(new d(b)).setTagsToRead(c._tagsToRead).read(a)},onError:a.onError})},onError:a.onError})}},{key:"_getFileReader",value:function(){return this._fileReader?this._fileReader:this._findFileReader()}},{key:"_findFileReader",value:function(){for(var a=0;a<k.length;a++)if(k[a].canReadFile(this._file))return k[a];throw Error("No suitable file reader found for "+this._file);}},{key:"_getTagReader",value:function(a,b){if(this._tagReader){var c=
this._tagReader;setTimeout(function(){b.onSuccess(c)},1)}else this._findTagReader(a,b)}},{key:"_findTagReader",value:function(a,b){for(var c=[],d=[],e=a.getSize(),f=0;f<r.length;f++){var g=r[f].getTagIdentifierByteRange();l(g,e)&&(0<=g.offset&&g.offset<e/2||0>g.offset&&g.offset<-e/2?c.push(r[f]):d.push(r[f]))}var h=!1;f={onSuccess:function(){if(h){for(var c=0;c<r.length;c++){var d=r[c].getTagIdentifierByteRange();if(l(d,e)){try{var f=a.getBytesAt(0<=d.offset?d.offset:d.offset+e,d.length)}catch(A){if(b.onError)b.onError({type:"fileReader",
info:A.message});return}if(r[c].canReadTagFormat(f)){b.onSuccess(r[c]);return}}}if(b.onError)b.onError({type:"tagFormat",info:"No suitable tag reader found"})}else h=!0},onError:b.onError};this._loadTagIdentifierRanges(a,c,f);this._loadTagIdentifierRanges(a,d,f)}},{key:"_loadTagIdentifierRanges",value:function(a,b,c){if(0===b.length)setTimeout(c.onSuccess,1);else{for(var d=[Number.MAX_VALUE,0],e=a.getSize(),f=0;f<b.length;f++){var g=b[f].getTagIdentifierByteRange(),h=0<=g.offset?g.offset:g.offset+
e;g=h+g.length-1;d[0]=Math.min(h,d[0]);d[1]=Math.max(g,d[1])}a.loadRange(d,c)}}}]);return a}(),v=function(){function a(){m(this,a)}e(a,null,[{key:"addFileReader",value:function(b){k.push(b);return a}},{key:"addTagReader",value:function(b){r.push(b);return a}},{key:"removeTagReader",value:function(b){b=r.indexOf(b);0<=b&&r.splice(b,1);return a}},{key:"EXPERIMENTAL_avoidHeadRequests",value:function(){d.setConfig({avoidHeadRequests:!0})}},{key:"setDisallowedXhrHeaders",value:function(a){d.setConfig({disallowedXhrHeaders:a})}},
{key:"setXhrTimeoutInSec",value:function(a){d.setConfig({timeoutInSec:a})}}]);return a}();v.addFileReader(d).addFileReader(c).addFileReader(a).addTagReader(g).addTagReader(b).addTagReader(h).addTagReader(f);"undefined"===typeof process||process.browser||v.addFileReader(q);p.exports={read:function(a,b){(new u(a)).read(b)},Reader:u,Config:v}},{"./ArrayFileReader":3,"./BlobFileReader":4,"./FLACTagReader":6,"./ID3v1TagReader":7,"./ID3v2TagReader":9,"./MP4TagReader":10,"./MediaFileReader":11,"./MediaTagReader":12,
"./NodeFileReader":1,"./XhrFileReader":14}]},{},[15])(15)});

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

217
out/js/worker.js Normal file
View file

@ -0,0 +1,217 @@
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);
}
}
}
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);
}
}
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;
}
}
const tagger = new Tagger(self),
eventHandler = new EventHandler(self);
tagger.init();

1
out/js/worker.min.js vendored Normal file
View file

@ -0,0 +1 @@
class EventHandler{constructor(e){this.events={},this.worker=e,this.worker.addEventListener("message",this.handleEvent.bind(this))}addEvent(e,t){this.events[e]=t}sendData(e,t,a){this.worker.postMessage({cmd:e,status:t,data:a})}handleEvent(e){let t=e.data;t.cmd&&this.events[t.cmd]&&this.events[t.cmd](t.data)}}class Database{constructor(e,t){this.isInit=!1,this.name=e,this.version=t,this.errored=!1,this.db,this.prepareDB()}async prepareDB(){if(this.isInit||this.errored)return;let e=this.db=indexedDB.open(this.name,this.version);e.onerror=DatabaseHandler.onError.bind(this),e.onsuccess=DatabaseHandler.onSuccess.bind(this),e.onupgradeneeded=DatabaseHandler.onUpgrade.bind(this),e.onblocked=DatabaseHandler.onBlocked.bind(this)}async set(e,t,a){return t.key=e,await this.run("put",t,a)}async get(e,t){return await this.run("get",e,t)}async remove(e,t){return await this.run("delete",e,t)}check(){return!(!this.isInit||this.errored)}async getTX(e){return await this.db.transaction([e],"readwrite")}async getObjectStore(e,t){return await e.objectStore(t)}async run(e,t,a){if(this.check()){let s=await this.getTX(a),n=await this.getObjectStore(s,a),r=await this.request(n[e](t));return await s.complete,await r}return null}request(e){return new Promise((t,a)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>a(e.error)})}}class DatabaseHandler{static onError(e){this.errored=!0,eventHandler.sendData("databaseError","error",e.message)}static onSuccess(e){this.db=this.db.result,this.isInit=!0,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)}}self.importScripts("jsmediatags.min.js");class Tagger{constructor(e){this.db=new Database("SongLib",1)}static prepareName(e){return(e.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(e){let t=Tagger.prepareName(e),a=await this.db.get(t,"songs"),s=e.force?"id3-request-force":"id3-request";a?(a.index=e.index,eventHandler.sendData(s,"success",a)):(this.parseData(e,t).then(t=>{t.index=e.index,eventHandler.sendData(s,"success",t)}),eventHandler.sendData(s,"waiting",e))}async removeData(e){let t=Tagger.prepareName(e),a=await this.db.remove(t,"songs");eventHandler.sendData("id3-remove","success",a)}async setData(e,t){let a=await this.db.set(t,e,"songs");eventHandler.sendData("id3-set","success",a)}ready(e){console.log("[ID3] > Song Database Ready"),eventHandler.sendData("id3-ready","startup","")}upgrade(e){e.result.createObjectStore("songs",{keyPath:"key"}).createIndex("name","name",{unique:!1})}async parseData(e,t){let a=(await new Promise((t,a)=>{new jsmediatags.Reader(e.file).read({onSuccess:e=>{t(e)},onError:e=>{console.log("[ID3] > Error Parsing Data!"),t({tags:{}})}})})).tags,s={title:a.title||e.name,artist:a.artist||"VA",genre:a.genre||"Unknown",year:a.year||1970,key:t};return await this.setData(s,t),s}}const tagger=new Tagger(self),eventHandler=new EventHandler(self);tagger.init();

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,4 @@
<div class="audio-item">
<span class="artist">$artist$</span>
<span class="title">$title$</span>
</div>

View file

@ -0,0 +1,3 @@
<div class="config-content">
$content$
</div>

5
out/tpl/config/nav.tpl Normal file
View file

@ -0,0 +1,5 @@
<nav class="config-nav">
<div class="item active" data-id="base">Base</div>
<div class="item" data-id="visuals">Visuals</div>
<div class="item" data-id="visual">Visual</div>
</nav>

View file

@ -0,0 +1,8 @@
<div class="visual-item $active$" data-id="$id$">
<!-- maybe add later a image?
<div class="image">
<img src="$image$">
</div>
-->
$title$
</div>

View file

@ -1,27 +1,29 @@
<div id="image-upload">
<label for="image" class="button">
Image-Upload (Only Local!)
</label>
<input type="file" id="image" accept="image/*|video/*">
<form>
<label for="image" class="button spaced">
Image-Upload (Only Local!)
</label>
<input type="file" id="image" accept="image/*|video/*">
<label class="input floating-label">
<input type="url" id="url" value="$value$">
<input type="url" id="url" autocomplete="off" value="$value$">
<span class="input-label">URL</span>
<span class="focus"></span>
</label>
<p>You only want to change the color? do it!</p>
<label class="input floating-label color-picker" for="color">
<span id="colorBlob" style="background-color: $bgValue$"></span>
<span class="colorBlob" style="background-color: $bgValue$"></span>
<input type="color" id="color" value="$bgValue$">
<span class="input-label">Color</span>
<span class="focus"></span>
</label>
<label class="input">
<label class="input floating-label">
<input class="range" type="range" id="alphaValue" min="0" max="1" value="$alphaValue$" step="0.1">
<span class="input-label">Alpha</span>
<span class="min">0</span>
<span class="max">1</span>
</label>
</form>
</div>

6
out/tpl/inputs/color.tpl Normal file
View file

@ -0,0 +1,6 @@
<label class="input floating-label color-picker" for="$name$">
<span class="colorBlob" style="background-color: $value$"></span>
<input data-type="$dataType$" type="color" id="$name$" name="$name$" value="$value$">
<span class="input-label">$showName$</span>
<span class="focus"></span>
</label>

5
out/tpl/inputs/input.tpl Normal file
View file

@ -0,0 +1,5 @@
<label class="input floating-label" for="$name$">
<input data-type="$dataType$" type="$type$" id="$name$" value="$value$">
<span class="input-label">$showName$</span>
<span class="focus"></span>
</label>

View file

@ -0,0 +1,3 @@
<custom-option data-value="$value$">
$value$
</custom-option>

View file

@ -0,0 +1,8 @@
<custom-select data-event="$event$" data-conf="$conf$">
<label>$showName$</label>
<main class="label">$value$</main>
<input data-type="$dataType$" type="text" name="$name$" value="$value$" required>
<custom-options>
$options$
</custom-options>
</custom-select>

View file

@ -0,0 +1,7 @@
<label class="input floating-label input-range">
<input data-type="$dataType$" class="range" type="range" name="$name$" min="$min$" max="$max$" value="$value$" step="$stepSize$">
<span class="input-label">$showName$</span>
<span class="min">$min$</span>
<span class="current">$value$</span>
<span class="max">$max$</span>
</label>

View file

@ -0,0 +1,5 @@
<switch>
<input id="$name$" name="$name$" type="checkbox" $value$>
<label for="$name$"></label>
<span>$showName$</span>
</switch>

4
out/tpl/notification.tpl Normal file
View file

@ -0,0 +1,4 @@
<div class="message">
$message$
</div>
<div class="fade-bar $type$" style="animation-duration: $time$ms"></div>

View file

@ -14,4 +14,7 @@
</svg>
</div>
</div>
<div class="readAll button">
Force Tagger
</div>
</playlist>

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;
$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 {
@ -51,3 +53,4 @@ div {
@import "playlist";
@import "modal";
@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;
}
}

View file

@ -6,6 +6,8 @@ precision mediump float;
in vec4 pos;
in vec3 v_surfaceToLight;
in vec3 baseColor;
in vec3 maxColor;
uniform vec4 u_color;
out vec4 outColor;
@ -14,8 +16,6 @@ void main() {
vec4 fragNormal = normalize(pos);
float u_light = 0.3;
float light = max(dot(fragNormal.xyz, normalize(v_surfaceToLight).xyz), u_light);
vec3 baseColor = vec3(0, 0, 1);
vec3 maxColor = vec3(1, 0, 0);
float y = pos.z;
if (y < 0.0) {
y = y * -1.0;

View file

@ -3,12 +3,18 @@
in vec3 a_position;
uniform mat4 u_matrix;
uniform vec3 u_lightPos;
uniform vec3 u_baseColor;
uniform vec3 u_maxColor;
out vec4 pos;
out vec3 v_surfaceToLight;
out vec3 maxColor;
out vec3 baseColor;
void main() {
pos = u_matrix * vec4(a_position, 1);
gl_Position = pos;
v_surfaceToLight = u_lightPos - pos.xyz;
maxColor = u_maxColor;
baseColor = u_baseColor;
}

19
shaders/wave2d.frag Normal file
View file

@ -0,0 +1,19 @@
#version 300 es
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default. It means "medium precision"
precision mediump float;
in vec4 pos;
in vec3 baseColor;
in vec3 maxColor;
out vec4 outColor;
void main() {
float y = pos.z;
if (y < 0.0) {
y = y * -1.0;
}
vec3 color = mix(baseColor, maxColor, y);
outColor = vec4(color, 1.0);
}

19
shaders/wave2d.vert Normal file
View file

@ -0,0 +1,19 @@
#version 300 es
in vec3 a_position;
uniform mat4 u_matrix;
uniform vec3 u_baseColor;
uniform vec3 u_maxColor;
out vec4 pos;
out vec3 maxColor;
out vec3 baseColor;
void main() {
pos = u_matrix * vec4(a_position, 1);
pos.y = pos.y * 0.6;
gl_Position = pos;
maxColor = u_maxColor;
baseColor = u_baseColor;
}