WIP
This commit is contained in:
parent
9d5259767c
commit
25fcefcb50
68 changed files with 2982 additions and 307 deletions
|
@ -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'));
|
||||
|
|
|
@ -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
26
build/task/worker.js
Normal 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;
|
97
index.html
97
index.html
|
@ -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>
|
||||
|
|
21
js/utils.js
21
js/utils.js
|
@ -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
1
out/gui/base.json
Normal 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!"}]
|
|
@ -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"}]
|
|
@ -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
1
out/gui/wave2d.json
Normal 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
85
out/js/jsmediatags.min.js
vendored
Executable 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
2
out/js/scripts.min.js
vendored
2
out/js/scripts.min.js
vendored
File diff suppressed because one or more lines are too long
217
out/js/worker.js
Normal file
217
out/js/worker.js
Normal 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
1
out/js/worker.min.js
vendored
Normal 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
4
out/tpl/audio-information.tpl
Normal file
4
out/tpl/audio-information.tpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="audio-item">
|
||||
<span class="artist">$artist$</span>
|
||||
<span class="title">$title$</span>
|
||||
</div>
|
3
out/tpl/config/content.tpl
Normal file
3
out/tpl/config/content.tpl
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="config-content">
|
||||
$content$
|
||||
</div>
|
5
out/tpl/config/nav.tpl
Normal file
5
out/tpl/config/nav.tpl
Normal 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>
|
8
out/tpl/config/visualitem.tpl
Normal file
8
out/tpl/config/visualitem.tpl
Normal 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>
|
|
@ -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
6
out/tpl/inputs/color.tpl
Normal 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
5
out/tpl/inputs/input.tpl
Normal 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>
|
3
out/tpl/inputs/option.tpl
Normal file
3
out/tpl/inputs/option.tpl
Normal file
|
@ -0,0 +1,3 @@
|
|||
<custom-option data-value="$value$">
|
||||
$value$
|
||||
</custom-option>
|
8
out/tpl/inputs/select.tpl
Normal file
8
out/tpl/inputs/select.tpl
Normal 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>
|
7
out/tpl/inputs/slider.tpl
Normal file
7
out/tpl/inputs/slider.tpl
Normal 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>
|
5
out/tpl/inputs/switch.tpl
Normal file
5
out/tpl/inputs/switch.tpl
Normal 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
4
out/tpl/notification.tpl
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="message">
|
||||
$message$
|
||||
</div>
|
||||
<div class="fade-bar $type$" style="animation-duration: $time$ms"></div>
|
|
@ -14,4 +14,7 @@
|
|||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="readAll button">
|
||||
Force Tagger
|
||||
</div>
|
||||
</playlist>
|
39
raw/gui/base.json
Normal file
39
raw/gui/base.json
Normal file
|
@ -0,0 +1,39 @@
|
|||
[
|
||||
{
|
||||
"group": "",
|
||||
"name": "showSeekbar",
|
||||
"showName": "Show Seekbar",
|
||||
"type": "checkbox",
|
||||
"value": true,
|
||||
"tooltip": "Showing SeekBar at the Bottom of the Screen"
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"name": "showPlaying",
|
||||
"showName": "Show Playing",
|
||||
"type": "checkbox",
|
||||
"value": true,
|
||||
"tooltip": "Showing \"Now Playing\" Notification"
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"name": "showPlayingTime",
|
||||
"showName": "Now Playing Display Time",
|
||||
"type": "slider",
|
||||
"value": 1000,
|
||||
"max": 5000,
|
||||
"min": 500,
|
||||
"tooltip": "How long should the Now Playing Notification shown",
|
||||
"stepSize": 100,
|
||||
"dataType": "int"
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"name": "seekColor",
|
||||
"showName": "SeekBar Color",
|
||||
"type": "color",
|
||||
"value": "#ffffff",
|
||||
"dataType": "rgb",
|
||||
"tooltip": "SeekBar Color!"
|
||||
}
|
||||
]
|
|
@ -53,7 +53,8 @@
|
|||
],
|
||||
"type": "slider",
|
||||
"max": 255,
|
||||
"min": 0
|
||||
"min": 0,
|
||||
"value": 255
|
||||
},
|
||||
{
|
||||
"group": "light",
|
||||
|
|
|
@ -1 +1,72 @@
|
|||
{}
|
||||
[
|
||||
{
|
||||
"group": "",
|
||||
"name": "fftSize",
|
||||
"showName": "FFT-Size",
|
||||
"options": [
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
16384
|
||||
],
|
||||
"value": 4096,
|
||||
"type": "select",
|
||||
"tooltip": "How Many Items should the FFT Capture and Render",
|
||||
"dataType": "int"
|
||||
},
|
||||
{
|
||||
"group": "rotation",
|
||||
"name": [
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
],
|
||||
"props": [
|
||||
"x",
|
||||
"y",
|
||||
"z"
|
||||
],
|
||||
"type": "slider",
|
||||
"max": 360,
|
||||
"min": -360,
|
||||
"value": 0,
|
||||
"dataType": "int"
|
||||
},
|
||||
{
|
||||
"group": "rotation",
|
||||
"name": [
|
||||
"X-Inc",
|
||||
"Y-Inc",
|
||||
"Z-Inc"
|
||||
],
|
||||
"props": [
|
||||
"x-inc",
|
||||
"y-inc",
|
||||
"z-inc"
|
||||
],
|
||||
"type": "slider",
|
||||
"max": 1,
|
||||
"min": -1,
|
||||
"value": 0,
|
||||
"stepSize": 0.01,
|
||||
"dataType": "float"
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"name": "baseColor",
|
||||
"showName": "Base Color",
|
||||
"type": "color",
|
||||
"value": "#0089ff",
|
||||
"dataType": "rgb",
|
||||
"tooltip": "Base Color!"
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"name": "gradientToColor",
|
||||
"showName": "Second Color",
|
||||
"type": "color",
|
||||
"value": "#ff0000",
|
||||
"dataType": "rgb",
|
||||
"tooltip": "Second Color!"
|
||||
}
|
||||
]
|
73
raw/gui/wave2d.json
Normal file
73
raw/gui/wave2d.json
Normal file
|
@ -0,0 +1,73 @@
|
|||
[
|
||||
{
|
||||
"group": "",
|
||||
"name": "fftSize",
|
||||
"showName": "FFT-Size",
|
||||
"options": [
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
16384,
|
||||
32768
|
||||
],
|
||||
"value": 16384,
|
||||
"type": "select",
|
||||
"tooltip": "How Many Items should the FFT Capture and Render",
|
||||
"dataType": "int"
|
||||
},
|
||||
{
|
||||
"group": "rotation",
|
||||
"name": [
|
||||
"X",
|
||||
"Y",
|
||||
"Z"
|
||||
],
|
||||
"props": [
|
||||
"x",
|
||||
"y",
|
||||
"z"
|
||||
],
|
||||
"type": "slider",
|
||||
"max": 360,
|
||||
"min": -360,
|
||||
"value": 0,
|
||||
"dataType": "int"
|
||||
},
|
||||
{
|
||||
"group": "rotation",
|
||||
"name": [
|
||||
"X-Inc",
|
||||
"Y-Inc",
|
||||
"Z-Inc"
|
||||
],
|
||||
"props": [
|
||||
"x-inc",
|
||||
"y-inc",
|
||||
"z-inc"
|
||||
],
|
||||
"type": "slider",
|
||||
"max": 1,
|
||||
"min": -1,
|
||||
"value": 0,
|
||||
"stepSize": 0.01,
|
||||
"dataType": "float"
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"name": "baseColor",
|
||||
"showName": "Base Color",
|
||||
"type": "color",
|
||||
"value": "#0089ff",
|
||||
"dataType": "rgb",
|
||||
"tooltip": "Base Color!"
|
||||
},
|
||||
{
|
||||
"group": "",
|
||||
"name": "gradientToColor",
|
||||
"showName": "Second Color",
|
||||
"type": "color",
|
||||
"value": "#ff0000",
|
||||
"dataType": "rgb",
|
||||
"tooltip": "Second Color!"
|
||||
}
|
||||
]
|
37
raw/javascript/FileHandler.js
Normal file
37
raw/javascript/FileHandler.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Handler around the Playlist file to keep track on the ID3 and more!
|
||||
class AudioPlayerFile {
|
||||
constructor(file, index) {
|
||||
this.file = file;
|
||||
this.name = this.getName();
|
||||
this.id3 = null;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
getName() {
|
||||
let name = this.file.name.split(".");
|
||||
name.pop();
|
||||
name = name.join(".");
|
||||
return name;
|
||||
}
|
||||
|
||||
getID3Tag(force) {
|
||||
if (!force && this.id3 !== null) {
|
||||
return this.id3;
|
||||
}
|
||||
eventHandler.sendData('getData', {
|
||||
file: this.file,
|
||||
name: this.name,
|
||||
index: this.index,
|
||||
force: force === true
|
||||
});
|
||||
return {
|
||||
title: this.name,
|
||||
artist: 'VA',
|
||||
}
|
||||
}
|
||||
|
||||
getAudioName() {
|
||||
let tag = this.getID3Tag();
|
||||
return template.parseTemplate('audio-information', tag);
|
||||
}
|
||||
}
|
|
@ -4,32 +4,51 @@ const shaderHandler = new ShaderHandler(null),
|
|||
visual = new VisualDrawer(),
|
||||
template = new Template(),
|
||||
player = new Player(),
|
||||
vConf = new Config(),
|
||||
pConf = new Config();
|
||||
vConf = new Config("visual"),
|
||||
pConf = new Config("player"),
|
||||
worker = new Worker('/out/js/worker.min.js'),
|
||||
startup = new Startup(),
|
||||
eventHandler = new EventHandler(),
|
||||
playerConf = new PlayerConfigHandler();
|
||||
|
||||
let c = null,
|
||||
gl = null;
|
||||
let c, gl, cInfo, ctx;
|
||||
|
||||
worker.addEventListener('message', e => {
|
||||
if (e.data.status === 'startup') {
|
||||
startup.moduleLoaded(e.data.cmd);
|
||||
return;
|
||||
}
|
||||
eventHandler.handleEvent(e);
|
||||
});
|
||||
|
||||
window.addEventListener('startupFin', e => {
|
||||
setTimeout(e => {
|
||||
$('.loading-screen').remove();
|
||||
}, 100)
|
||||
})
|
||||
|
||||
async function startUP() {
|
||||
pConf.loadConfigByName('default');
|
||||
c = document.body.querySelector('#c'),
|
||||
gl = c.getContext("webgl2");
|
||||
c = $('#c'),
|
||||
gl = c.getContext("webgl2"),
|
||||
cInfo = $('#cInfo'),
|
||||
ctx = cInfo.getContext('2d');
|
||||
if (!gl) {
|
||||
alert("SORRY THE BROWSER DOESN'T SUPPORT WEBGL2");
|
||||
return false;
|
||||
}
|
||||
shaderHandler.setGL(gl)
|
||||
await shaderHandler.loadArray(["wave", "sphere", "water"], 'shaders/');
|
||||
await shaderHandler.loadArray(["wave", "sphere", "water", "wave2d"], 'shaders/');
|
||||
await NotificationHandler.instance.init();
|
||||
await audioHandler.init();
|
||||
await player.init();
|
||||
await visual.init();
|
||||
await gui.init();
|
||||
await imageUploader.init();
|
||||
await playerConf.init();
|
||||
await initHandler();
|
||||
}
|
||||
|
||||
startUP().then(r => {
|
||||
setTimeout(e => {
|
||||
$('.loading-screen').remove();
|
||||
}, 100)
|
||||
startup.moduleLoaded('startup');
|
||||
});
|
|
@ -45,8 +45,13 @@ class AudioHandler {
|
|||
this.analyser.smoothingTimeConstant = float;
|
||||
}
|
||||
|
||||
loadSong(src) {
|
||||
let self = this;
|
||||
loadSong(file) {
|
||||
if (!file) {
|
||||
NotificationHandler.createNotification("Sorry!<br> Currently no Song is uploaded!", "error", 2000);
|
||||
return false;
|
||||
}
|
||||
let self = this,
|
||||
src = file.file;
|
||||
if (self.lastSong) {
|
||||
URL.revokeObjectURL(self.lastSong);
|
||||
}
|
||||
|
@ -55,8 +60,12 @@ class AudioHandler {
|
|||
this.start().catch(alert);
|
||||
}
|
||||
this.audioFile.play().then(e => {
|
||||
if (pConf.get("showPlaying", "true")) {
|
||||
NotificationHandler.createNotification("<span class='now-playing'>Now Playing:</span>" + file.getAudioName(), "info", pConf.get("showPlayingTime", 1000));
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('playSong'));
|
||||
}).catch(e => {
|
||||
NotificationHandler.createNotification(e.message, "error", 1000);
|
||||
player.nextSong();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
class Config {
|
||||
constructor() {
|
||||
static allConfigs = {};
|
||||
|
||||
constructor(type) {
|
||||
this.config = {};
|
||||
this.name = ''
|
||||
this.type = type;
|
||||
Config.allConfigs[type] = this;
|
||||
}
|
||||
|
||||
loadConfigByName(name) {
|
||||
|
@ -35,4 +39,10 @@ class Config {
|
|||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
reset() {
|
||||
NotificationHandler.createNotification(`CONFIG REQUEST SUCCESS FOR ${this.type}`, "success", 2000);
|
||||
this.config = {};
|
||||
this.save();
|
||||
}
|
||||
}
|
|
@ -1,3 +1,31 @@
|
|||
class EventHandler {
|
||||
constructor() {
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
addEvent(name, cb) {
|
||||
this.events[name] = cb;
|
||||
}
|
||||
|
||||
sendData(name, data) {
|
||||
worker.postMessage({
|
||||
cmd: name,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
let data = event.data;
|
||||
if (!data.cmd) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.events[data.cmd]) {
|
||||
this.events[data.cmd](data.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function initHandler() {
|
||||
let body = $('body');
|
||||
$('.playlist.menu-icon').addEventListener('click', e => {
|
||||
|
@ -31,6 +59,72 @@ async function initHandler() {
|
|||
});
|
||||
window.addEventListener('playSong', setActiveOnPlaylist);
|
||||
$('.upload-image').addEventListener('click', imageUploader.renderModal.bind(imageUploader));
|
||||
body.addDelegatedEventListener('click', '.readAll', e => {
|
||||
let playlist = player.playlist.list;
|
||||
for (let i = 0; i < playlist.length; i++) {
|
||||
playlist[i].getID3Tag(true);
|
||||
}
|
||||
})
|
||||
|
||||
body.addDelegatedEventListener('input', '.input-range input[type="range"]', (e, el) => {
|
||||
let current = $('.current', el.parentNode);
|
||||
current.innerText = el.value;
|
||||
});
|
||||
|
||||
body.addDelegatedEventListener('input', 'input[type="color"]', (e, el) => {
|
||||
let parent = el.parentNode;
|
||||
$('.colorBlob', parent).style.backgroundColor = el.value;
|
||||
})
|
||||
|
||||
body.addDelegatedEventListener('click', '.visual-item', (e, el) => {
|
||||
visual.switch(el.dataset.id || 'wave');
|
||||
$('modal-content .visuals .active').removeClass('active');
|
||||
el.addClass('active');
|
||||
})
|
||||
|
||||
body.addDelegatedEventListener('input', 'section.base input', (e, el) => {
|
||||
if (el.type === 'checkbox') {
|
||||
pConf.set(el.name, el.checked);
|
||||
} else {
|
||||
setValue(el.name, el.value, pConf, el.dataset.type);
|
||||
}
|
||||
pConf.save();
|
||||
})
|
||||
body.addDelegatedEventListener('input', 'section.visual input', (e, el) => {
|
||||
if (el.type === 'checkbox') {
|
||||
vConf.set(el.name, el.checked);
|
||||
} else {
|
||||
setValue(el.name, el.value, vConf, el.dataset.type);
|
||||
}
|
||||
vConf.save();
|
||||
})
|
||||
|
||||
body.addDelegatedEventListener('click', '.button[data-action]', (e, el) => {
|
||||
switch (el.dataset.action) {
|
||||
case 'resetVConf':
|
||||
vConf.reset();
|
||||
setTimeout(e => {
|
||||
playerConf.handleById();
|
||||
}, 30);
|
||||
break;
|
||||
case 'makeModalTransparent':
|
||||
$('#modal').toggleClass('lightMode')
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function setValue(name, value, conf, type) {
|
||||
switch (type) {
|
||||
case 'float':
|
||||
value = parseFloat(value);
|
||||
break;
|
||||
case 'int':
|
||||
value = parseInt(value);
|
||||
break;
|
||||
}
|
||||
conf.set(name, value);
|
||||
}
|
||||
|
||||
function setActiveOnPlaylist(e) {
|
||||
|
@ -47,6 +141,8 @@ function setActiveOnPlaylist(e) {
|
|||
function toggleShuffle() {
|
||||
let active = player.playlist.isShuffle;
|
||||
$('#shuffle').toggleCheck('active', active);
|
||||
let status = active ? 'enabled' : 'disabled';
|
||||
NotificationHandler.createNotification("Shuffle: " + status, "info", 500);
|
||||
}
|
||||
|
||||
function togglePlayButton(status) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// most of the functions are from https://webglfundamentals.org/webgl/resources/m4.js! but i doesnt want to use them all and make some adjustment to them!
|
||||
class TDUtils {
|
||||
static lastMatrix = {m: null};
|
||||
|
||||
static multiply(a, b) {
|
||||
let b00 = b[0];
|
||||
let b01 = b[1];
|
||||
|
@ -93,9 +95,10 @@ class TDUtils {
|
|||
return dst;
|
||||
}
|
||||
|
||||
static xRotation(angleInRadians) {
|
||||
let c = Math.cos(angleInRadians);
|
||||
let s = Math.sin(angleInRadians);
|
||||
static xRotation(angle) {
|
||||
angle = TDUtils.degToRad(angle);
|
||||
let c = Math.cos(angle);
|
||||
let s = Math.sin(angle);
|
||||
|
||||
return [
|
||||
1, 0, 0, 0,
|
||||
|
@ -105,9 +108,10 @@ class TDUtils {
|
|||
];
|
||||
}
|
||||
|
||||
static yRotation(angleInRadians) {
|
||||
let c = Math.cos(angleInRadians);
|
||||
let s = Math.sin(angleInRadians);
|
||||
static yRotation(angle) {
|
||||
angle = TDUtils.degToRad(angle);
|
||||
let c = Math.cos(angle);
|
||||
let s = Math.sin(angle);
|
||||
|
||||
return [
|
||||
c, 0, -s, 0,
|
||||
|
@ -117,9 +121,10 @@ class TDUtils {
|
|||
];
|
||||
}
|
||||
|
||||
static zRotation(angleInRadians) {
|
||||
let c = Math.cos(angleInRadians);
|
||||
let s = Math.sin(angleInRadians);
|
||||
static zRotation(angle) {
|
||||
angle = TDUtils.degToRad(angle);
|
||||
let c = Math.cos(angle);
|
||||
let s = Math.sin(angle);
|
||||
|
||||
return [
|
||||
c, s, 0, 0,
|
||||
|
@ -300,8 +305,6 @@ class TDUtils {
|
|||
]
|
||||
}
|
||||
|
||||
static lastMatrix = {m: null};
|
||||
|
||||
static getMatrix(fov, aspect, near, far, camAngle, radius) {
|
||||
let lMat = this.lastMatrix,
|
||||
u = TDUtils;
|
||||
|
@ -330,4 +333,14 @@ class TDUtils {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static updateRotate(rotation, def) {
|
||||
let value = vConf.get(rotation, def) + vConf.get(rotation + '-inc', 0)
|
||||
if (value > 360) {
|
||||
value -= 360;
|
||||
} else if (value < -360) {
|
||||
value += 360;
|
||||
}
|
||||
vConf.set(rotation, value);
|
||||
}
|
||||
}
|
|
@ -3,23 +3,21 @@ class GUI {
|
|||
this.data = {};
|
||||
this.modal = new Modal();
|
||||
// load first vis window!
|
||||
await this.loadForVis();
|
||||
await template.loadArray([
|
||||
'playlist-item',
|
||||
'playlist',
|
||||
'playlist-footer'
|
||||
'playlist-footer',
|
||||
'audio-information',
|
||||
'inputs/color',
|
||||
'inputs/input',
|
||||
'inputs/slider',
|
||||
'inputs/switch',
|
||||
'inputs/select',
|
||||
'inputs/option'
|
||||
]);
|
||||
this.initDropZone();
|
||||
}
|
||||
|
||||
async loadForVis() {
|
||||
let c = visual.c,
|
||||
d = this.data[c];
|
||||
if (d == null) {
|
||||
this.data[c] = await fetch("out/gui/" + c + ".json").then((r) => r.json());
|
||||
}
|
||||
}
|
||||
|
||||
initDropZone() {
|
||||
let items = 'drag dragstart dragend dragover dragenter dragleave drop'.split(' ');
|
||||
items.forEach(el => {
|
||||
|
@ -39,6 +37,109 @@ class GUI {
|
|||
};
|
||||
}
|
||||
|
||||
// create config Inputs from JSON
|
||||
class GUIHelper {
|
||||
static fromJSON(json, conf) {
|
||||
let data = [];
|
||||
for (let item of json) {
|
||||
switch (item.type) {
|
||||
case 'slider':
|
||||
data.push(GUIHelper.createSliders(item, conf));
|
||||
break;
|
||||
case 'color':
|
||||
data.push(GUIHelper.createColorPicker(item, conf));
|
||||
break;
|
||||
case 'checkbox':
|
||||
data.push(GUIHelper.createCheckbox(item, conf));
|
||||
break;
|
||||
case 'input':
|
||||
data.push(GUIHelper.createInputField(item, conf));
|
||||
break;
|
||||
case 'select':
|
||||
data.push(GUIHelper.createSelect(item, conf));
|
||||
break;
|
||||
case 'button':
|
||||
data.push(GUIHelper.createButton(item, conf));
|
||||
break;
|
||||
default:
|
||||
console.error(`Unknown Type: ${item.type}`);
|
||||
}
|
||||
}
|
||||
return data.join(" ");
|
||||
}
|
||||
|
||||
static createSliders(data, conf) {
|
||||
let content = "";
|
||||
if (typeof data.name === "object") {
|
||||
for (let i = 0; i < data.name.length; i++) {
|
||||
let newData = {};
|
||||
Object.assign(newData, data);
|
||||
newData.showName = data.name[i];
|
||||
newData.name = GUIHelper.richName(data, data.props[i]);
|
||||
content += GUIHelper.createSlider(newData, conf);
|
||||
}
|
||||
} else {
|
||||
content = GUIHelper.createSlider(data, conf);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
static createSlider(data, conf) {
|
||||
let newData = {};
|
||||
Object.assign(newData, data);
|
||||
newData.value = conf.get(newData.name, newData.value);
|
||||
return template.parseTemplate('inputs/slider', newData)
|
||||
}
|
||||
|
||||
static createColorPicker(data, conf) {
|
||||
let newData = {};
|
||||
Object.assign(newData, data);
|
||||
newData.value = conf.get(newData.name, newData.value);
|
||||
return template.parseTemplate('inputs/color', newData)
|
||||
}
|
||||
|
||||
static createCheckbox(data, conf) {
|
||||
let newData = {};
|
||||
Object.assign(newData, data);
|
||||
newData.value = conf.get(newData.name, newData.value) ? 'checked' : '';
|
||||
return template.parseTemplate('inputs/switch', newData)
|
||||
}
|
||||
|
||||
static createInputField(data, conf) {
|
||||
let newData = {};
|
||||
Object.assign(newData, data);
|
||||
newData.value = conf.get(newData.name, newData.value);
|
||||
return template.parseTemplate('inputs/input', newData)
|
||||
}
|
||||
|
||||
static createSelect(data, conf) {
|
||||
let newData = {};
|
||||
Object.assign(newData, data);
|
||||
newData.value = conf.get(newData.name, newData.value);
|
||||
let options = '';
|
||||
for (let i = 0; i < newData.options.length; i++) {
|
||||
options += template.parseTemplate('inputs/option', {
|
||||
value: newData.options[i]
|
||||
})
|
||||
}
|
||||
newData.options = options;
|
||||
newData.event = 'visualConf';
|
||||
newData.conf = conf.type;
|
||||
return template.parseTemplate('inputs/select', newData)
|
||||
}
|
||||
|
||||
static richName(data, name) {
|
||||
if (data.group !== "") {
|
||||
return data.group + "-" + name
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
static createButton(item, conf) {
|
||||
return `<div class='button spaced' data-action="${item.action}">${item.name}</div>`
|
||||
}
|
||||
}
|
||||
|
||||
class Modal {
|
||||
constructor() {
|
||||
let self = this;
|
||||
|
@ -53,6 +154,7 @@ class Modal {
|
|||
}
|
||||
|
||||
renderModal(title, content, footer) {
|
||||
$('#modal').removeClass('lightMode')
|
||||
this.currentModal = title;
|
||||
this.renderHeader(title);
|
||||
this.renderContent(content);
|
||||
|
@ -70,8 +172,8 @@ class Modal {
|
|||
}
|
||||
|
||||
renderFooter(footer) {
|
||||
let con = $('modal-footer', this.modal);
|
||||
con.innerHTML = footer;
|
||||
let con = $('modal-footer .inner', this.modal);
|
||||
con.innerHTML = footer || "by VersusTuneZ";
|
||||
}
|
||||
|
||||
closeModal() {
|
||||
|
|
|
@ -53,10 +53,6 @@ class ImageUploader {
|
|||
let body = $('body');
|
||||
body.style.backgroundImage = 'url(' + this.image + ')';
|
||||
body.style.backgroundColor = this.color;
|
||||
let blob = $('#colorBlob');
|
||||
if (blob) {
|
||||
blob.style.backgroundColor = this.color;
|
||||
}
|
||||
}
|
||||
|
||||
getRealImage() {
|
||||
|
|
|
@ -1,5 +1,77 @@
|
|||
class Notification {
|
||||
constructor() {
|
||||
class NotificationHandler {
|
||||
static instance = new NotificationHandler();
|
||||
|
||||
constructor() {
|
||||
this.outer = $('.notification');
|
||||
this.notifications = [];
|
||||
}
|
||||
|
||||
async init() {
|
||||
await template.loadTemplate('notification');
|
||||
}
|
||||
|
||||
static createNotification(message, type, time) {
|
||||
time = parseInt(time || "3000");
|
||||
let handler = NotificationHandler.instance,
|
||||
not = new Notification(message, type, time);
|
||||
handler.notifications.push(not);
|
||||
not.show();
|
||||
return not;
|
||||
}
|
||||
}
|
||||
|
||||
class Notification {
|
||||
constructor(message, type, time) {
|
||||
this.outer = NotificationHandler.instance.outer;
|
||||
this.message = message;
|
||||
this.type = type;
|
||||
this.time = time;
|
||||
this.isRemoved = false;
|
||||
}
|
||||
|
||||
async show() {
|
||||
let self = this,
|
||||
endless = self.time === -1;
|
||||
|
||||
self.item = create('div');
|
||||
self.item.addClass('notification-item, ' + self.type);
|
||||
if (endless) {
|
||||
self.type += ' endless';
|
||||
}
|
||||
self.updateContent(self.message);
|
||||
this.outer.appendChild(self.item);
|
||||
if (!endless) {
|
||||
setTimeout(this.remove.bind(this), self.time)
|
||||
}
|
||||
}
|
||||
|
||||
async remove() {
|
||||
if (this.isRemoved) {
|
||||
return;
|
||||
}
|
||||
this.isRemoved = true;
|
||||
this.outer.removeChild(this.item);
|
||||
let not = NotificationHandler.instance.notifications,
|
||||
index = not.indexOf(this);
|
||||
not.splice(index, 1);
|
||||
delete this;
|
||||
}
|
||||
|
||||
updateContent(message) {
|
||||
let self = this,
|
||||
isEndless = self.time === -1,
|
||||
data = {
|
||||
message: message,
|
||||
time: isEndless ? 1000 : self.time + 1,
|
||||
type: self.type,
|
||||
}
|
||||
this.item.innerHTML = template.parseTemplate('notification', data);
|
||||
}
|
||||
|
||||
updateMessageOnly(message) {
|
||||
let item = $('.message', this.item);
|
||||
if (item) {
|
||||
item.innerHTML = message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,18 +5,18 @@ class Player {
|
|||
|
||||
nextSong() {
|
||||
let next = this.playlist.getNext();
|
||||
audioHandler.loadSong(next.file);
|
||||
audioHandler.loadSong(next);
|
||||
}
|
||||
|
||||
prevSong() {
|
||||
let next = this.playlist.getPrevious();
|
||||
audioHandler.loadSong(next.file);
|
||||
audioHandler.loadSong(next);
|
||||
}
|
||||
|
||||
playStop() {
|
||||
if (!audioHandler.lastSong) {
|
||||
let next = this.playlist.getCurrent();
|
||||
audioHandler.loadSong(next.file);
|
||||
audioHandler.loadSong(next);
|
||||
return;
|
||||
}
|
||||
let audioFile = audioHandler.audioFile;
|
||||
|
@ -31,7 +31,7 @@ class Player {
|
|||
playByID(number) {
|
||||
this.playlist.index = number;
|
||||
let next = this.playlist.getCurrent();
|
||||
audioHandler.loadSong(next.file);
|
||||
audioHandler.loadSong(next);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,13 +46,16 @@ class Playlist {
|
|||
this.isShuffle = false;
|
||||
$('body').addDelegatedEventListener('change', 'input[type="file"]', this.changeFiles.bind(this));
|
||||
$('body').addDelegatedEventListener('click', '.pagination .item', this.handlePagination.bind(this));
|
||||
eventHandler.addEvent('id3-request', this.handle.bind(this));
|
||||
eventHandler.addEvent('id3-request-force', this.forceID3.bind(this));
|
||||
}
|
||||
|
||||
shuffle() {
|
||||
// only shuffle if more then 2 elements are in
|
||||
let len = this.list.length;
|
||||
if (len < 3) {
|
||||
this.shuffled = this.list;
|
||||
this.shuffled = [0, 1, 2];
|
||||
return;
|
||||
}
|
||||
// the current-list need to be shuffled...
|
||||
for (let i = 0; i < len; i++) {
|
||||
|
@ -62,40 +65,40 @@ class Playlist {
|
|||
}
|
||||
|
||||
swap(a, b) {
|
||||
this.shuffled[a] = this.list[b];
|
||||
this.shuffled[b] = this.list[a];
|
||||
this.shuffled[a] = b;
|
||||
this.shuffled[b] = a;
|
||||
}
|
||||
|
||||
getNext() {
|
||||
let items = this.isShuffle ? this.shuffled : this.list,
|
||||
let items = this.list,
|
||||
len = items.length - 1,
|
||||
next = this.index + 1;
|
||||
if (next > len) {
|
||||
next = 0;
|
||||
}
|
||||
this.index = next;
|
||||
return items[next];
|
||||
return items[this.getRealIndex()];
|
||||
}
|
||||
|
||||
getPrevious() {
|
||||
let items = this.isShuffle ? this.shuffled : this.list,
|
||||
let items = this.list,
|
||||
len = items.length - 1,
|
||||
next = this.index - 1;
|
||||
if (next < 0) {
|
||||
next = len;
|
||||
}
|
||||
this.index = next;
|
||||
return items[next];
|
||||
return items[this.getRealIndex()];
|
||||
}
|
||||
|
||||
getCurrent() {
|
||||
let items = this.isShuffle ? this.shuffled : this.list;
|
||||
return items[this.index];
|
||||
return this.list[this.getRealIndex()];
|
||||
}
|
||||
|
||||
// on new upload... this has to be an array!
|
||||
setPlaylist(files) {
|
||||
this.index = 0;
|
||||
this.forceData = undefined;
|
||||
this.list = files;
|
||||
this.shuffle();
|
||||
}
|
||||
|
@ -112,6 +115,9 @@ class Playlist {
|
|||
}
|
||||
|
||||
renderPagination(page) {
|
||||
if (page === undefined) {
|
||||
page = this.page;
|
||||
}
|
||||
let length = this.list.length,
|
||||
maxSite = Math.ceil(length / PAGINATIONLIMIT) - 1;
|
||||
if (page < 0) {
|
||||
|
@ -128,12 +134,12 @@ class Playlist {
|
|||
e = length;
|
||||
}
|
||||
if (length > 0) {
|
||||
let items = this.isShuffle ? this.shuffled : this.list;
|
||||
let items = this.list;
|
||||
for (let i = s; i < e; i++) {
|
||||
let obj = {
|
||||
index: i.toString(),
|
||||
nr: i + 1,
|
||||
title: items[i].name,
|
||||
title: items[this.getRealIndex(i)].getAudioName(),
|
||||
active: !audioHandler.audioFile.paused && i === this.index ? 'active' : ''
|
||||
}
|
||||
data += template.parseTemplate("playlist-item", obj);
|
||||
|
@ -165,21 +171,57 @@ class Playlist {
|
|||
let i = 0;
|
||||
for (let file of el.files) {
|
||||
if (file && file.type.indexOf('audio') !== -1 && file.name.match(".m3u") === null) {
|
||||
let name = file.name.split(".");
|
||||
name.pop();
|
||||
name = name.join(".");
|
||||
files.push({
|
||||
file: file,
|
||||
name: name,
|
||||
index: i++
|
||||
});
|
||||
let audioFile = new AudioPlayerFile(file, i++);
|
||||
files.push(audioFile);
|
||||
}
|
||||
}
|
||||
this.setPlaylist(files);
|
||||
if (files.length > 0) {
|
||||
NotificationHandler.createNotification("Songs added successfully!<br> Songs: " + files.length, "success", 3000);
|
||||
this.renderPagination(0);
|
||||
} else {
|
||||
alert("No Valid AudioFiles found!");
|
||||
NotificationHandler.createNotification("File Upload failed!", "error", 3000);
|
||||
}
|
||||
}
|
||||
|
||||
getRealIndex(index) {
|
||||
if (index === undefined) {
|
||||
index = this.index;
|
||||
}
|
||||
if (this.isShuffle) {
|
||||
return this.shuffled[index];
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
handle(data) {
|
||||
let index = data.index;
|
||||
if (data.status === "waiting") {
|
||||
return;
|
||||
}
|
||||
this.list[index].id3 = data;
|
||||
if (this.timeout) {
|
||||
window.clearTimeout(this.timeout);
|
||||
}
|
||||
this.timeout = setTimeout(this.renderPagination.bind(this), 100);
|
||||
}
|
||||
|
||||
forceID3(data) {
|
||||
let self = this;
|
||||
if (!self.forceData) {
|
||||
self.forceData = {};
|
||||
self.forceNotification = NotificationHandler.createNotification("TagReader -> 0 / " + self.list.length, "info", -1);
|
||||
}
|
||||
let index = data.index;
|
||||
if (data.status === "waiting") {
|
||||
return;
|
||||
}
|
||||
self.list[index].id3 = data;
|
||||
self.forceData[index] = true;
|
||||
let leng = Object.keys(self.forceData).length;
|
||||
this.forceNotification.updateMessageOnly("TagReader -> " + leng + " / " + self.list.length);
|
||||
if (leng === self.list.length) {
|
||||
self.forceNotification.remove()
|
||||
}
|
||||
}
|
||||
}
|
106
raw/javascript/playerConfigHandler.js
Normal file
106
raw/javascript/playerConfigHandler.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
class PlayerConfigHandler {
|
||||
async init() {
|
||||
await template.loadArray([
|
||||
'config/nav',
|
||||
'config/content',
|
||||
'config/visualitem'
|
||||
]);
|
||||
this.last = 'base';
|
||||
$('.settings-icon').addEventListener('click', this.open.bind(this));
|
||||
$('modal-content').addDelegatedEventListener('click', '.config-nav .item', this.navHandler.bind(this));
|
||||
}
|
||||
|
||||
open() {
|
||||
if (this.content === undefined) {
|
||||
let content = template.parseTemplate('config/nav', {});
|
||||
content += template.parseTemplate('config/content', {content: ""});
|
||||
this.content = content;
|
||||
}
|
||||
gui.modal.renderModal('Settings', this.content, "by VersusTuneZ");
|
||||
this.handleById();
|
||||
gui.modal.showModal();
|
||||
}
|
||||
|
||||
navHandler(e, el) {
|
||||
this.last = el.dataset.id;
|
||||
this.handleById();
|
||||
}
|
||||
|
||||
handleById() {
|
||||
let id = this.last;
|
||||
new VisualConfig(id === 'visual', id === 'base');
|
||||
let active = $('.config-nav .item.active'),
|
||||
current = $('.config-nav .item[data-id="' + id + '"]');
|
||||
if (active) {
|
||||
active.removeClass('active');
|
||||
}
|
||||
if (current) {
|
||||
current.addClass('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VisualConfig {
|
||||
static visualTemplates = {};
|
||||
|
||||
constructor(showVisual, renderBase) {
|
||||
this.content = $('modal-content .config-content');
|
||||
if (showVisual) {
|
||||
this.renderVisualConfig(visual.c);
|
||||
} else {
|
||||
if (renderBase) {
|
||||
this.renderBase();
|
||||
} else {
|
||||
this.renderVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderVisuals() {
|
||||
let keys = Object.keys(visual.visuals),
|
||||
content = '<section class="visuals">';
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
content += template.parseTemplate('config/visualitem', {
|
||||
title: visual.visuals[keys[i]].name,
|
||||
id: keys[i],
|
||||
active: keys[i] === visual.c ? 'active' : ''
|
||||
})
|
||||
}
|
||||
content += '</div>';
|
||||
this.content.innerHTML = content;
|
||||
}
|
||||
|
||||
async renderBase() {
|
||||
let data = await this.loadVisualConfig('base'),
|
||||
div = create('section');
|
||||
div.addClass('base');
|
||||
div.innerHTML = GUIHelper.fromJSON(data, pConf);
|
||||
this.content.innerHTML = div.outerHTML;
|
||||
}
|
||||
|
||||
// the name loads the json and handle it!
|
||||
async renderVisualConfig(name) {
|
||||
let data = await this.loadVisualConfig(name, vConf),
|
||||
div = create('section');
|
||||
div.addClass('visual');
|
||||
div.innerHTML = GUIHelper.fromJSON(data, vConf);
|
||||
div.innerHTML += GUIHelper.createButton({
|
||||
action: "resetVConf",
|
||||
name: "Reset Visual Config"
|
||||
});
|
||||
div.innerHTML += GUIHelper.createButton({
|
||||
action: "makeModalTransparent",
|
||||
name: "toggle Modal Opacity"
|
||||
})
|
||||
this.content.innerHTML = div.outerHTML;
|
||||
}
|
||||
|
||||
async loadVisualConfig(name) {
|
||||
let tem = VisualConfig.visualTemplates;
|
||||
if (!tem[name]) {
|
||||
//load config and save it
|
||||
tem[name] = await fetch('/out/gui/' + name + ".json").then((res) => res.json());
|
||||
}
|
||||
return tem[name];
|
||||
}
|
||||
}
|
64
raw/javascript/select.js
Normal file
64
raw/javascript/select.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
(function () {
|
||||
const body = $('body');
|
||||
body.addDelegatedEventListener('click', 'custom-select .label', (e, el) => {
|
||||
let parent = el.parentNode;
|
||||
let options = $$('custom-option', parent),
|
||||
optionsDiv = $('custom-options', parent);
|
||||
if (parent.hasClass('open')) {
|
||||
optionsDiv.style.maxHeight = '';
|
||||
parent.removeClass('open');
|
||||
} else {
|
||||
let sum = 0;
|
||||
options.forEach(function (element) {
|
||||
sum += element.offsetHeight;
|
||||
});
|
||||
optionsDiv.style.maxHeight = sum + 'px';
|
||||
parent.addClass('open');
|
||||
}
|
||||
})
|
||||
body.addDelegatedEventListener('click', 'custom-select custom-option', (e, el) => {
|
||||
let select = el.closest('custom-select'),
|
||||
input = $('input', select);
|
||||
$$('custom-option.active').forEach(activeEl => {
|
||||
activeEl.removeClass('active');
|
||||
})
|
||||
el.addClass('active');
|
||||
if (input) {
|
||||
input.value = el.dataset.value || el.innerText;
|
||||
$('.label', select).innerText = el.innerText;
|
||||
select.removeClass('open');
|
||||
el.parentNode.style.maxHeight = '';
|
||||
window.dispatchEvent(new CustomEvent('selectChanged', {
|
||||
detail: {
|
||||
select: select,
|
||||
event: select.dataset.event,
|
||||
value: input.value,
|
||||
name: input.name
|
||||
}
|
||||
}));
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('selectChanged', (e) => {
|
||||
if (e.detail.event === 'visualConf') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleVisualConf(e.detail);
|
||||
}
|
||||
})
|
||||
|
||||
function handleVisualConf(e) {
|
||||
try {
|
||||
let value = e.value,
|
||||
config = Config.allConfigs[e.select.dataset.conf];
|
||||
if (e.name === 'fftSize') {
|
||||
value = parseInt(e.value);
|
||||
visual.visuals[visual.c].updateFFT(value);
|
||||
}
|
||||
config.set(e.name, value);
|
||||
config.save();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
})()
|
23
raw/javascript/startup.js
Normal file
23
raw/javascript/startup.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
class Startup {
|
||||
constructor() {
|
||||
this.modules = {
|
||||
'startup': false,
|
||||
'id3-ready': false
|
||||
};
|
||||
}
|
||||
|
||||
moduleLoaded(name) {
|
||||
this.modules[name] = true
|
||||
this.allModulesLoaded();
|
||||
}
|
||||
|
||||
allModulesLoaded() {
|
||||
for (let module in this.modules) {
|
||||
if (!this.modules[module]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
window.dispatchEvent(new CustomEvent('startupFin'));
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -28,7 +28,11 @@ class Template {
|
|||
templateEx.lastIndex++;
|
||||
}
|
||||
let key = m[0];
|
||||
d = d.replace(key, data[m[1]] || "")
|
||||
let value = data[m[1]];
|
||||
if (value === undefined || value === null) {
|
||||
value = "";
|
||||
}
|
||||
d = d.replace(key, value)
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
|
|
@ -159,7 +159,12 @@ Node.prototype.addDelegatedEventListener = function (type, aim, cb) {
|
|||
} else {
|
||||
let parent = target.closest(aim);
|
||||
if (parent) {
|
||||
cb(event, parent);
|
||||
try {
|
||||
cb(event, parent);
|
||||
} catch (e) {
|
||||
NotificationHandler.createNotification("FATAL ERROR WITHIN HANDLER!", "error", 1000);
|
||||
//nothing!
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -249,23 +254,12 @@ function append(to, array) {
|
|||
}
|
||||
}
|
||||
|
||||
function loadFromJSONToVisualData(useKeys) {
|
||||
fetch('/audio-vis/out/showCase.json').then((res) => {
|
||||
return res.json()
|
||||
}).then(e => {
|
||||
let floatArray;
|
||||
if (useKeys) {
|
||||
let keys = Object.keys(e);
|
||||
floatArray = new Float32Array(keys.length);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
floatArray[i] = e[keys[i]];
|
||||
}
|
||||
} else {
|
||||
floatArray = new Float32Array(e.length);
|
||||
for (let i = 0; i < e.length; i++) {
|
||||
floatArray[i] = e[i];
|
||||
}
|
||||
}
|
||||
visual.visuals[visual.c].data = floatArray;
|
||||
})
|
||||
function hexToRgb(hex) {
|
||||
hex = hex.replace("#", "");
|
||||
let bigint = parseInt(hex, 16),
|
||||
r = (bigint >> 16) & 255,
|
||||
g = (bigint >> 8) & 255,
|
||||
b = bigint & 255;
|
||||
|
||||
return [r / 255, g / 255, b / 255];
|
||||
}
|
|
@ -2,10 +2,13 @@ class Visual {
|
|||
constructor() {
|
||||
this.data = []; //for drawing
|
||||
this.dataArray = [];
|
||||
this.name = "Default";
|
||||
}
|
||||
|
||||
updateData() {
|
||||
}
|
||||
|
||||
updateFFT(fftSize) {
|
||||
}
|
||||
|
||||
draw() {
|
||||
|
@ -18,14 +21,23 @@ class Visual {
|
|||
class VisualDrawer {
|
||||
constructor() {
|
||||
this.visuals = {
|
||||
"sphere": new Sphere(),
|
||||
//"sphere": new Sphere(),
|
||||
"wave": new Wave(),
|
||||
"water": new Water()
|
||||
"wave2d": new Wave2D(),
|
||||
//"water": new Water()
|
||||
}
|
||||
this.lastMainColor = {
|
||||
base: '#-1',
|
||||
color: [0, 0, 0]
|
||||
};
|
||||
this.lastSecondColor = {
|
||||
base: '#-1',
|
||||
color: [0, 0, 0]
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.switch('wave');
|
||||
this.switch(pConf.get("visual", "wave2d"));
|
||||
this.updateLoop();
|
||||
}
|
||||
|
||||
|
@ -34,20 +46,38 @@ class VisualDrawer {
|
|||
this.c = visual;
|
||||
vConf.loadConfigByName(this.c);
|
||||
this.visuals[this.c].setup();
|
||||
pConf.set("visual", this.c);
|
||||
pConf.save();
|
||||
}
|
||||
}
|
||||
|
||||
updateLoop() {
|
||||
let self = this;
|
||||
let pro = shaderHandler.use(self.c);
|
||||
let vis = self.visuals[self.c];
|
||||
let pro = shaderHandler.use(self.c);
|
||||
this.updateSeekbar();
|
||||
this.prepare(pro);
|
||||
vis.updateData();
|
||||
this.prepare();
|
||||
vis.draw(pro);
|
||||
requestAnimationFrame(self.updateLoop.bind(self))
|
||||
}
|
||||
|
||||
prepare() {
|
||||
updateSeekbar() {
|
||||
cInfo.width = window.innerWidth;
|
||||
cInfo.height = window.innerHeight;
|
||||
let audioFile = audioHandler.audioFile;
|
||||
ctx.clearRect(0, 0, cInfo.width, cInfo.height);
|
||||
if (!audioFile.paused && pConf.get("showSeekbar", true)) {
|
||||
//show seekbar
|
||||
let dur = audioFile.duration,
|
||||
cur = audioFile.currentTime,
|
||||
percent = cur / dur * cInfo.width;
|
||||
ctx.fillStyle = pConf.get("seekColor", '#fff');
|
||||
ctx.fillRect(0, c.height - 10, percent, c.height);
|
||||
}
|
||||
}
|
||||
|
||||
prepare(pro) {
|
||||
c.width = window.innerWidth;
|
||||
c.height = window.innerHeight;
|
||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||
|
@ -56,5 +86,29 @@ class VisualDrawer {
|
|||
gl.enable(gl.DEPTH_TEST);
|
||||
gl.depthFunc(gl.LEQUAL);
|
||||
gl.enable(gl.CULL_FACE);
|
||||
|
||||
// u_baseColor || u_maxColor
|
||||
this.setColor(pro);
|
||||
}
|
||||
|
||||
setColor(program) {
|
||||
let baseColor = gl.getUniformLocation(program, "u_baseColor"),
|
||||
maxColor = gl.getUniformLocation(program, "u_maxColor"),
|
||||
self = this,
|
||||
mainColor = self.lastMainColor,
|
||||
secondColor = self.lastSecondColor;
|
||||
this.updateColor('lastMainColor', 'baseColor');
|
||||
this.updateColor('lastSecondColor', 'gradientToColor');
|
||||
gl.uniform3fv(baseColor, mainColor.color);
|
||||
gl.uniform3fv(maxColor, secondColor.color);
|
||||
}
|
||||
|
||||
updateColor(index, col) {
|
||||
let color = this[index],
|
||||
value = vConf.get(col, '#ffffff')
|
||||
if(value !== color.base) {
|
||||
color.color = hexToRgb(value);
|
||||
color.base = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,9 @@
|
|||
class Sphere extends Visual {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "Sphere";
|
||||
}
|
||||
|
||||
draw() {
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
//animate Water the way like the Audio is Coming... 256FFT-Size max!
|
||||
class Water extends Visual {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "Water";
|
||||
}
|
||||
|
||||
draw() {
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// 3D Audio-Waves -> maybe also 2D?
|
||||
class Wave extends Visual {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "3D Wave";
|
||||
}
|
||||
|
||||
updateData() {
|
||||
//only for debug! remove pls
|
||||
if (window.stopUpdate) {
|
||||
return;
|
||||
}
|
||||
let data = audioHandler.getFloatArray();
|
||||
let add = 2 / data.length,
|
||||
x = -1;
|
||||
|
@ -36,6 +37,7 @@ class Wave extends Visual {
|
|||
gl.enableVertexAttribArray(position);
|
||||
gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0);
|
||||
gl.drawArrays(vConf.get("waveForm", gl.TRIANGLES), 0, this.data.length / 3);
|
||||
this.afterDraw();
|
||||
}
|
||||
|
||||
rotate(program) {
|
||||
|
@ -46,27 +48,36 @@ class Wave extends Visual {
|
|||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
]
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("xRotate", 0)));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("yRotate", 0)));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("zRotate", 0)));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("rotation-x", 10)));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("rotation-y", 50)));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("rotation-z", -30)));
|
||||
let rotate = gl.getUniformLocation(program, "u_matrix");
|
||||
gl.uniformMatrix4fv(rotate, false, matrix);
|
||||
}
|
||||
|
||||
setup() {
|
||||
audioHandler.fftSize(16384)
|
||||
this.data = new Float32Array(16384 * 9);
|
||||
vConf.get("zRotate", TDUtils.degToRad(-30));
|
||||
vConf.get("yRotate", TDUtils.degToRad(50));
|
||||
vConf.get("xRotate", TDUtils.degToRad(10));
|
||||
this.updateFFT(vConf.get('fftSize', 4096));
|
||||
vConf.get("rotation-z", -30);
|
||||
vConf.get("rotation-y", 50);
|
||||
vConf.get("rotation-x", 10);
|
||||
}
|
||||
|
||||
updateFFT(fftSize) {
|
||||
audioHandler.fftSize(fftSize);
|
||||
this.data = new Float32Array(fftSize * 9);
|
||||
}
|
||||
|
||||
prepare(program) {
|
||||
this.position = gl.getAttribLocation(program, "a_position");
|
||||
this.color = gl.getUniformLocation(program, "u_color");
|
||||
let lightPos = gl.getUniformLocation(program, "u_lightPos"),
|
||||
matrix = gl.getUniformLocation(program, "u_matrix");
|
||||
let lightPos = gl.getUniformLocation(program, "u_lightPos");
|
||||
gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56]));
|
||||
//gl.uniformMatrix4fv(matrix, false, TDUtils.getMatrix(90, c.width / c.height, 1, 2000, 200, 200));
|
||||
}
|
||||
|
||||
afterDraw() {
|
||||
TDUtils.updateRotate('rotation-x', 10);
|
||||
TDUtils.updateRotate('rotation-y', 50);
|
||||
TDUtils.updateRotate('rotation-z', -30);
|
||||
vConf.save();
|
||||
}
|
||||
}
|
73
raw/javascript/visuals/wave2d.js
Normal file
73
raw/javascript/visuals/wave2d.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
class Wave2D extends Visual {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "2D Wave";
|
||||
}
|
||||
|
||||
updateData() {
|
||||
let data = audioHandler.getFloatArray();
|
||||
let add = 2 / data.length,
|
||||
x = -1;
|
||||
let outerLoop = 0;
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
//first
|
||||
this.data[outerLoop] = x;
|
||||
this.data[outerLoop + 1] = data[i];
|
||||
this.data[outerLoop + 2] = data[i];
|
||||
outerLoop += 3;
|
||||
x += add;
|
||||
}
|
||||
}
|
||||
|
||||
draw(program) {
|
||||
this.prepare(program);
|
||||
let position = this.position,
|
||||
positionBuffer = gl.createBuffer();
|
||||
this.rotate(program);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(this.data), gl.DYNAMIC_DRAW);
|
||||
let vao = gl.createVertexArray();
|
||||
gl.bindVertexArray(vao);
|
||||
gl.enableVertexAttribArray(position);
|
||||
gl.vertexAttribPointer(position, 3, gl.FLOAT, true, 0, 0);
|
||||
gl.drawArrays(vConf.get("waveForm", gl.LINE_STRIP), 0, this.data.length / 3);
|
||||
this.afterDraw();
|
||||
}
|
||||
|
||||
rotate(program) {
|
||||
let matrix = [
|
||||
1, 0, 0, 0,
|
||||
0, 0.6, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1
|
||||
]
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.xRotation(vConf.get("rotation-x", 0)));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.yRotation(vConf.get("rotation-y", 0)));
|
||||
matrix = TDUtils.multiply(matrix, TDUtils.zRotation(vConf.get("rotation-z", 0)));
|
||||
let rotate = gl.getUniformLocation(program, "u_matrix");
|
||||
gl.uniformMatrix4fv(rotate, false, matrix);
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.updateFFT(vConf.get('fftSize', 16384));
|
||||
}
|
||||
|
||||
updateFFT(fftSize) {
|
||||
audioHandler.fftSize(fftSize);
|
||||
this.data = new Float32Array(fftSize * 3);
|
||||
}
|
||||
|
||||
prepare(program) {
|
||||
this.position = gl.getAttribLocation(program, "a_position");
|
||||
this.color = gl.getUniformLocation(program, "u_color");
|
||||
let lightPos = gl.getUniformLocation(program, "u_lightPos");
|
||||
gl.uniform3fv(lightPos, vConf.get("light", [0, 5, -56]));
|
||||
}
|
||||
|
||||
afterDraw() {
|
||||
TDUtils.updateRotate('rotation-x', 0);
|
||||
TDUtils.updateRotate('rotation-y', 0);
|
||||
TDUtils.updateRotate('rotation-z', 0);
|
||||
vConf.save();
|
||||
}
|
||||
}
|
54
raw/scss/_config.scss
Normal file
54
raw/scss/_config.scss
Normal file
|
@ -0,0 +1,54 @@
|
|||
.config-nav {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: $nav;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
|
||||
div {
|
||||
padding: 10px 5px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
|
||||
&.active {
|
||||
border-color: $active;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, .4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.config-content {
|
||||
padding: 1em;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.config-content .visuals {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
|
||||
.visual-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1em;
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
background-color: $darker;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
transition: .5s;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: $nav;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border: 1px solid $primary;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
#c {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
group {
|
||||
|
@ -118,15 +122,45 @@ group-input {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
|
||||
&.hide {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
modal-footer playlist {
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.pagination {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#image-upload form {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 90%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
.audio-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.artist {
|
||||
font-size: .75em;
|
||||
color: #dcdcdc;
|
||||
}
|
||||
}
|
||||
|
||||
.now-playing {
|
||||
font-size: .8em;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.menus {
|
||||
z-index: 10;
|
||||
}
|
|
@ -98,11 +98,15 @@
|
|||
}
|
||||
|
||||
.input {
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
background-color: transparent;
|
||||
|
||||
&-range {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
input:focus + .input:after {
|
||||
|
@ -148,20 +152,20 @@ input:focus + .input:after {
|
|||
.floating-label input:focus ~ .input-label,
|
||||
.floating-label input:valid ~ .input-label {
|
||||
transform: translateY(-0.72rem);
|
||||
color: #ff0089;
|
||||
color: $active;
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.floating-label input:valid ~ .input-label {
|
||||
transform: translateY(-0.72rem);
|
||||
color: #3949ab;
|
||||
color: $second;
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.focus {
|
||||
content: '';
|
||||
width: 0;
|
||||
background-color: #ff0a8e;
|
||||
background-color: $second;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
|
@ -180,6 +184,10 @@ input:focus ~ .focus {
|
|||
}
|
||||
|
||||
switch {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 5px;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
appearance: none;
|
||||
|
@ -190,6 +198,10 @@ switch {
|
|||
}
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
border-radius: 10px;
|
||||
|
@ -202,7 +214,7 @@ switch {
|
|||
|
||||
&:after {
|
||||
content: '';
|
||||
background-color: #ff3232;
|
||||
background-color: $primary;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
|
@ -234,7 +246,7 @@ input[type="color"] {
|
|||
}
|
||||
}
|
||||
|
||||
#colorBlob {
|
||||
.colorBlob {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
|
@ -242,12 +254,124 @@ input[type="color"] {
|
|||
}
|
||||
|
||||
.button {
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
border-radius: 5px;
|
||||
border: 1px solid $primary;
|
||||
padding: 0.5em 1em;
|
||||
cursor: pointer;
|
||||
transition: .5s;
|
||||
|
||||
&.spaced {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $second;
|
||||
border-radius: 7px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
}
|
||||
}
|
||||
|
||||
.input {
|
||||
.max, .min, .current {
|
||||
font-size: .8em;
|
||||
color: $second;
|
||||
position: absolute;
|
||||
bottom: -1rem;
|
||||
}
|
||||
|
||||
.current {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.min {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.max {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
custom-select {
|
||||
width: auto;
|
||||
min-width: 200px;
|
||||
display: block;
|
||||
|
||||
label {
|
||||
color: #ff0089;
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 10px 30px 10px 10px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all .3s;
|
||||
box-shadow: inset 0 0 3px rgba(94, 94, 94, 1);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
border: solid #dcdcdc;
|
||||
border-width: 0 3px 3px 0;
|
||||
display: inline-block;
|
||||
padding: 3px;
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
transition: all .3s ease-in-out;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: calc(50% - 6px);
|
||||
}
|
||||
}
|
||||
|
||||
custom-options {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: all .5s ease;
|
||||
font-size: .9em;
|
||||
display: block;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
custom-option {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 5px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px -1px 0px 0px rgba(0, 0, 0, .08);
|
||||
|
||||
&:hover {
|
||||
background-color: $primary;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
custom-option.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
custom-select.open {
|
||||
.label {
|
||||
&::after {
|
||||
transform: rotate(-135deg);
|
||||
-webkit-transform: rotate(-135deg);
|
||||
top: calc(50% - 3px);
|
||||
}
|
||||
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
custom-options {
|
||||
max-height: none;
|
||||
border: 1px solid $nav !important;
|
||||
}
|
||||
}
|
|
@ -7,6 +7,10 @@
|
|||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
|
||||
&.lightMode {
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
}
|
||||
|
||||
div {
|
||||
position: unset;
|
||||
}
|
||||
|
@ -45,5 +49,11 @@
|
|||
modal-footer {
|
||||
display: block;
|
||||
box-shadow: 0 -3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
|
||||
.inner {
|
||||
padding: 5px;
|
||||
width: calc(100% - 40px);
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,117 @@
|
|||
.notification {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
width: 320px;
|
||||
flex-direction: column-reverse;
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
pointer-events: none;
|
||||
|
||||
.notification-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
div {
|
||||
position: unset;
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
div.notification-item {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23);
|
||||
overflow: hidden;
|
||||
|
||||
&.success {
|
||||
background-color: $success;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: $error;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: $warning;
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: $info;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-item .fade-bar {
|
||||
animation: fadeOut ease-in-out 3000ms;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
opacity: 0.4;
|
||||
transform-origin: left;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
&.endless {
|
||||
animation: endlessFade ease-in-out 500ms infinite;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: $successBorder;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: $errorBorder;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
background-color: $warningBorder;
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOut {
|
||||
from {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
to {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes endlessFade {
|
||||
0% {
|
||||
transform: scaleX(1);
|
||||
transform-origin: right;
|
||||
}
|
||||
49% {
|
||||
transform-origin: right;
|
||||
}
|
||||
50% {
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
|
@ -65,6 +65,8 @@ playlist {
|
|||
&-number {
|
||||
padding: 5px 10px 5px 5px;
|
||||
width: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
$bg: #303030;
|
||||
$darker: #212121;
|
||||
$nav: #1b1b1b;
|
||||
|
||||
$primary: #3949ab;
|
||||
$second: #ff0089;
|
||||
$active: #5ff507;
|
||||
|
||||
$info: rgba(71,73,171,.6);
|
||||
$success: rgba(74, 177, 11,.6);
|
||||
$error: rgba(255,50,50,.6);
|
||||
$warning: rgba(255, 177, 89, 0.6);
|
||||
|
||||
$successBorder: #60ff00;
|
||||
$errorBorder: #fa0000;
|
||||
$warningBorder: #ff7700;
|
|
@ -19,6 +19,8 @@ html, body {
|
|||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
div {
|
||||
|
@ -51,3 +53,4 @@ div {
|
|||
@import "playlist";
|
||||
@import "modal";
|
||||
@import "notification";
|
||||
@import "config";
|
4
raw/worker/app.js
Normal file
4
raw/worker/app.js
Normal file
|
@ -0,0 +1,4 @@
|
|||
const tagger = new Tagger(self),
|
||||
eventHandler = new EventHandler(self);
|
||||
|
||||
tagger.init();
|
97
raw/worker/database.js
Normal file
97
raw/worker/database.js
Normal file
|
@ -0,0 +1,97 @@
|
|||
class Database {
|
||||
constructor(name, version) {
|
||||
this.isInit = false;
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.errored = false;
|
||||
this.db;
|
||||
this.prepareDB();
|
||||
}
|
||||
|
||||
async prepareDB() {
|
||||
if (this.isInit || this.errored) {
|
||||
return;
|
||||
}
|
||||
let req = this.db = indexedDB.open(this.name, this.version);
|
||||
req.onerror = DatabaseHandler.onError.bind(this);
|
||||
req.onsuccess = DatabaseHandler.onSuccess.bind(this);
|
||||
req.onupgradeneeded = DatabaseHandler.onUpgrade.bind(this);
|
||||
req.onblocked = DatabaseHandler.onBlocked.bind(this);
|
||||
}
|
||||
|
||||
async set(key, data, store) {
|
||||
data['key'] = key;
|
||||
return await this.run('put', data, store);
|
||||
}
|
||||
|
||||
async get(key, store) {
|
||||
return await this.run('get', key, store);
|
||||
}
|
||||
|
||||
async remove(key, store) {
|
||||
return await this.run('delete', key, store);
|
||||
}
|
||||
|
||||
check() {
|
||||
return !(!this.isInit || this.errored);
|
||||
}
|
||||
|
||||
async getTX(store) {
|
||||
return await this.db.transaction([store], "readwrite")
|
||||
}
|
||||
|
||||
async getObjectStore(tx, store) {
|
||||
return await tx.objectStore(store)
|
||||
}
|
||||
|
||||
async run(action, key, store) {
|
||||
if (this.check()) {
|
||||
let tx = await this.getTX(store);
|
||||
let obj = await this.getObjectStore(tx, store);
|
||||
let data = await this.request(obj[action](key));
|
||||
await tx.complete
|
||||
return await data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
request(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = () => resolve(req.result);
|
||||
req.onerror = () => reject(req.error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseHandler {
|
||||
static onError(e) {
|
||||
this.errored = true;
|
||||
eventHandler.sendData("databaseError", "error", e.message);
|
||||
}
|
||||
|
||||
static onSuccess(e) {
|
||||
this.db = this.db.result;
|
||||
this.isInit = true;
|
||||
eventHandler.sendData("databaseCreated", "success", "");
|
||||
eventHandler.handleEvent({
|
||||
data: {
|
||||
cmd: 'dbReady-' + this.name,
|
||||
data: this.db
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static onUpgrade(e) {
|
||||
eventHandler.sendData("databaseUpgradeNeeded", "info", e.message);
|
||||
eventHandler.handleEvent({
|
||||
data: {
|
||||
cmd: 'dbUpgrade-' + this.name,
|
||||
data: this.db
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static onBlocked(e) {
|
||||
eventHandler.sendData("databaseBlocked", "error", e.message);
|
||||
}
|
||||
}
|
30
raw/worker/eventHandler.js
Normal file
30
raw/worker/eventHandler.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
class EventHandler {
|
||||
constructor(worker) {
|
||||
this.events = {};
|
||||
this.worker = worker;
|
||||
this.worker.addEventListener('message', this.handleEvent.bind(this));
|
||||
}
|
||||
|
||||
addEvent(name, cb) {
|
||||
this.events[name] = cb;
|
||||
}
|
||||
|
||||
sendData(name, status, data) {
|
||||
this.worker.postMessage({
|
||||
cmd: name,
|
||||
status: status,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
handleEvent(event) {
|
||||
let data = event.data;
|
||||
if (!data.cmd) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.events[data.cmd]) {
|
||||
this.events[data.cmd](data.data);
|
||||
}
|
||||
}
|
||||
}
|
86
raw/worker/id3.js
Normal file
86
raw/worker/id3.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
self.importScripts('jsmediatags.min.js');
|
||||
|
||||
class Tagger {
|
||||
constructor(worker) {
|
||||
this.db = new Database("SongLib", 1);
|
||||
}
|
||||
|
||||
static prepareName(data) {
|
||||
let name = data.name || '';
|
||||
return name.replace(/[^\w\s]/gi, '').split(" ").join("")
|
||||
}
|
||||
|
||||
init() {
|
||||
eventHandler.addEvent('getData', this.getData.bind(this));
|
||||
eventHandler.addEvent('removeData', this.getData.bind(this));
|
||||
eventHandler.addEvent('setData', this.getData.bind(this));
|
||||
eventHandler.addEvent('dbReady-SongLib', this.ready.bind(this));
|
||||
eventHandler.addEvent('dbUpgrade-SongLib', this.upgrade.bind(this));
|
||||
}
|
||||
|
||||
async getData(data) {
|
||||
let key = Tagger.prepareName(data),
|
||||
newData = await this.db.get(key, 'songs'),
|
||||
handlerName = data.force ? 'id3-request-force' : 'id3-request';
|
||||
if (newData) {
|
||||
newData['index'] = data['index'];
|
||||
eventHandler.sendData(handlerName, 'success', newData);
|
||||
} else {
|
||||
this.parseData(data, key).then(r => {
|
||||
r['index'] = data['index'];
|
||||
eventHandler.sendData(handlerName, 'success', r);
|
||||
});
|
||||
eventHandler.sendData(handlerName, 'waiting', data);
|
||||
}
|
||||
}
|
||||
|
||||
async removeData(data) {
|
||||
let key = Tagger.prepareName(data),
|
||||
newData = await this.db.remove(key, 'songs');
|
||||
eventHandler.sendData('id3-remove', 'success', newData);
|
||||
}
|
||||
|
||||
async setData(data, key) {
|
||||
let newData = await this.db.set(key, data, 'songs');
|
||||
eventHandler.sendData('id3-set', 'success', newData);
|
||||
}
|
||||
|
||||
ready(data) {
|
||||
console.log("[ID3] > Song Database Ready");
|
||||
eventHandler.sendData('id3-ready', "startup", "");
|
||||
}
|
||||
|
||||
upgrade(data) {
|
||||
let db = data.result,
|
||||
songs = db.createObjectStore("songs", {keyPath: 'key'});
|
||||
songs.createIndex("name", "name", {unique: false});
|
||||
}
|
||||
|
||||
//if not found in key-value storage read it! this take some time so this is async!
|
||||
async parseData(data, key) {
|
||||
let tag = await new Promise((resolve, reject) => {
|
||||
new jsmediatags.Reader(data.file)
|
||||
.read({
|
||||
onSuccess: (tag) => {
|
||||
resolve(tag);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(`[ID3] > Error Parsing Data!`);
|
||||
resolve({
|
||||
tags: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
let tags = tag.tags,
|
||||
values = {
|
||||
title: tags.title || data.name,
|
||||
artist: tags.artist || 'VA',
|
||||
genre: tags.genre || 'Unknown',
|
||||
year: tags.year || 1970,
|
||||
key: key
|
||||
};
|
||||
await this.setData(values, key);
|
||||
return values;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
19
shaders/wave2d.frag
Normal 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
19
shaders/wave2d.vert
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue