├── dist ├── skins │ ├── doorn │ │ ├── skin.js │ │ ├── fonts │ │ │ ├── Glyphter.eot │ │ │ ├── Glyphter.ttf │ │ │ ├── Glyphter.woff │ │ │ └── Glyphter.svg │ │ ├── skin.html │ │ └── skin.css │ ├── dummy │ │ ├── skin.js │ │ ├── skin.css │ │ └── skin.html │ ├── in-text │ │ ├── skin.js │ │ ├── fonts │ │ │ ├── Glyphter.eot │ │ │ ├── Glyphter.ttf │ │ │ ├── Glyphter.woff │ │ │ └── Glyphter.svg │ │ ├── skin.html │ │ └── skin.css │ ├── pronunciation │ │ ├── skin.js │ │ ├── fonts │ │ │ ├── Glyphter.eot │ │ │ ├── Glyphter.ttf │ │ │ ├── Glyphter.woff │ │ │ └── Glyphter.svg │ │ ├── skin.html │ │ └── skin.css │ ├── default-album-cover.png │ ├── nerio │ │ ├── fonts │ │ │ ├── Glyphter.eot │ │ │ ├── Glyphter.ttf │ │ │ ├── Glyphter.woff │ │ │ └── Glyphter.svg │ │ ├── skin.js │ │ ├── skin.html │ │ └── skin.css │ ├── calamansi │ │ ├── fonts │ │ │ ├── Glyphter.eot │ │ │ ├── Glyphter.ttf │ │ │ ├── Glyphter.woff │ │ │ └── Glyphter.svg │ │ ├── skin.js │ │ ├── skin.html │ │ └── skin.css │ ├── calamansi-compact │ │ ├── fonts │ │ │ ├── Glyphter.eot │ │ │ ├── Glyphter.ttf │ │ │ ├── Glyphter.woff │ │ │ └── Glyphter.svg │ │ ├── skin.js │ │ ├── skin.html │ │ └── skin.css │ ├── basic │ │ ├── skin.js │ │ ├── skin.html │ │ └── skin.css │ └── ayon │ │ ├── skin.js │ │ ├── skin.html │ │ └── skin.css └── calamansi.min.css ├── .gitignore ├── mix-manifest.json ├── webpack.mix.js ├── src ├── calamansi.js ├── calamansi.scss └── calamansi │ ├── CalamansiEventHub.js │ ├── services │ ├── TrackInfoReader.js │ └── Id3Reader.js │ ├── CalamansiAudio.js │ └── Calamansi.js └── package.json /dist/skins/doorn/skin.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/skins/dummy/skin.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/skins/in-text/skin.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/skins/pronunciation/skin.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /demo 3 | todo -------------------------------------------------------------------------------- /dist/skins/default-album-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/default-album-cover.png -------------------------------------------------------------------------------- /dist/skins/doorn/fonts/Glyphter.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/doorn/fonts/Glyphter.eot -------------------------------------------------------------------------------- /dist/skins/doorn/fonts/Glyphter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/doorn/fonts/Glyphter.ttf -------------------------------------------------------------------------------- /dist/skins/doorn/fonts/Glyphter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/doorn/fonts/Glyphter.woff -------------------------------------------------------------------------------- /dist/skins/nerio/fonts/Glyphter.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/nerio/fonts/Glyphter.eot -------------------------------------------------------------------------------- /dist/skins/nerio/fonts/Glyphter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/nerio/fonts/Glyphter.ttf -------------------------------------------------------------------------------- /dist/skins/nerio/fonts/Glyphter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/nerio/fonts/Glyphter.woff -------------------------------------------------------------------------------- /dist/skins/calamansi/fonts/Glyphter.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/calamansi/fonts/Glyphter.eot -------------------------------------------------------------------------------- /dist/skins/calamansi/fonts/Glyphter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/calamansi/fonts/Glyphter.ttf -------------------------------------------------------------------------------- /dist/skins/in-text/fonts/Glyphter.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/in-text/fonts/Glyphter.eot -------------------------------------------------------------------------------- /dist/skins/in-text/fonts/Glyphter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/in-text/fonts/Glyphter.ttf -------------------------------------------------------------------------------- /dist/skins/in-text/fonts/Glyphter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/in-text/fonts/Glyphter.woff -------------------------------------------------------------------------------- /dist/skins/calamansi/fonts/Glyphter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/calamansi/fonts/Glyphter.woff -------------------------------------------------------------------------------- /dist/skins/pronunciation/fonts/Glyphter.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/pronunciation/fonts/Glyphter.eot -------------------------------------------------------------------------------- /dist/skins/pronunciation/fonts/Glyphter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/pronunciation/fonts/Glyphter.ttf -------------------------------------------------------------------------------- /dist/skins/pronunciation/fonts/Glyphter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/pronunciation/fonts/Glyphter.woff -------------------------------------------------------------------------------- /dist/skins/calamansi-compact/fonts/Glyphter.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/calamansi-compact/fonts/Glyphter.eot -------------------------------------------------------------------------------- /dist/skins/calamansi-compact/fonts/Glyphter.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/calamansi-compact/fonts/Glyphter.ttf -------------------------------------------------------------------------------- /dist/skins/calamansi-compact/fonts/Glyphter.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voerro/calamansi-js/HEAD/dist/skins/calamansi-compact/fonts/Glyphter.woff -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dist/calamansi.min.js": "/dist/calamansi.min.js", 3 | "/dist/calamansi.min.css": "/dist/calamansi.min.css" 4 | } 5 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | mix.js('src/calamansi.js', 'dist/calamansi.min.js').sass('src/calamansi.scss', 'dist/calamansi.min.css'); 4 | -------------------------------------------------------------------------------- /src/calamansi.js: -------------------------------------------------------------------------------- 1 | import Calamansi from './calamansi/Calamansi'; 2 | import CalamansiEventHub from './calamansi/CalamansiEventHub'; 3 | 4 | window.Calamansi = Calamansi; 5 | window.CalamansiEvents = new CalamansiEventHub(); 6 | -------------------------------------------------------------------------------- /dist/skins/pronunciation/skin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /dist/calamansi.min.css: -------------------------------------------------------------------------------- 1 | .calamansi{box-sizing:border-box;width:100%;height:auto}.calamansi .clmns--control{cursor:pointer}.calamansi .clmns--slider{touch-action:none}.calamansi .clmns--noselect{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.calamansi .clmns--playlist-item{cursor:pointer} -------------------------------------------------------------------------------- /dist/skins/basic/skin.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | CalamansiEvents.on('initialized', function (instance) { 3 | var player = instance.el; 4 | 5 | if (player.matches('.calamansi-skin--basic')) { 6 | var volumeBtn = player.querySelector('.clmns--volume-btn'); 7 | 8 | if (!volumeBtn) { 9 | return; 10 | } 11 | 12 | volumeBtn.addEventListener('click', function (e) { 13 | volumeBtn.classList.toggle('clmns--open'); 14 | }); 15 | } 16 | }); 17 | })(); -------------------------------------------------------------------------------- /src/calamansi.scss: -------------------------------------------------------------------------------- 1 | .calamansi { 2 | box-sizing: border-box; 3 | width: 100%; 4 | height: auto; 5 | } 6 | 7 | .calamansi .clmns--control { 8 | cursor: pointer; 9 | } 10 | 11 | .calamansi .clmns--slider { 12 | touch-action: none; 13 | } 14 | 15 | .calamansi .clmns--noselect { 16 | -webkit-touch-callout: none; 17 | -webkit-user-select: none; 18 | -khtml-user-select: none; 19 | -moz-user-select: none; 20 | -ms-user-select: none; 21 | user-select: none; 22 | } 23 | 24 | .calamansi .clmns--playlist-item { 25 | cursor: pointer; 26 | } -------------------------------------------------------------------------------- /dist/skins/in-text/skin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/skins/nerio/skin.js: -------------------------------------------------------------------------------- 1 | ; (function () { 2 | CalamansiEvents.on('initialized', function (instance) { 3 | var player = instance.el; 4 | 5 | if (player.matches('.calamansi-skin--nerio')) { 6 | if (document.querySelectorAll(`script[src="https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.js"]`).length == 0) { 7 | var script = document.createElement('script'); 8 | script.setAttribute('src', 'https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.js'); 9 | script.setAttribute('type', 'text/javascript'); 10 | document.querySelector('head').appendChild(script); 11 | } 12 | 13 | player.querySelectorAll('.clmns--control-toggle-playlist').forEach(function (el) { 14 | el.addEventListener('change', function (e) { 15 | player.classList.toggle('clmns--show-playlist'); 16 | }); 17 | }); 18 | } 19 | }); 20 | })(); -------------------------------------------------------------------------------- /dist/skins/ayon/skin.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | CalamansiEvents.on('play', function (instance) { 3 | var player = instance.el; 4 | 5 | if (player.matches('.calamansi-skin--ayon')) { 6 | var info = player.querySelector('.clmns--info'); 7 | var controlPanel = player.querySelector('.clmns--control-panel'); 8 | 9 | if (info) { 10 | info.classList.add('clmns--active'); 11 | } 12 | 13 | if (controlPanel) { 14 | controlPanel.classList.add('clmns--active'); 15 | } 16 | } 17 | }); 18 | 19 | CalamansiEvents.on(['pause', 'stop'], function (instance) { 20 | var player = instance.el; 21 | 22 | if (player.matches('.calamansi-skin--ayon')) { 23 | var info = player.querySelector('.clmns--info'); 24 | var controlPanel = player.querySelector('.clmns--control-panel'); 25 | 26 | if (info) { 27 | info.classList.remove('clmns--active'); 28 | } 29 | 30 | if (controlPanel) { 31 | controlPanel.classList.remove('clmns--active'); 32 | } 33 | } 34 | }); 35 | })(); -------------------------------------------------------------------------------- /dist/skins/in-text/skin.css: -------------------------------------------------------------------------------- 1 | .calamansi-skin--in-text a { 2 | color: inherit; 3 | } 4 | 5 | /* Generated by Glyphter (http://www.glyphter.com) on Tue Sep 10 2019*/ 6 | @font-face { 7 | font-family: 'calamansi-skin--in-text--glyphter'; 8 | src: url('fonts/Glyphter.eot'); 9 | src: url('fonts/Glyphter.eot?#iefix') format('embedded-opentype'), 10 | url('fonts/Glyphter.woff') format('woff'), 11 | url('fonts/Glyphter.ttf') format('truetype'), 12 | url('fonts/Glyphter.svg#Glyphter') format('svg'); 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | .calamansi-skin--in-text [class*='clmns--icon-']:before{ 17 | display: inline-block; 18 | font-family: 'calamansi-skin--in-text--glyphter'; 19 | font-style: normal; 20 | font-weight: normal; 21 | line-height: 1; 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | margin-right: 0.25rem; 25 | font-size: .9em; 26 | } 27 | .calamansi-skin--in-text .clmns--icon-play:before { 28 | content: '\0041'; 29 | } 30 | .calamansi-skin--in-text .clmns--icon-pause:before { 31 | content: '\0042'; 32 | } 33 | .calamansi-skin--in-text .clmns--icon-stop:before { 34 | content: '\0043'; 35 | } -------------------------------------------------------------------------------- /dist/skins/pronunciation/skin.css: -------------------------------------------------------------------------------- 1 | .calamansi-skin--pronunciation a { 2 | color: inherit; 3 | outline: none; 4 | text-decoration: none; 5 | } 6 | 7 | .calamansi-skin--pronunciation .clmns--slot--content { 8 | text-decoration: underline; 9 | } 10 | 11 | /* Generated by Glyphter (http://www.glyphter.com) on Tue Sep 10 2019*/ 12 | @font-face { 13 | font-family: 'calamansi-skin--pronunciation--glyphter'; 14 | src: url('fonts/Glyphter.eot'); 15 | src: url('fonts/Glyphter.eot?#iefix') format('embedded-opentype'), 16 | url('fonts/Glyphter.woff') format('woff'), 17 | url('fonts/Glyphter.ttf') format('truetype'), 18 | url('fonts/Glyphter.svg#Glyphter') format('svg'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | .calamansi-skin--pronunciation [class*='clmns--icon-']:before{ 23 | display: inline-block; 24 | font-family: 'calamansi-skin--pronunciation--glyphter'; 25 | font-style: normal; 26 | font-weight: normal; 27 | line-height: 1; 28 | -webkit-font-smoothing: antialiased; 29 | -moz-osx-font-smoothing: grayscale; 30 | margin-right: 0.25rem; 31 | font-size: .9em; 32 | } 33 | .calamansi-skin--pronunciation .clmns--icon-play:before { 34 | content: '\0041'; 35 | } -------------------------------------------------------------------------------- /dist/skins/calamansi-compact/skin.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | // ----------------------------------------------- 3 | CalamansiEvents.on('initialized', function (instance) { 4 | var player = instance.el; 5 | 6 | if (player.matches('.calamansi-skin--calamansi-compact')) { 7 | if (document.querySelectorAll(`script[src="https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.js"]`).length == 0) { 8 | var script = document.createElement('script'); 9 | script.setAttribute('src', 'https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.js'); 10 | script.setAttribute('type', 'text/javascript'); 11 | document.querySelector('head').appendChild(script); 12 | } 13 | 14 | player.querySelectorAll('.calamansi-skin--calamansi-compact .clmns--toggle-playlist').forEach(function (el) { 15 | el.addEventListener('click', function (e) { 16 | player.classList.toggle('clmns--show-playlist'); 17 | }); 18 | }); 19 | 20 | var controls = player.querySelector('.clmns--controls'); 21 | 22 | if (controls.offsetWidth < 300) { 23 | controls.classList.add('clmns--compact'); 24 | } 25 | } 26 | }); 27 | })(); -------------------------------------------------------------------------------- /dist/skins/calamansi/skin.js: -------------------------------------------------------------------------------- 1 | ;(function () { 2 | CalamansiEvents.on('initialized', function (instance) { 3 | var player = instance.el; 4 | 5 | if (player.matches('.calamansi-skin--calamansi')) { 6 | if (document.querySelectorAll(`script[src="https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.js"]`).length == 0) { 7 | var script = document.createElement('script'); 8 | script.setAttribute('src', 'https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.js'); 9 | script.setAttribute('type', 'text/javascript'); 10 | document.querySelector('head').appendChild(script); 11 | } 12 | 13 | var info = player.querySelector('.calamansi-skin--calamansi .clmns--info'); 14 | 15 | player.querySelectorAll('.calamansi-skin--calamansi .clmns--toggle-playlist').forEach(function (el) { 16 | el.addEventListener('change', function (e) { 17 | info.classList.toggle('clmns--show-playlist'); 18 | }); 19 | }); 20 | 21 | var controls = player.querySelector('.clmns--controls'); 22 | 23 | if (controls.offsetWidth < 300) { 24 | controls.classList.add('clmns--compact'); 25 | } 26 | } 27 | }); 28 | })(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@voerro/calamansi-js", 3 | "version": "1.0.0", 4 | "description": "Flexible feature-rich HTML5 & Vanilla JS audio player.", 5 | "main": "src/app.js", 6 | "scripts": { 7 | "dev": "npm run development", 8 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "watch": "npm run development -- --watch", 10 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 11 | "prod": "npm run production", 12 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" 13 | }, 14 | "keywords": [ 15 | "audio", 16 | "player", 17 | "mp3", 18 | "wav", 19 | "id3" 20 | ], 21 | "author": "Alexander Zavyalov", 22 | "devDependencies": { 23 | "cross-env": "^5.2.1", 24 | "laravel-mix": "^4.1.4", 25 | "resolve-url-loader": "^3.1.0", 26 | "sass": "^1.22.0", 27 | "sass-loader": "^7.3.1", 28 | "vue-template-compiler": "^2.6.10" 29 | }, 30 | "license": "MIT", 31 | "repository": "https://github.com/voerro/calamansi-js" 32 | } 33 | -------------------------------------------------------------------------------- /dist/skins/ayon/skin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 |
00:00
8 |
9 |
00:00
10 |
11 | 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
-------------------------------------------------------------------------------- /src/calamansi/CalamansiEventHub.js: -------------------------------------------------------------------------------- 1 | class CalamansiEventHub 2 | { 3 | constructor() { 4 | this.eventListeners = { 5 | initialized: [], 6 | play: [], 7 | pause: [], 8 | stop: [], 9 | trackEnded: [], 10 | loadeddata: [], 11 | loadedmetadata: [], 12 | canplaythrough: [], 13 | loadingProgress: [], 14 | timeupdate: [], 15 | volumechange: [], 16 | ratechange: [], 17 | playlistLoaded: [], 18 | playlistReordered: [], 19 | trackInfoReady: [], 20 | trackSwitched: [], 21 | }; 22 | } 23 | 24 | /** 25 | * Emit an event. Call all the event listeners' callbacks. 26 | * 27 | * @param {*} event 28 | * @param {*} instance 29 | * @param {*} data 30 | */ 31 | _emit(event, instance, data = {}) { 32 | // Ignore inexisting event types 33 | if (!this.eventListeners[event]) { 34 | return; 35 | } 36 | 37 | for (let callback of this.eventListeners[event]) { 38 | callback(instance, data); 39 | } 40 | } 41 | 42 | /** 43 | * Register an event listener 44 | * 45 | * @param {*} event 46 | * @param {*} callback 47 | */ 48 | on(events, callback) { 49 | if (typeof events === 'string') { 50 | events = [events]; 51 | } 52 | 53 | for (let event of events) { 54 | // Ignore inexisting event types 55 | if (!this.eventListeners[event]) { 56 | continue; 57 | } 58 | 59 | this.eventListeners[event].push(callback); 60 | } 61 | } 62 | } 63 | 64 | export default CalamansiEventHub; -------------------------------------------------------------------------------- /dist/skins/doorn/skin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |

6 |

7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 | 15 | 16 |
17 | 18 |
19 | 20 |
21 | 22 |
23 | 24 |
25 |
26 | 27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 |
37 |
38 |
39 |
-------------------------------------------------------------------------------- /dist/skins/basic/skin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 |
7 | 8 |
9 | 10 | 11 | 12 |
13 | 14 |
15 | 0:00 16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 0:00 24 |
25 | 26 |
27 |
28 | 29 | 32 | 33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /dist/skins/in-text/fonts/Glyphter.svg: -------------------------------------------------------------------------------- 1 | Generated by Glyphter -------------------------------------------------------------------------------- /dist/skins/dummy/skin.css: -------------------------------------------------------------------------------- 1 | .calamansi-skin--dummy { 2 | border: 2px solid black; 3 | } 4 | 5 | .calamansi-skin--dummy .clmns--playback-bar { 6 | position: relative; 7 | height: 1rem; 8 | background-color: #eee; 9 | border: 1px solid #000; 10 | margin: 1rem 1rem; 11 | } 12 | .calamansi-skin--dummy .clmns--playback-load { 13 | height: 100%; 14 | width: 100%; 15 | background-color: #ddd; 16 | } 17 | .calamansi-skin--dummy .clmns--playback-progress { 18 | position: absolute; 19 | left: 0; 20 | top: 0; 21 | height: 100%; 22 | width: 0%; 23 | background-color: #13abea; 24 | } 25 | 26 | .calamansi-skin--dummy .clmns--volume-container { 27 | margin: 1rem 1rem; 28 | } 29 | .calamansi-skin--dummy .clmns--volume-bar { 30 | display: inline-block; 31 | position: relative; 32 | width: 5rem; 33 | height: 1rem; 34 | background-color: #eee; 35 | border: 1px solid #000; 36 | } 37 | .calamansi-skin--dummy .clmns--volume-value { 38 | height: 100%; 39 | width: 100%; 40 | background-color: #13abea; 41 | } 42 | 43 | /* Track info */ 44 | .calamansi-skin--dummy .clmns--album-cover, 45 | .calamansi-skin--dummy .clmns--track-name, 46 | .calamansi-skin--dummy .clmns--track-links, 47 | .calamansi-skin--dummy .clmns--controls, 48 | .calamansi-skin--dummy .clmns--track-playback-info 49 | { 50 | text-align: center; 51 | } 52 | 53 | .calamansi-skin--dummy .clmns--track-info--albumCover { 54 | max-width: 300px; 55 | } 56 | .calamansi-skin--dummy .clmns--track-name { 57 | font-weight: 700; 58 | } 59 | 60 | .calamansi-skin--dummy .clmns--controls { 61 | margin: 1rem 0; 62 | } 63 | .calamansi-skin--dummy .clmns--control { 64 | font-size: 2rem; 65 | margin: 0 .25rem; 66 | } 67 | 68 | .calamansi-skin--dummy .clmns--states { 69 | text-align: center; 70 | margin-bottom: 1rem; 71 | font-size: 1.5rem; 72 | } 73 | .calamansi-skin--dummy .clmns--playback-speed-container { 74 | margin: 1rem 1rem; 75 | font-size: 1rem; 76 | } 77 | 78 | /* Playlists */ 79 | .calamansi-skin--dummy .clmns--playlists-select-container { 80 | margin: 1rem 1rem; 81 | font-size: 1rem; 82 | } 83 | .calamansi-skin--dummy .clmns--playlists { 84 | width: 100%; 85 | } 86 | 87 | /* Playlist */ 88 | .calamansi-skin--dummy .clmns--playlist-container { 89 | width: 100%; 90 | overflow: auto; 91 | } 92 | .calamansi-skin--dummy .clmns--playlist { 93 | width: 100%; 94 | } 95 | 96 | .calamansi-skin--dummy .clmns--playlist th { 97 | text-align: left; 98 | } 99 | 100 | .calamansi-skin--dummy .clmns--playlist .clmns--playlist-item.clmns--template { 101 | display: none; 102 | } 103 | 104 | .calamansi-skin--dummy div.clmns--playlist .clmns--playlist-item { 105 | display: flex; 106 | } 107 | 108 | .calamansi-skin--dummy .clmns--playlist-item { 109 | -webkit-touch-callout: none; 110 | -webkit-user-select: none; 111 | -khtml-user-select: none; 112 | -moz-user-select: none; 113 | -ms-user-select: none; 114 | user-select: none; 115 | } 116 | 117 | .calamansi-skin--dummy div.clmns--playlist .clmns--playlist-item--name { 118 | flex: 1; 119 | } 120 | 121 | .calamansi-skin--dummy .clmns--playlist-item.clmns--active { 122 | background-color: rgb(185, 217, 231); 123 | } -------------------------------------------------------------------------------- /dist/skins/basic/skin.css: -------------------------------------------------------------------------------- 1 | .calamansi-skin--basic { 2 | width: 100%; 3 | box-sizing: border-box; 4 | height: 56px; 5 | box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.07); 6 | display: flex; 7 | justify-content: space-between; 8 | align-items: center; 9 | padding-left: 24px; 10 | padding-right: 24px; 11 | border-radius: 4px; 12 | user-select: none; 13 | -webkit-user-select: none; 14 | background-color: #fff; 15 | } 16 | .calamansi-skin--basic .clmns--slider { 17 | flex-grow: 1; 18 | background-color: #D8D8D8; 19 | cursor: pointer; 20 | position: relative; 21 | } 22 | .calamansi-skin--basic .clmns--slider .clmns--playback-progress { 23 | background-color: #44BFA3; 24 | border-radius: inherit; 25 | position: absolute; 26 | pointer-events: none; 27 | } 28 | .calamansi-skin--basic .clmns--slider .clmns--pin { 29 | height: 16px; 30 | width: 16px; 31 | border-radius: 8px; 32 | background-color: #44BFA3; 33 | position: absolute; 34 | pointer-events: all; 35 | box-shadow: 0px 1px 1px 0px rgba(0, 0, 0, 0.32); 36 | } 37 | .calamansi-skin--basic .clmns--controls { 38 | font-family: 'Roboto', sans-serif; 39 | font-size: 16px; 40 | line-height: 18px; 41 | color: #55606E; 42 | display: flex; 43 | flex-grow: 1; 44 | justify-content: space-between; 45 | align-items: center; 46 | margin-left: 24px; 47 | margin-right: 24px; 48 | } 49 | .calamansi-skin--basic .clmns--controls .clmns--slider { 50 | margin-left: 16px; 51 | margin-right: 16px; 52 | border-radius: 2px; 53 | height: 4px; 54 | } 55 | .calamansi-skin--basic .clmns--controls .clmns--slider .clmns--playback-progress { 56 | width: 0; 57 | height: 100%; 58 | } 59 | .calamansi-skin--basic .clmns--controls .clmns--slider .clmns--playback-progress .clmns--pin { 60 | right: -8px; 61 | top: -6px; 62 | } 63 | .calamansi-skin--basic .clmns--controls span { 64 | cursor: default; 65 | } 66 | .calamansi-skin--basic .clmns--volume { 67 | position: relative; 68 | } 69 | .calamansi-skin--basic .clmns--volume .clmns--volume-btn { 70 | cursor: pointer; 71 | } 72 | .calamansi-skin--basic .clmns--volume .clmns--volume-btn.clmns--open path { 73 | fill: #44BFA3; 74 | } 75 | .calamansi-skin--basic .clmns--volume .clmns--volume-btn.clmns--open ~ .clmns--volume-controls { 76 | display: flex; 77 | } 78 | .calamansi-skin--basic .clmns--volume .clmns--volume-controls { 79 | width: 30px; 80 | height: 135px; 81 | background-color: rgba(0, 0, 0, 0.62); 82 | border-radius: 7px; 83 | position: absolute; 84 | left: -3px; 85 | bottom: 52px; 86 | flex-direction: column; 87 | align-items: center; 88 | display: none; 89 | z-index: 1; 90 | } 91 | .calamansi-skin--basic .clmns--volume .clmns--volume-controls .clmns--slider { 92 | margin-top: 12px; 93 | margin-bottom: 12px; 94 | width: 6px; 95 | border-radius: 3px; 96 | } 97 | .calamansi-skin--basic .clmns--volume .clmns--volume-controls .clmns--slider .clmns--volume-value { 98 | bottom: 0; 99 | height: 100%; 100 | width: 6px; 101 | border-radius: 3px; 102 | background-color: #44BFA3; 103 | position: absolute; 104 | } 105 | .calamansi-skin--basic .clmns--volume .clmns--volume-controls .clmns--slider .clmns--volume-value .clmns--pin { 106 | left: -5px; 107 | top: -8px; 108 | } 109 | 110 | .calamansi-skin--basic .clmns--svg { 111 | display: block; 112 | } 113 | 114 | -------------------------------------------------------------------------------- /dist/skins/pronunciation/fonts/Glyphter.svg: -------------------------------------------------------------------------------- 1 | Generated by Glyphter -------------------------------------------------------------------------------- /src/calamansi/services/TrackInfoReader.js: -------------------------------------------------------------------------------- 1 | let jsmediatags = require('../../vendor/jsmediatags.min.js'); 2 | 3 | class TrackInfoReader { 4 | constructor(soundcloudClientId) { 5 | this.soundcloudClientId = soundcloudClientId; 6 | } 7 | 8 | read(track) { 9 | switch (track.sourceType) { 10 | case 'mp3': 11 | return this.readId3(track); 12 | case 'soundcloud': 13 | return this.readSoundcloud(track); 14 | } 15 | } 16 | 17 | readId3(track) { 18 | return new Promise((resolve, reject) => { 19 | let url; 20 | 21 | if (track.source.startsWith('http')) { 22 | url = track.source; 23 | } else if (track.source.startsWith('/')) { 24 | url = window.location.origin + track.source; 25 | } else { 26 | url = window.location.origin + window.location.pathname + track.source; 27 | } 28 | 29 | jsmediatags.read(url, { 30 | onSuccess: (tags) => { 31 | let trackInfo = tags.tags; 32 | 33 | if (trackInfo.artist && trackInfo.title) { 34 | trackInfo.name = `${trackInfo.artist} - ${trackInfo.title}`; 35 | } 36 | 37 | if (trackInfo.title) { 38 | trackInfo.titleOrFilename = trackInfo.title; 39 | } 40 | 41 | if (trackInfo.artist) { 42 | trackInfo.artistOrFilename = trackInfo.artist; 43 | } 44 | 45 | if (trackInfo.track) { 46 | trackInfo.trackNumber = parseInt(trackInfo.track.split('/')[0]); 47 | } 48 | 49 | if (trackInfo.picture) { 50 | let base64 = btoa(String.fromCharCode.apply(null, trackInfo.picture.data)); 51 | 52 | trackInfo.picture = Object.assign(trackInfo.picture, { 53 | base64: 'data:' + trackInfo.picture.format + ';base64,' + base64 54 | }); 55 | 56 | trackInfo.albumCover = trackInfo.picture; 57 | } 58 | 59 | if (tags.tags.TYER || tags.tags.TDRC) { 60 | trackInfo.year = tags.tags.TYER ? parseInt(tags.tags.TYER.data) : ( 61 | tags.tags.TDRC ? parseInt(tags.tags.TDRC.data) : null 62 | ) 63 | } 64 | 65 | trackInfo._loaded = true; 66 | 67 | resolve(trackInfo); 68 | }, 69 | onError: (error) => { 70 | reject(error); 71 | } 72 | }); 73 | }); 74 | } 75 | 76 | readSoundcloud(track) { 77 | return new Promise((resolve, reject) => { 78 | // const response = fetch('https://api.soundcloud.com/resolve.json?url=https%3A%2F%2Fsoundcloud.com%2Fmsmrsounds%2Fms-mr-hurricane-chvrches-remix&client_id=' + this.soundcloudClientId) 79 | 80 | // reject(response); 81 | // jsmediatags.read(window.location.origin + window.location.pathname + track.source, { 82 | // onSuccess: (tags) => { 83 | // let trackInfo = tags.tags; 84 | 85 | // if (trackInfo.artist && trackInfo.title) { 86 | // trackInfo.name = `${trackInfo.artist} - ${trackInfo.title}`; 87 | // } 88 | 89 | // if (trackInfo.title) { 90 | // trackInfo.titleOrFilename = trackInfo.title; 91 | // } 92 | 93 | // if (trackInfo.artist) { 94 | // trackInfo.artistOrFilename = trackInfo.artist; 95 | // } 96 | 97 | // if (trackInfo.track) { 98 | // trackInfo.trackNumber = parseInt(trackInfo.track.split('/')[0]); 99 | // } 100 | 101 | // if (trackInfo.picture) { 102 | // let base64 = btoa(String.fromCharCode.apply(null, trackInfo.picture.data)); 103 | 104 | // trackInfo.picture = Object.assign(trackInfo.picture, { 105 | // base64: 'data:' + trackInfo.picture.format + ';base64,' + base64 106 | // }); 107 | 108 | // trackInfo.albumCover = trackInfo.picture; 109 | // } 110 | 111 | // if (tags.tags.TYER || tags.tags.TDRC) { 112 | // trackInfo.year = tags.tags.TYER ? parseInt(tags.tags.TYER.data) : ( 113 | // tags.tags.TDRC ? parseInt(tags.tags.TDRC.data) : null 114 | // ) 115 | // } 116 | 117 | // trackInfo._loaded = true; 118 | 119 | // resolve(trackInfo); 120 | // }, 121 | // onError: (error) => { 122 | // reject(error); 123 | // } 124 | // }); 125 | }); 126 | } 127 | } 128 | 129 | export default TrackInfoReader; -------------------------------------------------------------------------------- /dist/skins/doorn/skin.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Montserrat"); 2 | 3 | .calamansi-skin--doorn { 4 | background-size: cover; 5 | background-position: center; 6 | 7 | color: white; 8 | height: auto; 9 | min-width: 300px; 10 | width: 100%; 11 | position: relative; 12 | border-radius: 5px; 13 | overflow: hidden; 14 | -webkit-box-shadow: 0px 3px 15px -1px rgba(0, 0, 0, 0.5); 15 | -moz-box-shadow: 0px 3px 15px -1px rgba(0, 0, 0, 0.5); 16 | box-shadow: 0px 3px 15px -1px rgba(0, 0, 0, 0.5); 17 | 18 | font-family: "Montserrat", sans-serif; 19 | font-size: .9rem; 20 | } 21 | .calamansi-skin--doorn .clmns--overlay { 22 | position: absolute; 23 | height: calc(100% + 1px); 24 | width: calc(100% + 1px); 25 | background: -moz-linear-gradient(-45deg, #333333 0%, #222222 100%); 26 | /* FF3.6-15 */ 27 | background: -webkit-linear-gradient(-45deg, #333333 0%, #222222 100%); 28 | /* Chrome10-25,Safari5.1-6 */ 29 | background: linear-gradient(135deg, #333333 0%, #222222 100%); 30 | /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 31 | opacity: 0.85; 32 | } 33 | .calamansi-skin--doorn .clmns--content { 34 | text-align: center; 35 | position: relative; 36 | } 37 | 38 | .calamansi-skin--doorn .clmns--content .clmns--track-info--titleOrFilename, 39 | .calamansi-skin--doorn .clmns--content .clmns--track-info--artist 40 | { 41 | white-space: nowrap; 42 | overflow: hidden; 43 | text-overflow: ellipsis; 44 | padding:0 1em; 45 | } 46 | .calamansi-skin--doorn .clmns--content .clmns--track-info--titleOrFilename { 47 | font-size: 1.3em; 48 | margin: 35px 0 0 0; 49 | font-weight: 600; 50 | } 51 | .calamansi-skin--doorn .clmns--content .clmns--track-info--artist { 52 | margin: 7px 0 15px 0; 53 | font-size: 0.9em; 54 | opacity: 0.6; 55 | } 56 | .calamansi-skin--doorn .clmns--content .clmns--playback-bar { 57 | width: 100%; 58 | height: 3px; 59 | margin: 0 0 15px 0; 60 | background: #ffffff33; 61 | padding: .25em 0; 62 | position: relative; 63 | } 64 | .calamansi-skin--doorn .clmns--content .clmns--playback-progress { 65 | position: absolute; 66 | top: 0; 67 | height: 100%; 68 | background: linear-gradient(to right, #3498db 0%, #9b59b6 72%); 69 | } 70 | 71 | .calamansi-skin--doorn .clmns--content .clmns--controls { 72 | height: 50px; 73 | display: flex; 74 | display: -webkit-flex; 75 | -webkit-align-items: center; 76 | align-items: center; 77 | -webkit-justify-content: center; 78 | justify-content: center; 79 | } 80 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--column { 81 | float: left; 82 | } 83 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--column.clmns--control-prev-track, 84 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--column.clmns--control-next-track { 85 | font-size: 1.3em; 86 | } 87 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--column:nth-child(n + 2):nth-child(-n + 4) { 88 | color: white !important; 89 | } 90 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--column.clmns--control-resume, 91 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--column.clmns--control-pause { 92 | font-size: 2em; 93 | } 94 | 95 | .calamansi-skin--doorn .clmns--custom-checkbox { 96 | position: relative; 97 | } 98 | .calamansi-skin--doorn .clmns--custom-checkbox input { 99 | position: absolute; 100 | 101 | opacity: 0; 102 | top: 0; 103 | left: 0; 104 | margin: 0; 105 | } 106 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--custom-checkbox input:checked ~ i { 107 | color: #9b59b6; 108 | transition: all .4s ease; 109 | } 110 | 111 | .calamansi-skin--doorn .clmns--content .clmns--controls .clmns--column { 112 | margin: 0 15px; 113 | cursor: pointer; 114 | } 115 | 116 | /* Icon font */ 117 | /* Generated by Glyphter (http://www.clmns--glyphter.com) on Sun Sep 22 2019*/ 118 | @font-face { 119 | font-family: 'calamansi-skin--doorn--glyphter'; 120 | src: url('fonts/Glyphter.eot'); 121 | src: url('fonts/Glyphter.eot?#iefix') format('embedded-opentype'), 122 | url('fonts/Glyphter.woff') format('woff'), 123 | url('fonts/Glyphter.ttf') format('truetype'), 124 | url('fonts/Glyphter.svg#Glyphter') format('svg'); 125 | font-weight: normal; 126 | font-style: normal; 127 | } 128 | .calamansi-skin--doorn .clmns--glyphter:before { 129 | display: inline-block; 130 | font-family: 'calamansi-skin--doorn--glyphter'; 131 | font-style: normal; 132 | font-weight: normal; 133 | line-height: 1; 134 | -webkit-font-smoothing: antialiased; 135 | -moz-osx-font-smoothing: grayscale 136 | } 137 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-play:before { 138 | content: '\0041'; 139 | } 140 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-pause:before { 141 | content: '\0042'; 142 | } 143 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-backward:before { 144 | content: '\0044'; 145 | } 146 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-forward:before { 147 | content: '\0045'; 148 | } 149 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-bars:before { 150 | content: '\0046'; 151 | } 152 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-volume-up:before { 153 | content: '\0047'; 154 | } 155 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-random:before { 156 | content: '\0048'; 157 | } 158 | .calamansi-skin--doorn .clmns--glyphter.clmns--fa-loop:before { 159 | content: '\0049'; 160 | } 161 | 162 | @media all and (min-width: 1024px) { 163 | .calamansi-skin--doorn .clmns--content .clmns--playback-bar { 164 | padding: 0; 165 | } 166 | } -------------------------------------------------------------------------------- /src/calamansi/CalamansiAudio.js: -------------------------------------------------------------------------------- 1 | class CalamansiAudio 2 | { 3 | constructor(calamansi, source) { 4 | this._calamansi = calamansi; 5 | 6 | this.audio = new Audio(); 7 | this.load(source); 8 | 9 | // Metadata 10 | this.duration = 0; 11 | 12 | // State 13 | this.loadedPercent = 0.0; 14 | this.currentTime = 0; 15 | this.volume = this.audio.volume; 16 | this.playbackRate = 1.0; 17 | 18 | this._addEventListeners(); 19 | } 20 | 21 | _addEventListeners() { 22 | this.audio.addEventListener('loadedmetadata', (event) => { 23 | this.duration = this.audio.duration; 24 | this._calamansi.currentTrack().info.duration = this.audio.duration; 25 | 26 | this._calamansi._emit('loadedmetadata', this._calamansi); 27 | CalamansiEvents._emit('loadedmetadata', this._calamansi); 28 | }); 29 | 30 | // Fired when the first frame of the media has finished loading. 31 | this.audio.addEventListener('loadeddata', (event) => { 32 | this._setCurrentTime(this.audio.currentTime); 33 | 34 | this._calamansi._emit('loadeddata', this._calamansi); 35 | CalamansiEvents._emit('loadeddata', this._calamansi); 36 | }); 37 | 38 | // Data loading progress 39 | this.audio.addEventListener('progress', (event, progress) => { 40 | // NOTE: There seems to be no way to actually determine how much has 41 | // been loaded 42 | }); 43 | 44 | // Data has been fully loaded till the end 45 | this.audio.addEventListener('canplaythrough', (event) => { 46 | this.loadedPercent = 100; 47 | 48 | this._calamansi._emit('canplaythrough', this._calamansi); 49 | CalamansiEvents._emit('canplaythrough', this._calamansi); 50 | 51 | this._calamansi._emit('loadingProgress', this._calamansi); 52 | CalamansiEvents._emit('loadingProgress', this._calamansi); 53 | }); 54 | 55 | this.audio.addEventListener('timeupdate', (event) => { 56 | this._setCurrentTime(this.audio.currentTime); 57 | }); 58 | 59 | this.audio.addEventListener('ended', (event) => { 60 | this._setCurrentTime(0); 61 | 62 | this._calamansi._emit('trackEnded', this._calamansi); 63 | CalamansiEvents._emit('trackEnded', this._calamansi); 64 | }); 65 | } 66 | 67 | /** 68 | * Load an audio track from a source 69 | * 70 | * @param string source 71 | */ 72 | load(source) { 73 | this.stop(); 74 | 75 | if (source.startsWith('https://api.soundcloud.com')) { 76 | if (source.endsWith('/')) { 77 | source = source.substring(0, source.length - 1); 78 | } 79 | 80 | if (!this._calamansi._options.soundcloudClientId) { 81 | console.error('Please set your SoundCloud client id in the soundcloudClientId option to play SoundCloud tracks.'); 82 | } 83 | 84 | source += '/stream?client_id=' + this._calamansi._options.soundcloudClientId; 85 | } 86 | 87 | this.audio.src = source; 88 | this.audio.load(); 89 | } 90 | 91 | /** 92 | * Start playing the current track from the start 93 | */ 94 | playFromStart() { 95 | this.audio.pause(); 96 | this.audio.currentTime = 0; 97 | this.currentTime = 0; 98 | this.audio.play(); 99 | 100 | this._calamansi._emit('play', this._calamansi); 101 | CalamansiEvents._emit('play', this._calamansi); 102 | } 103 | 104 | /** 105 | * Start/resume playback of the current track 106 | */ 107 | play() { 108 | this.audio.play(); 109 | 110 | this._calamansi._emit('play', this._calamansi); 111 | CalamansiEvents._emit('play', this._calamansi); 112 | } 113 | 114 | /** 115 | * Pause playback of the current track 116 | */ 117 | pause() { 118 | this.audio.pause(); 119 | 120 | this._calamansi._emit('pause', this._calamansi); 121 | CalamansiEvents._emit('pause', this._calamansi); 122 | } 123 | 124 | /** 125 | * Stop playback of the current track 126 | */ 127 | stop() { 128 | this.audio.pause(); 129 | this.audio.currentTime = 0; 130 | this.currentTime = 0; 131 | 132 | this._calamansi._emit('stop', this._calamansi); 133 | CalamansiEvents._emit('stop', this._calamansi); 134 | } 135 | 136 | /** 137 | * Unload the currently loaded audio 138 | */ 139 | unload() { 140 | this.audio.pause(); 141 | this.audio.removeAttribute('src'); 142 | this.audio.load(); 143 | } 144 | 145 | _setCurrentTime(time) { 146 | this.currentTime = time; 147 | 148 | this._calamansi._emit('timeupdate', this._calamansi); 149 | CalamansiEvents._emit('timeupdate', this._calamansi); 150 | } 151 | 152 | /** 153 | * Seek to a position 154 | * 155 | * @param int time (seconds) 156 | */ 157 | seekTo(time) { 158 | this.audio.currentTime = time; 159 | 160 | this._setCurrentTime(time); 161 | } 162 | 163 | /** 164 | * Set player's volume 165 | * 166 | * @param float volume [0.0-1.0] 167 | */ 168 | changeVolume(volume) { 169 | volume = volume >= 0 ? volume : 0; 170 | volume = volume <= 1 ? volume : 1; 171 | 172 | this.audio.volume = volume; 173 | this.volume = volume; 174 | 175 | this._calamansi._emit('volumechange', this._calamansi); 176 | CalamansiEvents._emit('volumechange', this._calamansi); 177 | } 178 | 179 | /** 180 | * Set player's playback rate 181 | * 182 | * @param float rate [0.0-1.0] 183 | */ 184 | changePlaybackRate(rate) { 185 | this.playbackRate = rate; 186 | this.audio.playbackRate = rate; 187 | 188 | this._calamansi._emit('ratechange', this._calamansi); 189 | CalamansiEvents._emit('ratechange', this._calamansi); 190 | } 191 | } 192 | 193 | export default CalamansiAudio; -------------------------------------------------------------------------------- /dist/skins/calamansi-compact/skin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
00:00
5 | 6 |
7 |
8 |
9 |
10 | 11 |
-00:00
12 |
13 | 14 |
15 |

16 |

17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 | 41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 | 50 |
51 | 52 | 53 |
54 |
55 | 56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 | 64 |
65 |
66 | 67 |
68 | 69 |
70 | 71 |
72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 |
80 | 81 |
82 |
83 | 84 |
85 | 86 |
87 | 88 | 89 |
90 | 91 |
92 | 93 | 94 |
95 | 96 |
97 | 98 | 99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | 107 |
108 |
109 | 110 |
111 | 112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
-------------------------------------------------------------------------------- /dist/skins/dummy/skin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 | 8 |
9 | 10 | 20 | 21 |
22 | 23 | play 24 | 25 | 26 | 27 | pause 28 | 29 | 30 | | 31 | 32 | 33 | stop 34 | 35 | 36 | 37 | | 38 | [ 39 | << 40 | 41 | | 42 | 43 | >> 44 | ] 45 | 46 |
47 | 48 |
49 | 52 | 53 | 56 |
57 | 58 |
59 | 00:00 / 00:00 (-00:00) 60 |
61 | 62 |
63 |
64 |
65 |
66 | 67 |
68 | Volume:
69 |
70 | 71 |
72 | Playback Speed: 73 | 82 |
83 | 84 |
85 |

Playlists:

86 | 87 |
88 | 89 | 91 | 104 | 105 | 106 |
107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 134 | 135 | 138 | 139 | 140 |
#TitleArtistAlbumYearLengthDownloadCustom FieldCustom Field 2
123 | 124 | 132 | Download 133 | 136 | BUY 137 |
141 |
142 |
-------------------------------------------------------------------------------- /dist/skins/calamansi/skin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 |
10 |
00:00
11 | 12 |
13 |
14 |
15 |
16 | 17 |
-00:00
18 |
19 | 20 |
21 |

22 |

23 |
24 | 25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 |
40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 |
52 | 53 |
54 | 55 |
56 | 57 |
58 | 59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 | 67 |
68 |
69 | 70 | 71 |
72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 |
80 | 81 |
82 | 83 |
84 | 85 |
86 |
87 | 88 |
89 |
90 | 91 | 92 |
93 | 94 |
95 | 96 | 97 |
98 | 99 |
100 | 101 | 102 |
103 | 104 |
105 | 106 | 107 |
108 |
109 |
110 |
111 |
112 |
113 | 114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
-------------------------------------------------------------------------------- /src/calamansi/services/Id3Reader.js: -------------------------------------------------------------------------------- 1 | class Id3Reader 2 | { 3 | constructor(url) { 4 | this.url = url; 5 | 6 | this.buffer = null; 7 | this.byteArray = []; 8 | this.id3Size = 0; 9 | this.tags = {}; 10 | 11 | this.frames = { 12 | 'TYER': 'year', 13 | 'TPE1': 'artist', 14 | 'TALB': 'album', 15 | 'TIT2': 'title', 16 | 'TLAN': 'language', 17 | 'TRCK': 'trackNumber', 18 | 'TCON': 'genre', 19 | 'APIC': 'albumCover', 20 | 'TCOP': 'copyright', 21 | 'WCOP': 'copyrightPageUrl', 22 | 'COMM': 'comments', 23 | }; 24 | } 25 | 26 | async getAllTags() { 27 | try { 28 | await this.loadMetaData(); 29 | } catch (error) { 30 | // Do nothing 31 | return; 32 | } 33 | 34 | // Is there an ID3 tags block at all? 35 | if (!this._hasId3Tags()) { 36 | return; 37 | } 38 | 39 | // ID3 major version. We're support only v2.3 up for now. 40 | // NOTE: There are slight differences between v2.3 & v2.4 which we 41 | // don't consider for now as well 42 | if (this._getId3MajorVersion() < 3) { 43 | return; 44 | } 45 | 46 | this.id3Size = new DataView(this.buffer.slice(6, 10)).getInt32(); 47 | 48 | // Determine where the actual tags start from 49 | const id3Start = this._headerIsExtended() 50 | ? 10 + new DataView(this.buffer.slice(10, 14)).getInt32() 51 | : 10; 52 | 53 | return new Promise((resolve, reject) => { 54 | fetch(this.url, { 55 | method: 'GET', 56 | headers: { 57 | Range: `bytes=${id3Start}-${this.id3Size}` 58 | } 59 | }) 60 | .then(response => response.arrayBuffer()) 61 | .then(data => { 62 | this.buffer = data; 63 | this.byteArray = new Uint8Array(data); 64 | 65 | this._readTags(); 66 | 67 | resolve(this.tags); 68 | }); 69 | }); 70 | } 71 | 72 | /** 73 | * Load first 10 bytes of the file to determine whether it has id3 tags and 74 | * if it does - what is the id3 block size 75 | */ 76 | async loadMetaData() { 77 | await fetch(this.url, { 78 | method: 'GET', 79 | headers: { 80 | Range: 'bytes=0-13' 81 | } 82 | }) 83 | .then(response => response.arrayBuffer()) 84 | .then(data => { 85 | this.buffer = data; 86 | this.byteArray = new Uint8Array(data); 87 | }); 88 | } 89 | 90 | _slice(from, length) { 91 | return this.byteArray.slice(from, from + length); 92 | } 93 | 94 | _bytesToString(bytes) { 95 | return (new TextDecoder()).decode(bytes); 96 | } 97 | 98 | _sliceToString(from, length) { 99 | return (new TextDecoder()).decode(this._slice(from, length)); 100 | } 101 | 102 | _readTags() { 103 | let offset = 0; 104 | 105 | // Read all the tags one by one, extract the ones we need 106 | while (offset < this.id3Size) { 107 | try { 108 | offset = this._readTag(offset); 109 | } catch (error) { 110 | // Do nothing 111 | offset += this.id3Size; 112 | } 113 | } 114 | } 115 | 116 | _hasId3Tags() { 117 | // If the first 3 bytes are not 'ID3' - there's no ID3 data in the file 118 | return this._sliceToString(0, 3) === 'ID3'; 119 | } 120 | 121 | _getId3MajorVersion() { 122 | // The ID3 major version is stored in the 4th byte 123 | return this._slice(3, 1)[0]; 124 | } 125 | 126 | _headerIsExtended() { 127 | // The 4 first bits of the 6th byte contain flags (1 bit = 1 flag true 128 | // or false) 129 | const flags = this._slice(5, 1)[0].toString(2); 130 | 131 | if (flags.length == 8) { 132 | return flags[1] === '1'; 133 | } 134 | 135 | if (flags.length == 7) { 136 | return flags[0] === '1'; 137 | } 138 | 139 | return false; 140 | } 141 | 142 | _readTag(offset) { 143 | const type = this._sliceToString(offset, 4); 144 | offset += 4; 145 | 146 | const frameSize = new DataView(this.buffer.slice(offset, offset + 4)).getInt32(); 147 | offset += 4; 148 | 149 | // TODO: See the "4.1. Frame header flags" section at 150 | // http://id3.org/id3v2.4.0-structure on how to handle the frame flags 151 | const flags = this._slice(offset, 2); 152 | offset += 2; 153 | 154 | // NOTE: Textual frames are marked with an encoding byte, which takes 155 | // values $00-$03. It denotes the encoding used for the following text: 156 | // ISO-8859-1, UCS-2, UTF-16BE, UTF-8. We're going to ignore this value 157 | // for the time being. https://en.wikipedia.org/wiki/ID3#ID3v2 158 | let value; 159 | if (this._slice(offset, 1) <= 3) { 160 | value = this._sliceToString(offset + 1, frameSize - 1); 161 | } else { 162 | value = this._sliceToString(offset, frameSize); 163 | } 164 | 165 | if (this.frames[type]) { 166 | switch (type) { 167 | case 'APIC': 168 | // TODO: Each mp3 file can have multiple APICs of different type: icon, front cover, back cover, etc... Handle multiple pictures 169 | // this.tags[this.frames[type]] = this._readApic(this._sliceToString(offset, frameSize)); 170 | // this.tags[this.frames[type]] = this._readApic(new DataView(this.buffer.slice(offset, offset + frameSize))); 171 | this.tags[this.frames[type]] = this._readApic(this._slice(offset, frameSize)); 172 | 173 | // console.log(new DataView(this.buffer.slice(offset, offset + 1)).getInt16()); 174 | // console.log(this._sliceToString(offset, frameSize)); 175 | 176 | // console.log(this.tags[this.frames[type]]); 177 | break; 178 | case 'TRCK': 179 | this.tags[this.frames[type]] = parseInt(value.split('/')[0]); 180 | break; 181 | default: 182 | this.tags[this.frames[type]] = value; 183 | } 184 | } 185 | 186 | offset += frameSize; 187 | 188 | return offset; 189 | } 190 | 191 | _readApic(data) { 192 | const res = {}; 193 | 194 | let offset = 0; 195 | let textEncoding = this._bytesToString(data.slice(offset, 1)); 196 | offset++; 197 | 198 | let valueStart = offset; 199 | let valueEnd = valueStart + 1; 200 | 201 | while (true) { 202 | // console.log(data.slice(valueStart, valueEnd)); 203 | 204 | break; 205 | } 206 | 207 | // console.log(data.getUint8(0) === 0x00); 208 | 209 | res.data = data; 210 | 211 | return res; 212 | } 213 | } 214 | 215 | export default Id3Reader; -------------------------------------------------------------------------------- /dist/skins/nerio/skin.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 10 |
11 | 12 |
13 |
14 |

15 |

16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 | 36 | 37 |
38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 |
50 | 51 |
52 | 53 |
54 | 55 |
56 |
57 | 58 |
59 |
60 |
61 | 62 | 63 |
64 |
65 | 66 |
67 |
68 | 69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 87 |
88 |
89 | 90 |
91 | 92 |
93 | 94 |
95 |
96 | 97 |
98 | 99 |
100 | 101 |
102 |
103 |
104 | 105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | 114 |
115 |
116 |
117 |
118 |
119 | 120 |
121 |

122 |

123 |
124 | 125 |
126 |
127 | 128 |
129 | 130 |
131 | 132 |
133 |
134 | 135 |
136 | 137 |
138 | 139 |
140 |
141 |
142 | 143 | 165 |
166 |
167 |
168 | -------------------------------------------------------------------------------- /dist/skins/calamansi/skin.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Open Sans"); 2 | @import url("https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.css"); 3 | 4 | .calamansi-skin--calamansi { 5 | box-sizing: border-box; 6 | background: #FFFFFF; 7 | position: relative; 8 | width: 100%; 9 | height: 100%; 10 | overflow: hidden; 11 | border-radius: 5px; 12 | box-shadow: 5px 5px 15px rgba(54, 79, 60, 0.4); 13 | -webkit-transition: all .5s ease-in-out; 14 | transition: all .5s ease-in-out; 15 | 16 | font-family: "Open Sans", sans-serif; 17 | font-size: 18px; 18 | line-height: 1.2; 19 | 20 | display: flex; 21 | flex-direction: column; 22 | } 23 | 24 | .calamansi-skin--calamansi .clmns--album { 25 | position: absolute; 26 | background: linear-gradient(rgba(54, 79, 60, 0.25), rgba(73, 101, 77, 0.55)); 27 | background-repeat: no-repeat; 28 | background-size: cover; 29 | background-position: center; 30 | left: 0; 31 | top: 0; 32 | width: 100%; 33 | height: 100%; 34 | border-radius: 5px; 35 | } 36 | 37 | .calamansi-skin--calamansi .clmns--heart { 38 | position: absolute; 39 | right: 0; 40 | color: #FFFFFF; 41 | margin: 10px; 42 | transition: all .4s ease; 43 | } 44 | 45 | .calamansi-skin--calamansi .clmns--space { 46 | flex: 1; 47 | } 48 | 49 | .calamansi-skin--calamansi .clmns--info { 50 | position: relative; 51 | display: flex; 52 | flex-direction: column; 53 | width: 100%; 54 | background: rgba(255, 255, 255, 0.85); 55 | } 56 | 57 | .calamansi-skin--calamansi .clmns--info.clmns--show-playlist { 58 | position: relative; 59 | max-height: 100%; 60 | height: 100%; 61 | } 62 | 63 | .calamansi-skin--calamansi .clmns--playback-info { 64 | display: flex; 65 | align-items: center; 66 | margin-top: 15px; 67 | margin-bottom: 10px; 68 | } 69 | 70 | .calamansi-skin--calamansi .clmns--playback-bar { 71 | flex: 1; 72 | position: relative; 73 | height: .4em; 74 | background: #cdd9c2; 75 | border-radius: 10px; 76 | } 77 | 78 | .calamansi-skin--calamansi .clmns--playback-load { 79 | width: 100%; 80 | height: 100%; 81 | border-radius: 10px; 82 | } 83 | .calamansi-skin--calamansi .clmns--playback-progress { 84 | position: absolute; 85 | top: 0; 86 | background-color: #8BA989; 87 | width: 0%; 88 | height: 100%; 89 | border-radius: 10px; 90 | } 91 | 92 | .calamansi-skin--calamansi .clmns--playback-time, .calamansi-skin--calamansi .clmns--playback-time-left { 93 | color: #364F3C; 94 | font-size: .7em; 95 | margin: 0 10px; 96 | } 97 | 98 | .calamansi-skin--calamansi .clmns--playback-time { 99 | left: 15px; 100 | } 101 | 102 | .calamansi-skin--calamansi .clmns--playback-time-left { 103 | right: 15px; 104 | } 105 | 106 | .calamansi-skin--calamansi .clmns--currently-playing { 107 | text-align: center; 108 | } 109 | 110 | .calamansi-skin--calamansi .clmns--song-name, 111 | .calamansi-skin--calamansi .clmns--artist-name 112 | { 113 | font-weight: 400; 114 | text-transform: uppercase; 115 | margin: 0; 116 | padding: 0 1em; 117 | 118 | white-space: nowrap; 119 | overflow: hidden; 120 | text-overflow: ellipsis; 121 | } 122 | 123 | .calamansi-skin--calamansi .clmns--song-name { 124 | font-size: .8em; 125 | letter-spacing: 3px; 126 | color: #364F3C; 127 | } 128 | 129 | .calamansi-skin--calamansi .clmns--artist-name { 130 | font-size: .6em; 131 | letter-spacing: 1.5px; 132 | color: #557c5f; 133 | margin-top: 5px; 134 | } 135 | 136 | .calamansi-skin--calamansi .clmns--controls { 137 | display: flex; 138 | align-items: center; 139 | font-size: .8em; 140 | justify-content: center; 141 | color: #8BA989; 142 | padding: 1em; 143 | } 144 | .calamansi-skin--calamansi .clmns--controls .clmns--row { 145 | display: none; 146 | } 147 | .calamansi-skin--calamansi .clmns--controls .clmns--center { 148 | flex: 1; 149 | display: flex; 150 | justify-content: center; 151 | } 152 | .calamansi-skin--calamansi .clmns--controls .clmns--center > div { 153 | margin: 0 1em; 154 | } 155 | .calamansi-skin--calamansi .clmns--controls .clmns--center div:first-of-type { 156 | margin-left: 0; 157 | } 158 | .calamansi-skin--calamansi .clmns--controls .clmns--center div:last-of-type { 159 | margin-right: 0; 160 | } 161 | .calamansi-skin--calamansi .clmns--controls .clmns--play, .calamansi-skin--calamansi .clmns--controls .clmns--pause { 162 | color: #6e946c; 163 | } 164 | 165 | .calamansi-skin--calamansi .clmns--controls .clmns--volume { 166 | display: flex; 167 | align-items: center; 168 | } 169 | .calamansi-skin--calamansi .clmns--controls .clmns--volume .clmns--volume-bar { 170 | position: relative; 171 | width: 0; 172 | height: .4em; 173 | background-color: #cdd9c2; 174 | margin-top: -.1em; 175 | transition: all .2s ease; 176 | border-radius: 10px; 177 | } 178 | .calamansi-skin--calamansi .clmns--controls .clmns--volume:hover .clmns--volume-bar { 179 | width: 5em; 180 | margin-left: .5em; 181 | } 182 | .calamansi-skin--calamansi .clmns--controls .clmns--volume .clmns--volume-value { 183 | height: 100%; 184 | background-color: #8BA989; 185 | border-radius: 10px; 186 | } 187 | 188 | /* Small player size controls */ 189 | .calamansi-skin--calamansi .clmns--controls.clmns--compact { 190 | flex-direction: column; 191 | } 192 | .calamansi-skin--calamansi .clmns--controls.clmns--compact .clmns--left, 193 | .calamansi-skin--calamansi .clmns--controls.clmns--compact .clmns--center, 194 | .calamansi-skin--calamansi .clmns--controls.clmns--compact .clmns--right 195 | { 196 | display: none; 197 | } 198 | .calamansi-skin--calamansi .clmns--controls.clmns--compact .clmns--row { 199 | display: flex; 200 | } 201 | .calamansi-skin--calamansi .clmns--controls.clmns--compact .clmns--row:last-of-type { 202 | margin-top: .5em; 203 | } 204 | .calamansi-skin--calamansi .clmns--controls.clmns--compact .clmns--row > div { 205 | margin: 0 .5em; 206 | } 207 | .calamansi-skin--calamansi .clmns--controls.clmns--compact .clmns--volume:hover .clmns--volume-bar { 208 | width: 3em; 209 | margin-left: .5em; 210 | } 211 | 212 | .calamansi-skin--calamansi .clmns--play, 213 | .calamansi-skin--calamansi .clmns--pause, 214 | .calamansi-skin--calamansi .clmns--next, 215 | .calamansi-skin--calamansi .clmns--previous, 216 | .calamansi-skin--calamansi .clmns--option, 217 | .calamansi-skin--calamansi .clmns--loop, 218 | .calamansi-skin--calamansi .clmns--volume, 219 | .calamansi-skin--calamansi .clmns--shuffle 220 | { 221 | transition: all .5s ease; 222 | } 223 | .calamansi-skin--calamansi .clmns--play:hover, 224 | .calamansi-skin--calamansi .clmns--pause:hover, 225 | .calamansi-skin--calamansi .clmns--next:hover, 226 | .calamansi-skin--calamansi .clmns--previous:hover, 227 | .calamansi-skin--calamansi .clmns--option:hover 228 | .calamansi-skin--calamansi .clmns--loop:hover, 229 | .calamansi-skin--calamansi .clmns--volume:hover, 230 | .calamansi-skin--calamansi .clmns--shuffle:hover 231 | { 232 | color: #557c5f; 233 | } 234 | 235 | .calamansi-skin--calamansi .clmns--custom-checkbox { 236 | position: relative; 237 | } 238 | .calamansi-skin--calamansi .clmns--custom-checkbox input { 239 | position: absolute; 240 | 241 | top: 0; 242 | left: 0; 243 | margin: 0; 244 | opacity: 0; 245 | } 246 | .calamansi-skin--calamansi .clmns--custom-checkbox input:checked ~ i { 247 | color: #49654D; 248 | transition: all .4s ease; 249 | } 250 | 251 | .calamansi-skin--calamansi .clmns--playlist-wrapper { 252 | flex: 1; 253 | height: 0; 254 | max-height: 100%; 255 | } 256 | .calamansi-skin--calamansi .clmns--playlist ul { 257 | list-style: none; 258 | margin: 0; 259 | padding: 0; 260 | } 261 | .clmns--playlist-item.template { 262 | display: none !important; 263 | } 264 | .calamansi-skin--calamansi .clmns--playlist .clmns--playlist-item { 265 | display: flex; 266 | font-size: .8em; 267 | color: #364F3C; 268 | padding: .75em 1em; 269 | } 270 | .calamansi-skin--calamansi .clmns--playlist .clmns--playlist-item.clmns--active { 271 | background-color: #cdd9c2; 272 | } 273 | .calamansi-skin--calamansi .clmns--playlist .clmns--playlist-track-info--name { 274 | flex: 1; 275 | padding-right: 1em; 276 | 277 | white-space: nowrap; 278 | overflow: hidden; 279 | text-overflow: ellipsis; 280 | } 281 | 282 | .calamansi-skin--calamansi .simplebar-scrollbar::before { 283 | background: #8BA989; 284 | } 285 | 286 | /* Icon font */ 287 | /* Generated by Glyphter (http://www.clmns--glyphter.com) on Sun Sep 22 2019*/ 288 | @font-face { 289 | font-family: 'calamansi-skin--calamansi--glyphter'; 290 | src: url('fonts/Glyphter.eot'); 291 | src: url('fonts/Glyphter.eot?#iefix') format('embedded-opentype'), 292 | url('fonts/Glyphter.woff') format('woff'), 293 | url('fonts/Glyphter.ttf') format('truetype'), 294 | url('fonts/Glyphter.svg#Glyphter') format('svg'); 295 | font-weight: normal; 296 | font-style: normal; 297 | } 298 | .calamansi-skin--calamansi .clmns--glyphter:before { 299 | display: inline-block; 300 | font-family: 'calamansi-skin--calamansi--glyphter'; 301 | font-style: normal; 302 | font-weight: normal; 303 | line-height: 1; 304 | -webkit-font-smoothing: antialiased; 305 | -moz-osx-font-smoothing: grayscale 306 | } 307 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-play:before { 308 | content: '\0041'; 309 | } 310 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-pause:before { 311 | content: '\0042'; 312 | } 313 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-backward:before { 314 | content: '\0044'; 315 | } 316 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-forward:before { 317 | content: '\0045'; 318 | } 319 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-bars:before { 320 | content: '\0046'; 321 | } 322 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-volume-up:before { 323 | content: '\0047'; 324 | } 325 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-random:before { 326 | content: '\0048'; 327 | } 328 | .calamansi-skin--calamansi .clmns--glyphter.clmns--fa-loop:before { 329 | content: '\0049'; 330 | } 331 | 332 | @media all and (min-width: 1024px) { 333 | .calamansi-skin--calamansi { 334 | font-size: 16px; 335 | } 336 | } -------------------------------------------------------------------------------- /dist/skins/ayon/skin.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Fira+Sans"); 2 | 3 | .calamansi-skin--ayon { 4 | position: relative; 5 | padding-top: 60px; 6 | font-family: 'Fira Sans', sans-serif; 7 | } 8 | .calamansi-skin--ayon .clmns--info { 9 | position: absolute; 10 | bottom: 0; 11 | opacity: 0; 12 | left: 10px; 13 | right: 10px; 14 | background-color: rgba(255, 255, 255, 0.75); 15 | padding: 5px 15px 5px 110px; 16 | border-radius: 15px; 17 | transition: all .5s ease; 18 | } 19 | .calamansi-skin--ayon .clmns--info .clmns--artist, 20 | .calamansi-skin--ayon .clmns--info .clmns--name 21 | { 22 | display: block; 23 | white-space: nowrap; 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | } 27 | .calamansi-skin--ayon .clmns--info .clmns--artist { 28 | color: #222; 29 | font-size: 16px; 30 | margin-bottom: 5px; 31 | } 32 | .calamansi-skin--ayon .clmns--info .clmns--name { 33 | color: #999; 34 | font-size: 12px; 35 | margin-bottom: 8px; 36 | } 37 | .calamansi-skin--ayon .clmns--info .clmns--playback-times { 38 | color: #222; 39 | display: flex; 40 | font-size: .85em; 41 | } 42 | .calamansi-skin--ayon .clmns--info .clmns--playback-time-separator { 43 | flex: 1; 44 | } 45 | .calamansi-skin--ayon .clmns--info .clmns--progress-bar { 46 | background-color: #ddd; 47 | height: 3px; 48 | width: 100%; 49 | position: relative; 50 | padding: .25em 0; 51 | margin-bottom: 10px; 52 | } 53 | .calamansi-skin--ayon .clmns--info .clmns--progress-bar .clmns--bar { 54 | position: absolute; 55 | left: 0; 56 | top: 0; 57 | bottom: 0; 58 | background-color: red; 59 | transition: all .2s ease; 60 | } 61 | .calamansi-skin--ayon .clmns--info.clmns--active { 62 | bottom: 70px; 63 | opacity: 1; 64 | transition: all .5s ease; 65 | } 66 | .calamansi-skin--ayon .clmns--control-panel { 67 | position: relative; 68 | background-color: #fff; 69 | border-radius: 15px; 70 | height: 80px; 71 | z-index: 5; 72 | box-shadow: 0px 20px 20px 5px rgba(132, 132, 132, 0.3); 73 | } 74 | .calamansi-skin--ayon .clmns--control-panel .clmns--album-art { 75 | position: absolute; 76 | left: 20px; 77 | top: -15px; 78 | height: 80px; 79 | width: 80px; 80 | border-radius: 50%; 81 | box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0); 82 | transform: scale(1); 83 | transition: all .5s ease; 84 | 85 | background-position: center; 86 | background-repeat: no-repeat; 87 | background-size: cover; 88 | } 89 | .calamansi-skin--ayon .clmns--control-panel .clmns--album-art::after { 90 | content: ''; 91 | position: absolute; 92 | top: 50%; 93 | left: 50%; 94 | width: 15px; 95 | height: 15px; 96 | background-color: #fff; 97 | border-radius: 50%; 98 | z-index: 5; 99 | transform: translate(-50%, -50%); 100 | -webkit-transform: translate(-50%, -50%); 101 | } 102 | .calamansi-skin--ayon .clmns--control-panel.clmns--active .clmns--album-art { 103 | box-shadow: 0px 0px 20px 5px rgba(0, 0, 0, 0.2); 104 | transition: all .3s ease; 105 | 106 | left: 15px; 107 | top: -20px; 108 | width: 90px; 109 | height: 90px; 110 | 111 | animation: calamansi-skin--ayon--rotation 3s infinite linear; 112 | -webkit-animation: calamansi-skin--ayon--rotation 3s infinite linear; 113 | animation-fill-mode: forwards; 114 | } 115 | @keyframes calamansi-skin--ayon--rotation { 116 | 0% { 117 | transform: rotate(0deg); 118 | } 119 | 100% { 120 | transform: rotate(360deg); 121 | } 122 | } 123 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls { 124 | display: flex; 125 | justify-content: flex-end; 126 | height: 80px; 127 | padding: 0 15px; 128 | } 129 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--prev, 130 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--play, 131 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--pause, 132 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--next { 133 | width: 55px; 134 | height: auto; 135 | border-radius: 10px; 136 | background-position: center center; 137 | background-repeat: no-repeat; 138 | background-size: 20px; 139 | margin: 5px 0; 140 | background-color: #fff; 141 | cursor: pointer; 142 | transition: background-color .3s ease; 143 | -webkit-transition: background-color .3s ease; 144 | } 145 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--control:hover { 146 | background-color: #eee; 147 | transition: background-color .3s ease; 148 | -webkit-transition: background-color .3s ease; 149 | } 150 | 151 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--play { 152 | background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDIzMi4xNTMgMjMyLjE1MyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjMyLjE1MyAyMzIuMTUzOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjEyOHB4IiBoZWlnaHQ9IjEyOHB4Ij4KPGcgaWQ9IlBsYXkiPgoJPHBhdGggc3R5bGU9ImZpbGwtcnVsZTpldmVub2RkO2NsaXAtcnVsZTpldmVub2RkOyIgZD0iTTIwMy43OTEsOTkuNjI4TDQ5LjMwNywyLjI5NGMtNC41NjctMi43MTktMTAuMjM4LTIuMjY2LTE0LjUyMS0yLjI2NiAgIGMtMTcuMTMyLDAtMTcuMDU2LDEzLjIyNy0xNy4wNTYsMTYuNTc4djE5OC45NGMwLDIuODMzLTAuMDc1LDE2LjU3OSwxNy4wNTYsMTYuNTc5YzQuMjgzLDAsOS45NTUsMC40NTEsMTQuNTIxLTIuMjY3ICAgbDE1NC40ODMtOTcuMzMzYzEyLjY4LTcuNTQ1LDEwLjQ4OS0xNi40NDksMTAuNDg5LTE2LjQ0OVMyMTYuNDcxLDEwNy4xNzIsMjAzLjc5MSw5OS42Mjh6IiBmaWxsPSIjYzJjNmNmIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==); 153 | } 154 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--pause { 155 | background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDIzMi42NzkgMjMyLjY3OSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjMyLjY3OSAyMzIuNjc5OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjEyOHB4IiBoZWlnaHQ9IjEyOHB4Ij4KPGcgaWQ9IlBhdXNlIj4KCTxwYXRoIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDsiIGQ9Ik04MC41NDMsMEgzNS43OTdjLTkuODg1LDAtMTcuODk4LDguMDE0LTE3Ljg5OCwxNy44OTh2MTk2Ljg4MyAgIGMwLDkuODg1LDguMDEzLDE3Ljg5OCwxNy44OTgsMTcuODk4aDQ0Ljc0NmM5Ljg4NSwwLDE3Ljg5OC04LjAxMywxNy44OTgtMTcuODk4VjE3Ljg5OEM5OC40NCw4LjAxNCw5MC40MjcsMCw4MC41NDMsMHogTTE5Ni44ODIsMCAgIGgtNDQuNzQ2Yy05Ljg4NiwwLTE3Ljg5OSw4LjAxNC0xNy44OTksMTcuODk4djE5Ni44ODNjMCw5Ljg4NSw4LjAxMywxNy44OTgsMTcuODk5LDE3Ljg5OGg0NC43NDYgICBjOS44ODUsMCwxNy44OTgtOC4wMTMsMTcuODk4LTE3Ljg5OFYxNy44OThDMjE0Ljc4MSw4LjAxNCwyMDYuNzY3LDAsMTk2Ljg4MiwweiIgZmlsbD0iI2MyYzZjZiIvPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=); 156 | } 157 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--prev { 158 | background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDI1MC40ODggMjUwLjQ4OCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjUwLjQ4OCAyNTAuNDg4OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjEyOHB4IiBoZWlnaHQ9IjEyOHB4Ij4KPGcgaWQ9IlByZXZpb3VzX3RyYWNrIj4KCTxwYXRoIHN0eWxlPSJmaWxsLXJ1bGU6ZXZlbm9kZDtjbGlwLXJ1bGU6ZXZlbm9kZDsiIGQ9Ik0yMzcuNDg0LDIyLjU4N2MtMy4yNjYsMC03LjU5MS0wLjQwMS0xMS4wNzIsMi4wMDVsLTkyLjI2NCw3Ny45MVYzNy4yNTIgICBjMC0yLjUwNywwLjA1Ny0xNC42NjYtMTMuMDA0LTE0LjY2NmMtMy4yNjUsMC03LjU5LTAuNDAxLTExLjA3MiwyLjAwNUw4LjEwNywxMTAuNjkzYy05LjY2OSw2LjY3NC03Ljk5NywxNC41NTEtNy45OTcsMTQuNTUxICAgcy0xLjY3MSw3Ljg3OCw3Ljk5NywxNC41NTFsMTAxLjk2NSw4Ni4xMDJjMy40ODIsMi40MDUsNy44MDcsMi4wMDQsMTEuMDcyLDIuMDA0YzEzLjA2MiwwLDEzLjAwNC0xMS43LDEzLjAwNC0xNC42NjZ2LTY1LjI0OSAgIGw5Mi4yNjQsNzcuOTExYzMuNDgyLDIuNDA1LDcuODA3LDIuMDA0LDExLjA3MiwyLjAwNGMxMy4wNjIsMCwxMy4wMDQtMTEuNywxMy4wMDQtMTQuNjY2VjM3LjI1MiAgIEMyNTAuNDg4LDM0Ljc0NiwyNTAuNTQ2LDIyLjU4NywyMzcuNDg0LDIyLjU4N3oiIGZpbGw9IiNjMmM2Y2YiLz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K); 159 | } 160 | .calamansi-skin--ayon .clmns--control-panel .clmns--controls .clmns--next { 161 | background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTguMS4xLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDI1MC40ODggMjUwLjQ4OCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjUwLjQ4OCAyNTAuNDg4OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjEyOHB4IiBoZWlnaHQ9IjEyOHB4Ij4KPGcgaWQ9Ik5leHRfdHJhY2tfMiI+Cgk8cGF0aCBzdHlsZT0iZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7IiBkPSJNMjQyLjM4MSwxMTAuNjkzTDE0MC40MTUsMjQuNTkxYy0zLjQ4LTIuNDA2LTcuODA1LTIuMDA1LTExLjA3MS0yLjAwNSAgIGMtMTMuMDYxLDAtMTMuMDAzLDExLjctMTMuMDAzLDE0LjY2NnY2NS4yNDlsLTkyLjI2NS03Ny45MWMtMy40ODItMi40MDYtNy44MDctMi4wMDUtMTEuMDcyLTIuMDA1ICAgQy0wLjA1NywyMi41ODcsMCwzNC4yODcsMCwzNy4yNTJ2MTc1Ljk4M2MwLDIuNTA3LTAuMDU3LDE0LjY2NiwxMy4wMDQsMTQuNjY2YzMuMjY1LDAsNy41OSwwLjQwMSwxMS4wNzItMi4wMDVsOTIuMjY1LTc3LjkxICAgdjY1LjI0OWMwLDIuNTA3LTAuMDU4LDE0LjY2NiwxMy4wMDMsMTQuNjY2YzMuMjY2LDAsNy41OTEsMC40MDEsMTEuMDcxLTIuMDA1bDEwMS45NjYtODYuMTAxICAgYzkuNjY4LTYuNjc1LDcuOTk3LTE0LjU1MSw3Ljk5Ny0xNC41NTFTMjUyLjA0OSwxMTcuMzY3LDI0Mi4zODEsMTEwLjY5M3oiIGZpbGw9IiNjMmM2Y2YiLz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K); 162 | } 163 | 164 | @media all and (min-width: 1024px) { 165 | .calamansi-skin--ayon .clmns--info .clmns--progress-bar { 166 | padding: 0; 167 | } 168 | } -------------------------------------------------------------------------------- /dist/skins/calamansi-compact/skin.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Open Sans"); 2 | @import url("https://unpkg.com/simplebar@4.2.3/dist/simplebar.min.css"); 3 | 4 | .calamansi-skin--calamansi-compact { 5 | background: #FFFFFF; 6 | position: relative; 7 | width: 100%; 8 | height: auto; 9 | overflow: hidden; 10 | border-radius: 5px; 11 | box-shadow: 5px 5px 15px rgba(54, 79, 60, 0.4); 12 | -webkit-transition: all .5s ease-in-out; 13 | transition: all .5s ease-in-out; 14 | 15 | font-family: "Open Sans", sans-serif; 16 | font-size: 18px; 17 | line-height: 1.2; 18 | 19 | display: flex; 20 | flex-direction: column; 21 | } 22 | 23 | .calamansi-skin--calamansi-compact .clmns--heart { 24 | position: absolute; 25 | right: 0; 26 | color: #FFFFFF; 27 | margin: 10px; 28 | transition: all .4s ease; 29 | } 30 | 31 | .calamansi-skin--calamansi-compact .clmns--info { 32 | width: 100%; 33 | background: rgba(255, 255, 255, 0.85); 34 | } 35 | 36 | .calamansi-skin--calamansi-compact.clmns--show-playlist .clmns--playlist-container { 37 | display: flex; 38 | } 39 | 40 | .calamansi-skin--calamansi-compact .clmns--playback-info { 41 | display: flex; 42 | align-items: center; 43 | margin-top: 15px; 44 | margin-bottom: 10px; 45 | } 46 | 47 | .calamansi-skin--calamansi-compact .clmns--playback-bar { 48 | flex: 1; 49 | position: relative; 50 | height: .4em; 51 | background: #cdd9c2; 52 | border-radius: 10px; 53 | } 54 | 55 | .calamansi-skin--calamansi-compact .clmns--playback-load { 56 | width: 100%; 57 | height: 100%; 58 | border-radius: 10px; 59 | } 60 | .calamansi-skin--calamansi-compact .clmns--playback-progress { 61 | position: absolute; 62 | top: 0; 63 | background-color: #8BA989; 64 | width: 0%; 65 | height: 100%; 66 | border-radius: 10px; 67 | } 68 | 69 | .calamansi-skin--calamansi-compact .clmns--playback-time, .calamansi-skin--calamansi-compact .clmns--playback-time-left { 70 | color: #364F3C; 71 | font-size: .7em; 72 | margin: 0 10px; 73 | } 74 | 75 | .calamansi-skin--calamansi-compact .clmns--playback-time { 76 | left: 15px; 77 | } 78 | 79 | .calamansi-skin--calamansi-compact .clmns--playback-time-left { 80 | right: 15px; 81 | } 82 | 83 | .calamansi-skin--calamansi-compact .clmns--currently-playing { 84 | text-align: center; 85 | } 86 | 87 | .calamansi-skin--calamansi-compact .clmns--song-name, 88 | .calamansi-skin--calamansi-compact .clmns--artist-name 89 | { 90 | font-weight: 400; 91 | text-transform: uppercase; 92 | margin: 0; 93 | padding: 0 1em; 94 | 95 | white-space: nowrap; 96 | overflow: hidden; 97 | text-overflow: ellipsis; 98 | } 99 | 100 | .calamansi-skin--calamansi-compact .clmns--song-name { 101 | font-size: .8em; 102 | letter-spacing: 3px; 103 | color: #364F3C; 104 | } 105 | 106 | .calamansi-skin--calamansi-compact .clmns--artist-name { 107 | font-size: .6em; 108 | letter-spacing: 1.5px; 109 | color: #557c5f; 110 | margin-top: 5px; 111 | } 112 | 113 | .calamansi-skin--calamansi-compact .clmns--controls { 114 | display: flex; 115 | align-items: center; 116 | font-size: .8em; 117 | justify-content: center; 118 | color: #8BA989; 119 | padding: 1em; 120 | } 121 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--row { 122 | display: none; 123 | } 124 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--center { 125 | flex: 1; 126 | display: flex; 127 | justify-content: center; 128 | } 129 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--center > div { 130 | margin: 0 1em; 131 | } 132 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--center div:first-of-type { 133 | margin-left: 0; 134 | } 135 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--center div:last-of-type { 136 | margin-right: 0; 137 | } 138 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--play, .calamansi-skin--calamansi-compact .clmns--controls .clmns--pause { 139 | color: #6e946c; 140 | } 141 | 142 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--volume { 143 | display: flex; 144 | align-items: center; 145 | } 146 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--volume .clmns--volume-bar { 147 | position: relative; 148 | width: 0; 149 | height: .4em; 150 | background-color: #cdd9c2; 151 | margin-top: -.1em; 152 | transition: all .2s ease; 153 | border-radius: 10px; 154 | } 155 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--volume:hover .clmns--volume-bar { 156 | width: 5em; 157 | margin-left: .5em; 158 | } 159 | .calamansi-skin--calamansi-compact .clmns--controls .clmns--volume .clmns--volume-value { 160 | height: 100%; 161 | background-color: #8BA989; 162 | border-radius: 10px; 163 | } 164 | 165 | /* Small player size controls */ 166 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact { 167 | flex-direction: column; 168 | } 169 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact .clmns--left, 170 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact .clmns--center, 171 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact .clmns--right 172 | { 173 | display: none; 174 | } 175 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact .clmns--row { 176 | display: flex; 177 | } 178 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact .clmns--row:last-of-type { 179 | margin-top: .5em; 180 | } 181 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact .clmns--row > div { 182 | margin: 0 .5em; 183 | } 184 | .calamansi-skin--calamansi-compact .clmns--controls.clmns--compact .clmns--volume:hover .clmns--volume-bar { 185 | width: 3em; 186 | margin-left: .5em; 187 | } 188 | 189 | .calamansi-skin--calamansi-compact .clmns--play, 190 | .calamansi-skin--calamansi-compact .clmns--pause, 191 | .calamansi-skin--calamansi-compact .clmns--next, 192 | .calamansi-skin--calamansi-compact .clmns--previous, 193 | .calamansi-skin--calamansi-compact .clmns--option, 194 | .calamansi-skin--calamansi-compact .clmns--loop, 195 | .calamansi-skin--calamansi-compact .clmns--volume, 196 | .calamansi-skin--calamansi-compact .clmns--shuffle 197 | { 198 | transition: all .5s ease; 199 | } 200 | .calamansi-skin--calamansi-compact .clmns--play:hover, 201 | .calamansi-skin--calamansi-compact .clmns--pause:hover, 202 | .calamansi-skin--calamansi-compact .clmns--next:hover, 203 | .calamansi-skin--calamansi-compact .clmns--previous:hover, 204 | .calamansi-skin--calamansi-compact .clmns--option:hover 205 | .calamansi-skin--calamansi-compact .clmns--loop:hover, 206 | .calamansi-skin--calamansi-compact .clmns--volume:hover, 207 | .calamansi-skin--calamansi-compact .clmns--shuffle:hover 208 | { 209 | color: #557c5f; 210 | } 211 | 212 | .calamansi-skin--calamansi-compact .clmns--custom-checkbox { 213 | position: relative; 214 | } 215 | .calamansi-skin--calamansi-compact .clmns--custom-checkbox input { 216 | position: absolute; 217 | 218 | top: 0; 219 | left: 0; 220 | margin: 0; 221 | opacity: 0; 222 | } 223 | .calamansi-skin--calamansi-compact .clmns--custom-checkbox input:checked ~ i { 224 | color: #49654D; 225 | transition: all .4s ease; 226 | } 227 | 228 | .calamansi-skin--calamansi-compact .clmns--playlist-container { 229 | display: none; 230 | position: absolute; 231 | width: 100%; 232 | height: 100%; 233 | background-color: #ffffffdd; 234 | } 235 | .calamansi-skin--calamansi-compact .clmns--playlist-container .clmns--playlist-wrapper { 236 | width: 100%; 237 | } 238 | .calamansi-skin--calamansi-compact .clmns--playlist-container .clmns--control { 239 | display: flex; 240 | align-items: flex-end; 241 | font-size: .8em; 242 | color: #49654D; 243 | padding: 1em 1em; 244 | } 245 | .calamansi-skin--calamansi-compact .clmns--playlist ul { 246 | list-style: none; 247 | margin: 0; 248 | padding: 0; 249 | } 250 | .clmns--playlist-item.clmns--template { 251 | display: none !important; 252 | } 253 | .calamansi-skin--calamansi-compact .clmns--playlist .clmns--playlist-item { 254 | display: flex; 255 | font-size: .8em; 256 | color: #364F3C; 257 | padding: .75em 1em; 258 | } 259 | .calamansi-skin--calamansi-compact .clmns--playlist .clmns--playlist-item.clmns--active { 260 | background-color: #cdd9c2; 261 | } 262 | .calamansi-skin--calamansi-compact .clmns--playlist .clmns--playlist-track-info--name { 263 | flex: 1; 264 | padding-right: 1em; 265 | 266 | white-space: nowrap; 267 | overflow: hidden; 268 | text-overflow: ellipsis; 269 | } 270 | 271 | .calamansi-skin--calamansi-compact .simplebar-scrollbar::before { 272 | background: #8BA989; 273 | } 274 | 275 | /* Icon font */ 276 | /* Generated by Glyphter (http://www.clmns--glyphter.com) on Sun Sep 22 2019*/ 277 | @font-face { 278 | font-family: 'calamansi-skin--calamansi-compact--glyphter'; 279 | src: url('fonts/Glyphter.eot'); 280 | src: url('fonts/Glyphter.eot?#iefix') format('embedded-opentype'), 281 | url('fonts/Glyphter.woff') format('woff'), 282 | url('fonts/Glyphter.ttf') format('truetype'), 283 | url('fonts/Glyphter.svg#Glyphter') format('svg'); 284 | font-weight: normal; 285 | font-style: normal; 286 | } 287 | .calamansi-skin--calamansi-compact .clmns--glyphter:before{ 288 | display: inline-block; 289 | font-family: 'calamansi-skin--calamansi-compact--glyphter'; 290 | font-style: normal; 291 | font-weight: normal; 292 | line-height: 1; 293 | -webkit-font-smoothing: antialiased; 294 | -moz-osx-font-smoothing: grayscale 295 | } 296 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-play:before { 297 | content: '\0041'; 298 | } 299 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-pause:before { 300 | content: '\0042'; 301 | } 302 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-backward:before { 303 | content: '\0044'; 304 | } 305 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-forward:before { 306 | content: '\0045'; 307 | } 308 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-bars:before { 309 | content: '\0046'; 310 | } 311 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-volume-up:before { 312 | content: '\0047'; 313 | } 314 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-random:before { 315 | content: '\0048'; 316 | } 317 | .calamansi-skin--calamansi-compact .clmns--glyphter.clmns--fa-loop:before { 318 | content: '\0049'; 319 | } 320 | 321 | @media all and (min-width: 1024px) { 322 | .calamansi-skin--calamansi-compact { 323 | font-size: 16px; 324 | } 325 | } -------------------------------------------------------------------------------- /dist/skins/nerio/fonts/Glyphter.svg: -------------------------------------------------------------------------------- 1 | Generated by Glyphter -------------------------------------------------------------------------------- /dist/skins/nerio/skin.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto:300,400,700"); 2 | 3 | .calamansi-skin--nerio { 4 | font-family: 'Roboto', sans-serif; 5 | font-size: 16px; 6 | background-color: #ffffff; 7 | width: 100%; 8 | height: 100%; 9 | 10 | display: flex; 11 | flex-direction: column; 12 | box-sizing: border-box; 13 | } 14 | 15 | .calamansi-skin--nerio .clmns--header { 16 | position: relative; 17 | } 18 | .calamansi-skin--nerio .clmns--header .clmns--track-info--albumCover { 19 | width: 100%; 20 | height: 100%; 21 | background-size: cover; 22 | background-position: center; 23 | transition: all .2s ease-in-out; 24 | } 25 | .calamansi-skin--nerio .clmns--header .clmns--placeholder-svg { 26 | width: 100%; 27 | height: auto; 28 | } 29 | .calamansi-skin--nerio .clmns--header .clmns--section-controls { 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | margin: 0; 34 | padding: 1em; 35 | width: 100%; 36 | text-shadow: 0 0 3px #000; 37 | opacity: .9; 38 | display: flex; 39 | justify-content: flex-start; 40 | font-size: 1em; 41 | } 42 | .calamansi-skin--nerio .clmns--header .clmns--right { 43 | display: none; 44 | } 45 | .calamansi-skin--nerio .clmns--header .clmns--controls { 46 | display: none; 47 | } 48 | 49 | .calamansi-skin--nerio .clmns--main-section { 50 | flex: 1; 51 | display: flex; 52 | position: relative; 53 | background-color: #131313; 54 | overflow: hidden; 55 | } 56 | .calamansi-skin--nerio .clmns--content-main { 57 | flex: 1; 58 | 59 | display: flex; 60 | flex-direction: column; 61 | min-width: 0; 62 | width: 100%; 63 | height: 100%; 64 | 65 | position: absolute; 66 | opacity: 1; 67 | } 68 | 69 | .calamansi-skin--nerio .clmns--main { 70 | flex: 1; 71 | background-color: #131313; 72 | padding: 1em 1em; 73 | 74 | display: flex; 75 | flex-direction: column; 76 | } 77 | 78 | .calamansi-skin--nerio .clmns--section-playback-controls { 79 | padding-bottom: 1em; 80 | } 81 | .calamansi-skin--nerio .clmns--section-playback-controls .clmns--playback-bar { 82 | position: relative; 83 | padding: .5em 0; 84 | } 85 | .calamansi-skin--nerio .clmns--section-playback-controls .clmns--playback-bar-bg { 86 | height: 3px; 87 | background-color: #666666; 88 | } 89 | .calamansi-skin--nerio .clmns--section-playback-controls .clmns--playback-progress { 90 | height: 3px; 91 | background-color: #ed4f46; 92 | position: relative; 93 | } 94 | .calamansi-skin--nerio .clmns--section-playback-controls .clmns--playback-progress::after { 95 | content: ''; 96 | position: absolute; 97 | right: 0; 98 | top: -5px; 99 | height: 13px; 100 | width: 3px; 101 | background-color: #fff; 102 | } 103 | .calamansi-skin--nerio .clmns--section-playback-controls .clmns--section-playback-times { 104 | display: flex; 105 | font-size: .75em; 106 | color: #5d5d5d; 107 | } 108 | .calamansi-skin--nerio .clmns--section-playback-controls .clmns--section-playback-times > div:first-of-type { 109 | flex: 1; 110 | text-align: left; 111 | } 112 | 113 | .calamansi-skin--nerio .clmns--section-track-info { 114 | padding: 0 1em; 115 | text-align: center; 116 | } 117 | .calamansi-skin--nerio .clmns--section-track-info .clmns--track-info { 118 | white-space: nowrap; 119 | overflow: hidden; 120 | text-overflow: ellipsis; 121 | } 122 | .calamansi-skin--nerio .clmns--section-track-info .clmns--track-info--titleOrFilename { 123 | color: #ffffff; 124 | text-transform: uppercase; 125 | font-weight: 700; 126 | font-size: .9em; 127 | } 128 | .calamansi-skin--nerio .clmns--section-track-info .clmns--track-info--artist { 129 | color: #969696; 130 | font-size: .75em; 131 | } 132 | 133 | .calamansi-skin--nerio .clmns--section-controls { 134 | flex: 1; 135 | margin-top: 1em; 136 | display: flex; 137 | justify-content: center; 138 | align-items: center; 139 | } 140 | .calamansi-skin--nerio .clmns--section-controls { 141 | font-size: 1.25em; 142 | } 143 | .calamansi-skin--nerio .clmns--section-controls .clmns--control-resume, 144 | .calamansi-skin--nerio .clmns--section-controls .clmns--control-pause 145 | { 146 | font-size: 1.75em; 147 | margin: 0 1em; 148 | } 149 | 150 | .calamansi-skin--nerio .clmns--footer { 151 | min-height: 40px; 152 | height: 6%; 153 | background-color: #131313; 154 | 155 | display: flex; 156 | justify-content: center; 157 | align-items: center; 158 | } 159 | .calamansi-skin--nerio .clmns--footer > div { 160 | flex: 1; 161 | 162 | display: flex; 163 | justify-content: center; 164 | align-items: center; 165 | font-size: 1.25em; 166 | } 167 | 168 | .calamansi-skin--nerio .clmns--custom-checkbox { 169 | position: relative; 170 | } 171 | .calamansi-skin--nerio .clmns--custom-checkbox input { 172 | position: absolute; 173 | 174 | top: 0; 175 | left: 0; 176 | margin: 0; 177 | opacity: 0; 178 | transform: scale(2); 179 | } 180 | .calamansi-skin--nerio .clmns--custom-checkbox input:checked ~ i { 181 | color: #ed4f46; 182 | transition: all .4s ease; 183 | } 184 | 185 | /* 186 | * Show playlist 187 | */ 188 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-header { 189 | display: flex; 190 | margin: 1em; 191 | margin-top: 3em; 192 | } 193 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--track-info--albumCover { 194 | width: 30%; 195 | height: auto; 196 | border-radius: .25em; 197 | flex-shrink: 0; 198 | } 199 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--right { 200 | display: flex; 201 | flex-direction: column; 202 | 203 | overflow: hidden; 204 | flex: 1; 205 | padding-left: 1em; 206 | } 207 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-controls { 208 | text-shadow: none; 209 | } 210 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-track-info { 211 | flex: 1; 212 | text-align: left; 213 | padding: 0; 214 | } 215 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-track-info .clmns--track-info { 216 | white-space: nowrap; 217 | text-overflow: ellipsis; 218 | overflow: hidden; 219 | } 220 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-track-info .clmns--track-info--titleOrFilename { 221 | color: #000000; 222 | } 223 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-playback-controls { 224 | padding-bottom: .25em; 225 | } 226 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-playback-controls .clmns--playback-bar { 227 | padding-top: 0; 228 | padding-bottom: .25em; 229 | } 230 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-playback-controls .clmns--playback-progress::after { 231 | background-color: #131313; 232 | } 233 | 234 | .calamansi-skin--nerio.clmns--show-playlist .clmns--main-section .clmns--content-main { 235 | opacity: 0; 236 | } 237 | .calamansi-skin--nerio.clmns--show-playlist .clmns--main-section .clmns--content-playlist { 238 | transform: translateX(0%); 239 | } 240 | 241 | .calamansi-skin--nerio .clmns--content-playlist { 242 | min-width: 0; 243 | width: 100%; 244 | min-height: 0; 245 | height: 100%; 246 | 247 | position: absolute; 248 | transform: translateX(-110%); 249 | z-index: 1; 250 | transition: all .2s ease-in-out; 251 | 252 | display: flex; 253 | flex-direction: column; 254 | } 255 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist-wrapper { 256 | min-height: 0; 257 | height: 100%; 258 | flex: 1; 259 | padding-bottom: 2em; 260 | } 261 | .calamansi-skin--nerio .clmns--content-playlist .clmns--simplebar-scrollbar::before { 262 | background-color: #ffffff; 263 | } 264 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist { 265 | padding: 1em; 266 | } 267 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist ul { 268 | list-style: none; 269 | } 270 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist .clmns--playlist-item { 271 | display: flex; 272 | padding: .75em; 273 | background-color: #1d1d1d; 274 | margin-bottom: .1em; 275 | font-size: .9em; 276 | } 277 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist .clmns--playlist-item .clmns--playlist-track-info--name { 278 | flex: 1; 279 | white-space: nowrap; 280 | text-overflow: ellipsis; 281 | overflow: hidden; 282 | } 283 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist .clmns--playlist-item .clmns--playlist-track-info--duration { 284 | padding-left: .5em; 285 | color: #8a8a8a; 286 | } 287 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist .clmns--playlist-item.clmns--active .clmns--playlist-track-info--name { 288 | color: #ed4f46; 289 | } 290 | 291 | .calamansi-skin--nerio .clmns--content-playlist .clmns--controls { 292 | padding: 1em; 293 | display: flex; 294 | align-items: center; 295 | 296 | position: absolute; 297 | bottom: 0; 298 | 299 | background: linear-gradient(to bottom, #13131300, #131313dd, #131313); 300 | width: 100%; 301 | } 302 | .calamansi-skin--nerio .clmns--content-playlist .clmns--controls .clmns--control { 303 | font-size: 1em; 304 | } 305 | .calamansi-skin--nerio .clmns--content-playlist .clmns--controls .clmns--control-resume, 306 | .calamansi-skin--nerio .clmns--content-playlist .clmns--controls .clmns--control-pause 307 | { 308 | margin: 0 1em; 309 | } 310 | 311 | 312 | /* 313 | * Responsiveness 314 | */ 315 | @media all and (orientation: landscape) { 316 | .calamansi-skin--nerio { 317 | flex-direction: row; 318 | } 319 | 320 | .calamansi-skin--nerio .clmns--header { 321 | height: 100%; 322 | } 323 | .calamansi-skin--nerio .clmns--header .clmns--section-header { 324 | height: 100%; 325 | } 326 | .calamansi-skin--nerio .clmns--header .clmns--placeholder-svg { 327 | height: 100%; 328 | width: auto; 329 | } 330 | 331 | .calamansi-skin--nerio .clmns--main-section { 332 | min-width: 0; 333 | } 334 | 335 | .calamansi-skin--nerio .clmns--section-track-info .clmns--track-info { 336 | white-space: normal; 337 | overflow: hidden; 338 | text-overflow: ellipsis; 339 | } 340 | 341 | /* .calamansi-skin--nerio .clmns--header { 342 | min-height: 100%; 343 | height: 100%; 344 | width: 50%; 345 | padding-left: 100%; 346 | padding-bottom: 0; 347 | } */ 348 | 349 | /* 350 | * Playlist visible 351 | */ 352 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header { 353 | width: 50%; 354 | display: flex; 355 | flex-direction: column; 356 | } 357 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--section-header { 358 | height: auto; 359 | } 360 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--track-info--albumCover { 361 | height: 100%; 362 | width: auto; 363 | border-radius: .25em; 364 | flex-shrink: 0; 365 | } 366 | 367 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--controls { 368 | flex: 1; 369 | 370 | display: flex; 371 | flex-direction: column; 372 | color: #1d1d1d; 373 | 374 | padding: 1em; 375 | padding-bottom: 0; 376 | } 377 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--controls .clmns--playback-controls { 378 | flex: 1; 379 | display: flex; 380 | justify-content: center; 381 | align-items: center; 382 | } 383 | 384 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--controls .clmns--playback-controls { 385 | font-size: 1.25em; 386 | } 387 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--controls .clmns--playback-controls .clmns--control-resume, 388 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--controls .clmns--playback-controls .clmns--control-pause 389 | { 390 | font-size: 1.75em; 391 | margin: 0 1em; 392 | } 393 | 394 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--controls .clmns--other-controls { 395 | min-height: 40px; 396 | height: 6%; 397 | 398 | display: flex; 399 | justify-content: center; 400 | align-items: center; 401 | } 402 | .calamansi-skin--nerio.clmns--show-playlist .clmns--header .clmns--controls .clmns--other-controls > div { 403 | flex: 1; 404 | 405 | display: flex; 406 | justify-content: center; 407 | align-items: center; 408 | font-size: 1.25em; 409 | } 410 | 411 | .calamansi-skin--nerio .clmns--content-playlist { 412 | position: absolute; 413 | transform: translateX(0%); 414 | transform: translateY(-100%); 415 | } 416 | .calamansi-skin--nerio .clmns--content-playlist .clmns--controls { 417 | display: none; 418 | } 419 | .calamansi-skin--nerio .clmns--content-playlist .clmns--playlist-wrapper { 420 | padding-bottom: 0; 421 | } 422 | } 423 | /* @media all and (min-width: 769px) { 424 | .calamansi-skin--nerio .clmns--header { 425 | width: 50%; 426 | padding-bottom: 50%; 427 | } 428 | } */ 429 | 430 | /* Generated by Glyphter (http://www.clmns--glyphter.com) on Sat Oct 26 2019*/ 431 | @font-face { 432 | font-family: 'calamansi-skin--nerio--glyphter'; 433 | src: url('fonts/Glyphter.eot'); 434 | src: url('fonts/Glyphter.eot?#iefix') format('embedded-opentype'), 435 | url('fonts/Glyphter.woff') format('woff'), 436 | url('fonts/Glyphter.ttf') format('truetype'), 437 | url('fonts/Glyphter.svg#Glyphter') format('svg'); 438 | font-weight: normal; 439 | font-style: normal; 440 | } 441 | .calamansi-skin--nerio .clmns--glyphter:before { 442 | display: inline-block; 443 | font-family: 'calamansi-skin--nerio--glyphter'; 444 | font-style: normal; 445 | font-weight: normal; 446 | line-height: 1; 447 | -webkit-font-smoothing: antialiased; 448 | -moz-osx-font-smoothing: grayscale 449 | } 450 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-playlist:before { 451 | content: '\0041'; 452 | } 453 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-search:before { 454 | content: '\0042'; 455 | } 456 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-play:before { 457 | content: '\0043'; 458 | } 459 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-pause:before { 460 | content: '\0044'; 461 | } 462 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-next:before { 463 | content: '\0045'; 464 | } 465 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-prev:before { 466 | content: '\0046'; 467 | } 468 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-shuffle:before { 469 | content: '\0047'; 470 | } 471 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-loop:before { 472 | content: '\0048'; 473 | } 474 | .calamansi-skin--nerio .clmns--glyphter.clmns--fa-options:before { 475 | content: '\0049'; 476 | } -------------------------------------------------------------------------------- /dist/skins/doorn/fonts/Glyphter.svg: -------------------------------------------------------------------------------- 1 | Generated by Glyphter -------------------------------------------------------------------------------- /dist/skins/calamansi/fonts/Glyphter.svg: -------------------------------------------------------------------------------- 1 | Generated by Glyphter -------------------------------------------------------------------------------- /dist/skins/calamansi-compact/fonts/Glyphter.svg: -------------------------------------------------------------------------------- 1 | Generated by Glyphter -------------------------------------------------------------------------------- /src/calamansi/Calamansi.js: -------------------------------------------------------------------------------- 1 | import CalamansiAudio from './CalamansiAudio'; 2 | import CalamansiSkin from './CalamansiSkin'; 3 | import TrackInfoReader from './services/TrackInfoReader'; 4 | 5 | class Calamansi 6 | { 7 | constructor(el, options = {}) { 8 | /* DATA */ 9 | this._options = Object.assign({ 10 | // Default options... 11 | loop: false, 12 | shuffle: false, 13 | volume: 100, 14 | preloadTrackInfo: false, 15 | loadTrackInfoOnPlay: true, 16 | defaultAlbumCover: '', 17 | soundcloudClientId: '', 18 | }, options); 19 | 20 | // Make sure we have all the required options provided and the values 21 | // are all correct 22 | try { 23 | this._validateOptions(); 24 | } catch (error) { 25 | console.error(`Calamansi intialization error: ${error}`); 26 | 27 | return; 28 | } 29 | 30 | /* STATE */ 31 | this._initialized = false; 32 | 33 | this._trackInfoReader = new TrackInfoReader(this._options.soundcloudClientId); 34 | 35 | this.el = el; 36 | this.id = el.id ? el.id : this._generateUniqueId(); 37 | 38 | this._eventListeners = { 39 | initialized: [], 40 | play: [], 41 | pause: [], 42 | stop: [], 43 | trackEnded: [], 44 | loadeddata: [], 45 | loadedmetadata: [], 46 | canplaythrough: [], 47 | loadingProgress: [], 48 | timeupdate: [], 49 | volumechange: [], 50 | ratechange: [], 51 | playlistLoaded: [], 52 | playlistReordered: [], 53 | playlistSwitched: [], 54 | trackInfoReady: [], 55 | trackSwitched: [], 56 | }; 57 | 58 | this._skin = null; 59 | this.audio = null; 60 | 61 | this._playlists = []; 62 | this._currentPlaylist = null; 63 | this._currentTrack = null; 64 | this._currentPlaylistOrder = []; 65 | 66 | /* INITIALIZE PLAYER INSTANCE */ 67 | this._init(); 68 | } 69 | 70 | /** 71 | * Automatically initialize all the player instances 72 | * 73 | * @param string className = 'calamansi' 74 | */ 75 | static autoload(className = 'calamansi') { 76 | const calamansis = []; 77 | const elements = document.querySelectorAll(`.${className}`); 78 | 79 | // Initialize all the player instances 80 | elements.forEach(el => { 81 | calamansis.push(new Calamansi(el, this._readOptionsFromElement(el))); 82 | }); 83 | 84 | return calamansis; 85 | } 86 | 87 | /** 88 | * Read options from a DOM element for autoloaded instances 89 | * 90 | * @param {*} el 91 | */ 92 | static _readOptionsFromElement(el) { 93 | const options = {}; 94 | 95 | options.skin = el.dataset.skin ? el.dataset.skin : null; 96 | 97 | if (el.dataset.source) { 98 | options.playlists = { 99 | 'default': [{ source: el.dataset.source }] 100 | }; 101 | } 102 | 103 | return options; 104 | } 105 | 106 | _validateOptions() { 107 | if (!this._options.skin) { 108 | throw 'No skin provided.'; 109 | } 110 | } 111 | 112 | async _init() { 113 | // Prepare playlists/audio source, load the first track to play 114 | this._preparePlaylists(); 115 | 116 | // Register internal event listeners 117 | this._registerEventListeners(); 118 | 119 | // Initialize the skin 120 | this._skin = new CalamansiSkin(this, this._options.skin); 121 | await this._skin.init(); 122 | 123 | this.el = document.getElementById(this.id); 124 | 125 | // Initialization done! 126 | this._initialized = true; 127 | 128 | this._emit('initialized', this); 129 | CalamansiEvents._emit('initialized', this); 130 | 131 | // Load the first playlist with at least 1 track 132 | this._loadPlaylist(this.currentPlaylist()); 133 | 134 | if (this.audio) { 135 | this.audio.changeVolume(this._options.volume / 100); 136 | } 137 | } 138 | 139 | _generateUniqueId() { 140 | const id = Math.random().toString(36).substr(2, 5); 141 | 142 | return document.querySelectorAll(`calamansi-${id}`).length > 0 143 | ? this._generateUniqueId() 144 | : `calamansi-${id}`; 145 | } 146 | 147 | /** 148 | * Read playlist information from the provided options, select the first 149 | * playlist and track to be played 150 | */ 151 | _preparePlaylists() { 152 | if (this._options.playlists && Object.keys(this._options.playlists).length > 0) { 153 | let playlistIndex = 0; 154 | 155 | for (let name in this._options.playlists) { 156 | let playlist = { 157 | name: name, 158 | list: [] 159 | }; 160 | 161 | if (!Array.isArray(this._options.playlists[name])) { 162 | continue; 163 | } 164 | 165 | for (let track of this._options.playlists[name]) { 166 | if (!track.source) { 167 | continue; 168 | } 169 | 170 | track.info = track.info ? track.info : {}; 171 | track.info.url = track.source; 172 | track.info.filename = this._getTrackFilename(track); 173 | track.info.name = track.info.title 174 | ? track.info.title 175 | : track.info.filename; 176 | track.info.titleOrFilename = track.info.title 177 | ? track.info.title 178 | : track.info.filename; 179 | track.info.artistOrFilename = track.info.artist 180 | ? track.info.artist 181 | : track.info.filename; 182 | track.sourceType = this._getTrackSourceType(track); 183 | 184 | playlist.list.push(track); 185 | 186 | // Load track info 187 | if (this._options.preloadTrackInfo) { 188 | this._loadTrackInfo(track); 189 | } 190 | 191 | // Set the first playlist with at least 1 track as the 192 | // current playlist 193 | if (this._currentPlaylist === null) { 194 | this._currentPlaylist = playlistIndex; 195 | } 196 | } 197 | 198 | this._playlists.push(playlist); 199 | 200 | playlistIndex++; 201 | } 202 | 203 | // If no tracks were found - set the first playlist as the current 204 | if (this._currentPlaylist === null) { 205 | this._currentPlaylist = 0; 206 | } 207 | } 208 | } 209 | 210 | _loadPlaylist(playlist) { 211 | if (!playlist) { 212 | return; 213 | } 214 | 215 | if (this._options.shuffle) { 216 | this._shuffleCurrentPlaylist(false); 217 | } else { 218 | this._unshuffleCurrentPlaylist(false); 219 | } 220 | 221 | this.switchTrack(0); 222 | 223 | this._emit('playlistLoaded', this); 224 | CalamansiEvents._emit('playlistLoaded', this); 225 | } 226 | 227 | /** 228 | * Switch to a playlist by index 229 | * 230 | * @param int index 231 | */ 232 | switchPlaylist(index) { 233 | this._currentPlaylist = index; 234 | 235 | this._emit('playlistSwitched', this); 236 | CalamansiEvents._emit('playlistSwitched', this); 237 | 238 | // Load the first track to play 239 | this._loadPlaylist(this.currentPlaylist()); 240 | } 241 | 242 | _loadTrack(track) { 243 | if (!this.audio) { 244 | this.audio = new CalamansiAudio(this, track.source); 245 | 246 | if (this._options.loadTrackInfoOnPlay) { 247 | this._loadTrackInfo(track); 248 | } 249 | 250 | return; 251 | } 252 | 253 | this.audio.load(track.source); 254 | 255 | if (this._options.loadTrackInfoOnPlay) { 256 | this._loadTrackInfo(track); 257 | } 258 | } 259 | 260 | /** 261 | * Switch to a track by index 262 | * 263 | * @param int index 264 | * @param boolean startPlaying = false 265 | */ 266 | switchTrack(index, startPlaying = false) { 267 | this._currentTrack = index; 268 | 269 | this._emit('trackSwitched', this); 270 | CalamansiEvents._emit('trackSwitched', this); 271 | 272 | // Load the first track to play 273 | this._loadTrack(this.currentTrack()); 274 | 275 | if (startPlaying) { 276 | this.audio.play(); 277 | } 278 | } 279 | 280 | _getTrackFilename(track) { 281 | if (track.source.startsWith('https://api.soundcloud.com')) { 282 | return track.source; 283 | } 284 | 285 | return track.source.split('/').pop(); 286 | } 287 | 288 | _getTrackSourceType(track) { 289 | if (track.source.startsWith('https://api.soundcloud.com')) { 290 | return 'soundcloud'; 291 | } 292 | 293 | return track.info.filename.split('.').pop(); 294 | } 295 | 296 | _loadTrackInfo(track) { 297 | if (track.info._loaded === true) { 298 | return; 299 | } 300 | 301 | this._trackInfoReader.read(track) 302 | .then(trackInfo => { 303 | if (!trackInfo._loaded) { 304 | return; 305 | } 306 | 307 | track.info = Object.assign(track.info, trackInfo); 308 | 309 | this._emit('trackInfoReady', this, track); 310 | CalamansiEvents._emit('trackInfoReady', this); 311 | }); 312 | } 313 | 314 | /** 315 | * Get the current playlist 316 | */ 317 | currentPlaylist() { 318 | return this._playlists[this._currentPlaylist]; 319 | } 320 | 321 | /** 322 | * Get the current track 323 | */ 324 | currentTrack() { 325 | return this.currentPlaylist() 326 | ? this.currentPlaylist().list[this._currentPlaylistOrder[this._currentTrack]] 327 | : null; 328 | } 329 | 330 | /** 331 | * Switch to the next track 332 | */ 333 | nextTrack() { 334 | if (this._currentTrack + 1 < this.currentPlaylist().list.length) { 335 | this.switchTrack(this._currentTrack + 1, true); 336 | 337 | return true; 338 | } else { 339 | if (this._options.loop) { 340 | this.switchTrack(0, true); 341 | 342 | return true; 343 | } 344 | } 345 | 346 | return false; 347 | } 348 | 349 | /** 350 | * Switch to the previous track 351 | */ 352 | prevTrack() { 353 | if (this._currentTrack - 1 >= 0) { 354 | this.switchTrack(this._currentTrack - 1, true); 355 | 356 | return true; 357 | } else { 358 | if (this._options.loop) { 359 | this.switchTrack(this.currentPlaylist().list.length - 1, true); 360 | 361 | return true; 362 | } 363 | } 364 | 365 | return false; 366 | } 367 | 368 | /** 369 | * Toggle playlist loop 370 | */ 371 | toggleLoop() { 372 | this._options.loop = ! this._options.loop; 373 | } 374 | 375 | /** 376 | * Toggle playlist shuffle 377 | */ 378 | toggleShuffle() { 379 | this._options.shuffle = ! this._options.shuffle; 380 | 381 | if (this._options.shuffle) { 382 | this._shuffleCurrentPlaylist(); 383 | } else { 384 | this._unshuffleCurrentPlaylist(); 385 | } 386 | } 387 | 388 | _unshuffleCurrentPlaylist(_emitEvent = true) { 389 | this._currentTrack = this._currentPlaylistOrder[this._currentTrack]; 390 | 391 | this._currentPlaylistOrder = Object.keys(this.currentPlaylist().list) 392 | .map(i => parseInt(i)); 393 | 394 | if (_emitEvent) { 395 | this._emit('playlistReordered', this); 396 | CalamansiEvents._emit('playlistReordered', this); 397 | } 398 | } 399 | 400 | _shuffleCurrentPlaylist(_emitEvent = true) { 401 | if (this.currentPlaylist().list.length > 1) { 402 | this._currentPlaylistOrder = []; 403 | 404 | while (this._currentPlaylistOrder.length < this.currentPlaylist().list.length) { 405 | let order = Math.floor(Math.random() * (this.currentPlaylist().list.length)); 406 | 407 | if (this._currentPlaylistOrder.indexOf(order) > -1) { 408 | continue; 409 | } 410 | 411 | this._currentPlaylistOrder.push(order); 412 | } 413 | 414 | this._currentTrack = this._currentPlaylistOrder.indexOf(this._currentTrack); 415 | } else { 416 | this._currentPlaylistOrder = [0]; 417 | } 418 | 419 | if (_emitEvent) { 420 | this._emit('playlistReordered', this); 421 | CalamansiEvents._emit('playlistReordered', this); 422 | } 423 | } 424 | 425 | /** 426 | * Destroy the player instance 427 | */ 428 | destroy() { 429 | this.audio.unload(); 430 | this._skin.destroy(); 431 | } 432 | 433 | /** 434 | * Register an event listener (subscribe to an event) 435 | * 436 | * @param string|array events 437 | * @param function callback 438 | */ 439 | on(events, callback) { 440 | if (typeof events === 'string') { 441 | events = [events]; 442 | } 443 | 444 | for (let event of events) { 445 | // Ignore inexisting event types 446 | if (!this._eventListeners[event]) { 447 | continue; 448 | } 449 | 450 | this._eventListeners[event].push(callback); 451 | } 452 | } 453 | 454 | /** 455 | * _Emit an event. Call all the event listeners' callbacks. 456 | * 457 | * @param {*} event 458 | * @param {*} data 459 | * @param {*} data 460 | */ 461 | _emit(event, instance, data = {}) { 462 | // Sometimes the player initialization might fail 463 | if (!this._initialized) { 464 | return; 465 | } 466 | 467 | // Ignore inexisting event types 468 | if (!this._eventListeners[event]) { 469 | return; 470 | } 471 | 472 | for (let callback of this._eventListeners[event]) { 473 | callback(instance, data); 474 | } 475 | 476 | // DOM elements visibility can be dependent on events 477 | document.querySelectorAll(`#${this._skin.el.id} .clmns--hide-on-${event}`).forEach(el => { 478 | if (el.style.display == 'none') { 479 | return; 480 | } 481 | 482 | el.dataset.display = el.style.display ? el.style.display : 'inline'; 483 | el.style.display = 'none'; 484 | }); 485 | 486 | document.querySelectorAll(`#${this._skin.el.id} .clmns--show-on-${event}`).forEach(el => { 487 | el.style.display = el.dataset.display; 488 | }); 489 | } 490 | 491 | _registerEventListeners() { 492 | CalamansiEvents.on('play', (instance) => { 493 | // Pause all players when one of the players on the page has started 494 | // playing 495 | if (instance.id != this.id) { 496 | if (this.audio) { 497 | this.audio.pause(); 498 | } 499 | } 500 | }); 501 | 502 | this.on('trackEnded', (instance) => { 503 | if (!this.nextTrack()) { 504 | this._emit('stop'); 505 | CalamansiEvents._emit('stop', this); 506 | } 507 | }) 508 | } 509 | } 510 | 511 | export default Calamansi; --------------------------------------------------------------------------------