├── 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 |
15 |
16 |
17 |
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 |
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 |
7 |
8 |
13 |
14 |
15 |
0:00
16 |
17 |
22 |
23 |
0:00
24 |
25 |
26 |
43 |
44 |
--------------------------------------------------------------------------------
/dist/skins/in-text/fonts/Glyphter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
10 |
11 |
-00:00
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
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 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
120 |
121 |
--------------------------------------------------------------------------------
/dist/skins/dummy/skin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
47 |
48 |
49 |
52 |
53 |
56 |
57 |
58 |
59 | 00:00 / 00:00 (-00:00)
60 |
61 |
62 |
66 |
67 |
70 |
71 |
72 | Playback Speed:
73 |
82 |
83 |
84 |
85 |
Playlists:
86 |
87 |
88 |
89 |
91 |
104 |
105 |
106 |
107 |
108 |
109 | |
110 | # |
111 | Title |
112 | Artist |
113 | Album |
114 | Year |
115 | Length |
116 | Download |
117 | Custom Field |
118 | Custom Field 2 |
119 |
120 |
121 |
122 |
123 |
124 | |
125 | |
126 | |
127 | |
128 | |
129 | |
130 | |
131 |
132 | Download
133 | |
134 | |
135 |
136 | BUY
137 | |
138 |
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/dist/skins/calamansi/skin.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
00:00
11 |
12 |
16 |
17 |
-00:00
18 |
19 |
20 |
24 |
25 |
26 |
32 |
33 |
34 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
112 |
113 |
114 |
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 |
75 |
76 |
77 |
78 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
119 |
120 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/dist/skins/calamansi/fonts/Glyphter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dist/skins/calamansi-compact/fonts/Glyphter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/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;
--------------------------------------------------------------------------------