├── .eslintignore ├── .gitignore ├── img ├── cat.png ├── icon.ico ├── icon.png ├── favicon.png ├── musicicon.png ├── electron │ ├── pause.png │ ├── play.png │ ├── next-song.png │ ├── previous-song.png │ ├── close.svg │ ├── minimize.svg │ ├── maximize.svg │ └── restore.svg ├── play.svg ├── stop.svg ├── expand.svg ├── volume_mute.svg ├── pause.svg ├── previous-song.svg ├── next-song.svg ├── fast_forward.svg ├── fast_rewind.svg ├── volume_down.svg ├── info.svg ├── shuffle.svg ├── loop.svg ├── clock.svg ├── volume_up.svg ├── fullscreen.svg ├── fullscreen-exit.svg ├── menu.svg ├── cross.svg ├── settings.svg ├── icon.svg └── equalizer.svg ├── design ├── icon.sketch └── fullscreen-icons.sketch ├── app-resources └── mac │ ├── icon.icns │ └── Info.plist ├── screenshots ├── video-windows.png ├── audio-compact-mac.png ├── audio-default-mac.png └── audio-compact-light-playlist-mac.png ├── installer ├── windows │ └── create-installer.js └── setup-events.js ├── css ├── sidebar.css ├── style.css ├── header.css ├── playlist.css ├── main_content.css └── controls.css ├── LICENSE.md ├── package.json ├── README.md ├── js ├── SettingsOverlay.js ├── SettingsStore.js ├── utils.js ├── ElectronApp.js ├── Equalizer.js ├── Playlist.js └── MediaPlayer.js ├── index.html ├── main.js ├── .eslintrc.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/**/* 2 | media-player-*/**/* 3 | *.min.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | media-player-* 4 | dist/ -------------------------------------------------------------------------------- /img/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/cat.png -------------------------------------------------------------------------------- /img/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/icon.ico -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/icon.png -------------------------------------------------------------------------------- /img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/favicon.png -------------------------------------------------------------------------------- /img/musicicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/musicicon.png -------------------------------------------------------------------------------- /design/icon.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/design/icon.sketch -------------------------------------------------------------------------------- /img/electron/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/electron/pause.png -------------------------------------------------------------------------------- /img/electron/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/electron/play.png -------------------------------------------------------------------------------- /app-resources/mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/app-resources/mac/icon.icns -------------------------------------------------------------------------------- /img/electron/next-song.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/electron/next-song.png -------------------------------------------------------------------------------- /design/fullscreen-icons.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/design/fullscreen-icons.sketch -------------------------------------------------------------------------------- /img/electron/previous-song.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/img/electron/previous-song.png -------------------------------------------------------------------------------- /screenshots/video-windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/screenshots/video-windows.png -------------------------------------------------------------------------------- /screenshots/audio-compact-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/screenshots/audio-compact-mac.png -------------------------------------------------------------------------------- /screenshots/audio-default-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/screenshots/audio-default-mac.png -------------------------------------------------------------------------------- /screenshots/audio-compact-light-playlist-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nt1m/media-player/HEAD/screenshots/audio-compact-light-playlist-mac.png -------------------------------------------------------------------------------- /img/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/stop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/expand.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/volume_mute.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/previous-song.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/next-song.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/fast_forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/fast_rewind.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/volume_down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/shuffle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/loop.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/volume_up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /img/fullscreen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /img/fullscreen-exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 copy 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /img/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /img/electron/close.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /img/electron/minimize.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /img/electron/maximize.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /img/electron/restore.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /img/cross.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /img/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /installer/windows/create-installer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint-env node */ 4 | const createWindowsInstaller = require("electron-winstaller").createWindowsInstaller; 5 | const path = require("path"); 6 | 7 | getInstallerConfig().then(createWindowsInstaller).catch((error) => { 8 | console.error(error.message || error); 9 | process.exit(1); 10 | }); 11 | 12 | function getInstallerConfig() { 13 | console.log("Creating windows installer"); 14 | const rootPath = path.join("./"); 15 | const outPath = path.join(rootPath, "dist"); 16 | 17 | return Promise.resolve({ 18 | appDirectory: path.join(outPath, "Media\ Player-win32-x64"), 19 | authors: "Tim Nguyen", 20 | noMsi: true, 21 | outputDirectory: path.join(outPath, "windows-installer"), 22 | iconUrl: "https://raw.githubusercontent.com/nt1m/media-player/gh-pages/img/icon.ico", 23 | exe: "Media Player.exe", 24 | setupExe: "MediaPlayerInstaller.exe", 25 | setupIcon: path.join(rootPath, "img/icon.ico"), 26 | skipUpdateIcon: true 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /css/sidebar.css: -------------------------------------------------------------------------------- 1 | #sidebar { 2 | display: flex; 3 | flex-direction: column; 4 | max-height: calc(100vh + -65px); 5 | z-index: 2000; 6 | width: 250px; 7 | overflow: auto; 8 | background-color: #303030; 9 | } 10 | .video + #sidebar { 11 | transition: all .5s; 12 | margin-right: -250px; 13 | 14 | } 15 | .video:hover + #sidebar,.video + #sidebar:hover { 16 | transition-delay: .8s; 17 | margin-right: 0; 18 | } 19 | 20 | .light #sidebar { 21 | background-color: #111; 22 | } 23 | #sidebar.drag { 24 | box-sizing: border-box; 25 | outline: 2px dashed rgba(255,255,255,0.7); 26 | outline-offset: -3px; 27 | } 28 | #sidebar.drag > #upload-container { 29 | display: none; 30 | } 31 | #sidebar.no-drag { 32 | box-sizing: unset; 33 | outline: none; 34 | } 35 | #sidebar.no-drag > #upload-container { 36 | display: inherit; 37 | } 38 | #sidebar-header { 39 | background-color: #222; 40 | padding: 10px; 41 | -webkit-app-region: drag; 42 | background-repeat: no-repeat; 43 | background-position: right 10px center; 44 | } 45 | .light #sidebar-header { 46 | background-color: #1b1b1b; 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Tim Nguyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /img/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "media-player", 3 | "productName": "Media Player", 4 | "version": "1.0.0", 5 | "description": "An modern, clean media player built using HTML 5 technologies.", 6 | "main": "main.js", 7 | "scripts": { 8 | "start": "./node_modules/.bin/electron .", 9 | "pack": "./node_modules/.bin/electron-packager . \"Media Player\" --icon=img/icon.png --out=dist/ --overwrite", 10 | "pack-win-installer": "node installer/windows/create-installer.js", 11 | "pack-win": "./node_modules/.bin/electron-packager . \"Media Player\" --icon=img/icon.ico --out=dist/ --platform=win32 --arch=ia32,x64 --overwrite --win32metadata.CompanyName=\"Media Player team\" --win32metadata.ProductName=\"Media Player\" --win32metadata.InternalName=media-player --win32metadata.LegalCopyright=\"Media Player team\" --win32metadata.FileDescription=\"Media Player\" --win32metadata.OriginalFilename=\"Media Player\"", 12 | "pack-osx": "./node_modules/.bin/electron-packager . \"Media Player\" --icon=app-resources/mac/icon.icns --out=dist/ --platform=darwin --arch=x64 --extend-info=app-resources/mac/Info.plist --overwrite", 13 | "pack-linux": "./node_modules/.bin/electron-packager . \"Media Player\" --icon=img/icon.png --out=dist/ --platform=linux --arch=ia32,x64 --overwrite", 14 | "pack-all": "npm run pack-osx && npm run pack-linux && npm run pack-win" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/nt1m/media-player.git" 19 | }, 20 | "keywords": [ 21 | "Media", 22 | "player", 23 | "html5", 24 | "music", 25 | "video" 26 | ], 27 | "author": "Tim Nguyen", 28 | "contributors": [ 29 | "Mohamed Hadjoudj", 30 | "Daniell Mesquita" 31 | ], 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/nt1m/media-player/issues" 35 | }, 36 | "homepage": "https://github.com/nt1m/media-player#readme", 37 | "devDependencies": { 38 | "electron": "^22.0.0", 39 | "electron-packager": "^17.1.0", 40 | "electron-winstaller": "^2.5.2", 41 | "id3js": "^1.1.3" 42 | }, 43 | "dependencies": { 44 | "jsmediatags": "^3.4.0", 45 | "mime-types": "^2.1.15" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Media player 2 | 3 | To try this project, go to: http://nt1m.github.io/media-player 4 | 5 | Optimized for Firefox and Chrome. 6 | 7 | ## Authors 8 | - Maintainers: Mohamed Hadjoudj (@MohIceAge), Tim Nguyen (@nt1m) 9 | - Contributors: Daniell Mesquita (@Plasmmer) 10 | 11 | ## Resources 12 | - Audio visualizer (modified): [HTML5 Audio Visualizer](https://github.com/Wayou/HTML5_Audio_Visualizer) 13 | - Icons: [Material Design Icons](https://github.com/google/material-design-icons) 14 | 15 | ## Technologies used 16 | - Web Audio API (visualizer) 17 | - HTML `` tag 18 | - CSS variables 19 | - Canvas 20 | 21 | ## Contributing 22 | Pull requests ! Feel free to skim through [our list of issues](https://github.com/nt1m/media-player/issues) to see you can contribute [:)](http://i.imgur.com/Bq7Gq5W.png?raw=true ":)") 23 | 24 | # Run as standalone app 25 | In addition to being a web app, you can also run the Media Player as a standalone app using the Electron runtime. 26 | 27 | To get started, you'll need NodeJS, Npm and optionally git. 28 | 29 | ### Install the Prerequisites 30 | Download the project (by downloading the zipped project or by typing `git clone https://github.com/nt1m/media-player`) 31 | then run the following command in the root directory. 32 | ``` 33 | npm i 34 | ``` 35 | 36 | ### Run the app 37 | 38 | If nothing fails then you can run the app by typing: 39 | ``` 40 | npm start 41 | ``` 42 | 43 | ### Bundling the app: 44 | 45 | The bundles will appear in `dist/`. 46 | 47 | * Current platform only: `npm run pack` 48 | * Windows (both 32-bit and 64-bit): `npm run pack-win` 49 | * macOS 64-bit: `npm run pack-osx` 50 | * Linux (both 32-bit and 64-bit): `npm run pack-linux` 51 | * For all 3 platforms: `npm run pack-all` 52 | 53 | ### Screenshots 54 | 55 | Thank you for reading this readme! Here are some screenshots of the standalone app as a reward: 56 | 57 | 58 | 59 |  |  60 | :---------------------------------------------------:|:-----------------------------------------------------: 61 | 62 | 63 | -------------------------------------------------------------------------------- /installer/setup-events.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const electron = require("electron"); 4 | const app = electron.app; 5 | 6 | /* eslint-env node */ 7 | module.exports = { 8 | handleSquirrelEvent() { 9 | if (process.argv.length === 1) { 10 | return false; 11 | } 12 | 13 | const ChildProcess = require("child_process"); 14 | const path = require("path"); 15 | 16 | const appFolder = path.resolve(process.execPath, ".."); 17 | const rootAtomFolder = path.resolve(appFolder, ".."); 18 | const updateDotExe = path.resolve(path.join(rootAtomFolder, "Update.exe")); 19 | const exeName = path.basename(process.execPath); 20 | const spawn = function(command, args) { 21 | let spawnedProcess; 22 | 23 | try { 24 | spawnedProcess = ChildProcess.spawn(command, args, {detached: true}); 25 | } catch (error) { 26 | // Do nothing 27 | } 28 | 29 | return spawnedProcess; 30 | }; 31 | 32 | const spawnUpdate = function(args) { 33 | return spawn(updateDotExe, args); 34 | }; 35 | 36 | const squirrelEvent = process.argv[1]; 37 | switch (squirrelEvent) { 38 | case "--squirrel-install": 39 | case "--squirrel-updated": 40 | // Optionally do things such as: 41 | // - Add your .exe to the PATH 42 | // - Write to the registry for things like file associations and 43 | // explorer context menus 44 | 45 | // Install desktop and start menu shortcuts 46 | spawnUpdate(["--createShortcut", exeName]); 47 | 48 | setTimeout(app.quit, 1000); 49 | return true; 50 | 51 | case "--squirrel-uninstall": 52 | // Undo anything you did in the --squirrel-install and 53 | // --squirrel-updated handlers 54 | 55 | // Remove desktop and start menu shortcuts 56 | spawnUpdate(["--removeShortcut", exeName]); 57 | 58 | setTimeout(app.quit, 1000); 59 | return true; 60 | 61 | case "--squirrel-obsolete": 62 | // This is called on the outgoing version of your app before 63 | // we update to the new version - it's the opposite of 64 | // --squirrel-updated 65 | 66 | app.quit(); 67 | return true; 68 | } 69 | return null; 70 | } 71 | }; 72 | -------------------------------------------------------------------------------- /js/SettingsOverlay.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function SettingsOverlay(params) { 4 | this.store = params.store; 5 | this.element = params.element; 6 | this.toggle = params.toggle; 7 | 8 | this.toggle.addEventListener("click", () => { 9 | let isHidden = this.element.classList.contains("hidden"); 10 | this.element.classList.toggle("hidden", !isHidden); 11 | this.toggle.classList.toggle("checked", isHidden); 12 | }); 13 | 14 | Element("header", { 15 | class: "header", 16 | content: "Settings", 17 | parent: this.element 18 | }); 19 | 20 | this.settingsContainer = Element("div", { 21 | parent: this.element 22 | }); 23 | this.store.definitions.filter((s) => !s.hidden).forEach((s) => this.createSetting(s)); 24 | } 25 | 26 | SettingsOverlay.prototype = { 27 | get hidden() { 28 | return this.element.hidden; 29 | }, 30 | set hidden(value) { 31 | this.element.hidden = value; 32 | this.toggle.classList.toggle("checked", !value); 33 | }, 34 | createSetting(setting) { 35 | var element = Element("p", { 36 | class: "setting", 37 | parent: this.settingsContainer 38 | }); 39 | 40 | Element("span", { 41 | class: "label", 42 | parent: element, 43 | content: setting.name 44 | }); 45 | 46 | var store = this.store; 47 | if (setting.values) { 48 | var select = Element("select", { 49 | parent: element, 50 | onchange() { 51 | store.setItem(setting.id, this.value); 52 | } 53 | }); 54 | setting.values.forEach((value) => { 55 | Element("option", { 56 | parent: select, 57 | value, 58 | content: value 59 | }); 60 | }); 61 | 62 | select.value = store.getItem(setting.id); 63 | } else { 64 | var input = Element("input", { 65 | parent: element, 66 | type: setting.type, 67 | }); 68 | 69 | if (input.type == "color") { 70 | input.oninput = function() { 71 | store.setItem(setting.id, this.value); 72 | }; 73 | input.value = store.getItem(setting.id); 74 | } else { 75 | input.onchange = function() { 76 | store.setItem(setting.id, this.checked); 77 | }; 78 | input.checked = store.getItem(setting.id); 79 | } 80 | } 81 | 82 | return element; 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /img/equalizer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Artboard 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Montserrat:400,700); 2 | 3 | /* General styles */ 4 | [hidden] { 5 | display: none !important; 6 | } 7 | html { 8 | --theme-highlight-color: #0087ff; 9 | } 10 | html, body { 11 | height: 100%; 12 | } 13 | body { 14 | margin: 0; 15 | padding: 0; 16 | display: flex; 17 | flex-direction: column; 18 | background-color: rgba(20, 20, 20, 0.95); 19 | color: white; 20 | font-family: Montserrat, Roboto, Arial, sans-serif; 21 | user-select: none; 22 | -ms-user-select: none; 23 | -moz-user-select: none; 24 | -webkit-user-select: none; 25 | overflow: hidden; 26 | } 27 | 28 | :fullscreen .light { 29 | background: transparent; 30 | } 31 | :-webkit-full-screen .light { 32 | background: transparent; 33 | } 34 | 35 | .light { 36 | filter: invert(1); 37 | background-color: rgba(244, 244, 244, 0.9); 38 | } 39 | 40 | * { 41 | outline: none; 42 | } 43 | 44 | /* Make everything responsive */ 45 | @media (max-width: 560px) { 46 | #volume-control:not(:hover) { 47 | width: 50px; 48 | } 49 | #volume-control:not(:hover) #volume-range { 50 | transform: scale(0) 51 | } 52 | #volume-control:hover + #main-controls { 53 | flex: 0; 54 | opacity: 0; 55 | } 56 | #sidebar { 57 | max-width: 200px; 58 | } 59 | } 60 | 61 | @media (max-width: 800px) { 62 | .header { 63 | font-size: 1em !important; 64 | } 65 | } 66 | @media (max-width: 650px) { 67 | body .media-control { 68 | padding: 0; 69 | width: 45px; 70 | } 71 | body .media-control::after { 72 | transform: scale(0.8); 73 | } 74 | } 75 | @media (max-width: 500px) { 76 | .header { 77 | font-size: .9em !important; 78 | } 79 | .main-content { 80 | flex-direction: column; 81 | } 82 | #sidebar { 83 | max-width: none; 84 | width: 100% !important; 85 | transition: max-height 0.3s; 86 | } 87 | #sidebar:not(:hover) { 88 | max-height: 38px; 89 | overflow: hidden; 90 | } 91 | .osx #sidebar-header { 92 | text-align: center; 93 | } 94 | #sidebar-header { 95 | font-size: .9em !important; 96 | border-top: 1px solid rgba(255,255,255,0.05) !important; 97 | } 98 | #sidebar:not(:hover) #sidebar-header { 99 | background-image: url(../img/expand.svg); 100 | } 101 | #volume-control:not(:hover) { 102 | width: 50px; 103 | } 104 | #volume-icon { 105 | margin: 0 5px; 106 | } 107 | } 108 | 109 | @media (max-width: 410px) { 110 | #secondary-controls { 111 | width: 60px; 112 | } 113 | #secondary-controls .media-control { 114 | display: inline-block; 115 | height: 30px; 116 | width: 30px; 117 | } 118 | #secondary-controls .media-control::after { 119 | width: 20px; 120 | height: 20px; 121 | } 122 | #speed-menu, 123 | #speed-menu::after{ 124 | left: auto !important; 125 | right: 16px; 126 | } 127 | } -------------------------------------------------------------------------------- /css/header.css: -------------------------------------------------------------------------------- 1 | /* Header */ 2 | .header { 3 | padding: 15px; 4 | font-size: 20px; 5 | position: sticky; 6 | top: 0; 7 | white-space: nowrap; 8 | z-index: 999; 9 | } 10 | 11 | #settings-overlay .header, 12 | #header { 13 | overflow: hidden; 14 | -webkit-mask-image: linear-gradient(to left, transparent 0, black 75px); 15 | mask-image: linear-gradient(to left, transparent 0, black 75px); 16 | } 17 | 18 | #header { 19 | flex: 1; 20 | } 21 | 22 | #header-container { 23 | transition: font-size 0.5s, opacity 0.2s; 24 | padding-right: 0 !important; 25 | display: flex; 26 | } 27 | 28 | .video #header-container { 29 | background-image: linear-gradient(rgba(20, 20, 20, 0.95), transparent); 30 | } 31 | 32 | .video:not(:hover) #header-container { 33 | opacity: 0; 34 | transition-delay: 0s, .7s; 35 | } 36 | 37 | .electron.osx .header { 38 | text-align: center; 39 | } 40 | 41 | .electron #header-container { 42 | padding: 0; 43 | -webkit-mask-image: none; 44 | mask-image: none; 45 | } 46 | 47 | .electron #header, 48 | .electron #settings-overlay .header { 49 | min-width: 0; 50 | padding: 15px; 51 | -webkit-app-region: drag; 52 | } 53 | 54 | .electron.osx #header, 55 | .electron.osx #settings-overlay .header { 56 | padding: 15px 90px; 57 | } 58 | 59 | .electron.osx #header { 60 | padding-right: 40px; 61 | } 62 | 63 | .electron #header:empty::before { 64 | content: " "; 65 | width: 1em; 66 | height: 1em; 67 | display: inline-block; 68 | } 69 | 70 | .electron .caption-buttons { 71 | display: flex; 72 | height: 54px; 73 | padding: 0; 74 | } 75 | 76 | .caption-button { 77 | background: none; 78 | border: none; 79 | margin: 0; 80 | flex: 1; 81 | padding: 0 5px; 82 | } 83 | 84 | .caption-button:hover::before { 85 | background-color: rgba(0,0,0,0.1); 86 | } 87 | 88 | .caption-button.close:hover::before{ 89 | background-color: #15ced9; 90 | } 91 | 92 | .light .caption-button.close:hover::before { 93 | filter: none; 94 | } 95 | 96 | .caption-button::before { 97 | content: ""; 98 | display: inline-block; 99 | width: 12px; 100 | height: 12px; 101 | padding: 10px; 102 | filter: invert(1); 103 | border-radius: 100%; 104 | background-repeat: no-repeat; 105 | background-position: center; 106 | } 107 | 108 | .caption-button.fullscreen { 109 | flex: 0; 110 | } 111 | 112 | :root:not(.electron) .caption-button.fullscreen, 113 | .electron.osx .caption-button.fullscreen { 114 | min-width: 50px; 115 | text-align: center; 116 | } 117 | 118 | :root:not(.electron) .caption-button.fullscreen::before, 119 | .electron.osx .caption-button.fullscreen::before { 120 | transform: scale(1.25); 121 | } 122 | 123 | .caption-button.close::before { 124 | background-image: url(../img/electron/close.svg); 125 | } 126 | 127 | .caption-button.maximize::before { 128 | background-image: url(../img/electron/maximize.svg); 129 | } 130 | 131 | .caption-button.restore::before { 132 | background-image: url(../img/electron/restore.svg); 133 | } 134 | 135 | .caption-button.minimize::before { 136 | background-image: url(../img/electron/minimize.svg); 137 | } 138 | 139 | .caption-button.fullscreen::before { 140 | background-image: url(../img/fullscreen.svg); 141 | } 142 | .fullscreen .caption-button.fullscreen::before { 143 | background-image: url(../img/fullscreen-exit.svg); 144 | } -------------------------------------------------------------------------------- /js/SettingsStore.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function SettingsStore(params) { 4 | this.isElectron = params.isElectron; 5 | this.getItem = this.getItem.bind(this); 6 | this.setItem = this.setItem.bind(this); 7 | for (let definition of this.definitions) { 8 | if (!this.getItem(definition.id)) { 9 | this.setItem(definition.id, definition.default); 10 | } else { 11 | definition.onApply(this.getItem(definition.id)); 12 | } 13 | } 14 | } 15 | 16 | SettingsStore.prototype = { 17 | get definitions() { 18 | return [{ 19 | id: "volume", 20 | name: "Volume", 21 | hidden: true, 22 | default: 0.5, 23 | onApply: (value) => { 24 | value = new Number(value); 25 | 26 | if (isNaN(value)) { 27 | return this.setItem("volume", 0.5); 28 | } 29 | 30 | return MediaPlayer.changeVolume(value); 31 | } 32 | }, 33 | { 34 | id: "theme", 35 | name: "Theme", 36 | default: "dark", 37 | values: ["light", "dark"], 38 | onApply(value) { 39 | document.body.classList.toggle("light", value === "light"); 40 | } 41 | }, 42 | { 43 | id: "theme-highlight-color", 44 | name: "Highlight color", 45 | default: "#0087ff", 46 | type: "color", 47 | onApply(value) { 48 | function toRgb(color) { 49 | let r = "0x" + color[1] + color[2]; 50 | let g = "0x" + color[3] + color[4]; 51 | let b = "0x" + color[5] + color[6]; 52 | return [r, g, b]; 53 | } 54 | 55 | function getLuminance([r, g, b]) { 56 | let distanceB = 0, distanceW = 0; 57 | distanceB = 0.2 * r + g * 0.7 + b * 0.1; 58 | distanceW = (255 - r) * 0.2 + (255 - g) * 0.7 + (255 - b) * 0.1; 59 | return distanceB > distanceW ? 1 : 0; 60 | } 61 | 62 | let contrastColor = getLuminance(toRgb(value)) === 1 ? "#000" : "#fff"; 63 | document.documentElement.style.setProperty("--theme-highlight-color", value); 64 | document.documentElement.style.setProperty( 65 | "--theme-contrast-color", contrastColor 66 | ); 67 | document.documentElement.classList.toggle( 68 | "contrast-black", contrastColor == "#000"); 69 | } 70 | }, 71 | { 72 | id: "electron.always-on-top", 73 | name: "Pin window on top of other windows", 74 | default: false, 75 | hidden: !this.isElectron, 76 | type: "checkbox", 77 | onApply: (value) => { 78 | if (typeof value !== "boolean") { 79 | value = false; 80 | } 81 | if (this.isElectron) { 82 | require("electron").remote.BrowserWindow.getFocusedWindow().setAlwaysOnTop(value); 83 | } 84 | }, 85 | }]; 86 | }, 87 | 88 | getItem(id) { 89 | let value = localStorage.getItem("settings." + id); 90 | 91 | try { 92 | value = JSON.parse(value); 93 | } catch (e) { 94 | // Do nothing 95 | } 96 | return value; 97 | }, 98 | 99 | setItem(id, value) { 100 | var setting = this._lookupById(id); 101 | setting.onApply(value); 102 | return localStorage.setItem("settings." + id, value); 103 | }, 104 | 105 | _lookupById(id) { 106 | var i = this.definitions.findIndex(s => s.id === id); 107 | return this.definitions[i]; 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Media Player 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Playlist 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Speed : 70 | 2 71 | 1.5 72 | 1.25 73 | 1 74 | 0.75 75 | 0.5 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /js/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | /* global getMp3Tag */ 5 | var Utils = { 6 | readTag(media) { 7 | const prediction = this.predictTagFromName(media.name); 8 | return [prediction, new Promise(resolve => { 9 | if (media.type.match("audio") == "audio") { 10 | getMp3Tag(media) 11 | .then(tag => { 12 | resolve({ 13 | title: tag.title || prediction.title, 14 | artist: tag.artist || prediction.artist, 15 | album: tag.album, 16 | pic: tag.picture && window.URL.createObjectURL(tag.picture), 17 | }); 18 | }); 19 | } else { 20 | resolve(this.getVideoTag(media)); 21 | } 22 | })]; 23 | }, 24 | getVideoTag(vid) { 25 | return { 26 | title: vid.name 27 | }; 28 | }, 29 | predictTagFromName(name) { 30 | if (!name) { 31 | return {}; 32 | } 33 | var tag = {}; 34 | name = this.removeFileExtension(name); 35 | var splitName = name.split("-"); 36 | if (splitName.length > 1) { 37 | tag.artist = trim(splitName[0]); 38 | 39 | splitName.shift(); 40 | tag.title = trim(splitName.join("")); 41 | } else { 42 | tag.title = trim(name); 43 | } 44 | tag.title = this.sanitizeCommonKeywords(tag.title); 45 | 46 | var ftData = this.extractFeaturing(tag.title); 47 | if (ftData[1]) { 48 | tag.title = ftData[0]; 49 | tag.artist += ", " + ftData[1]; 50 | } 51 | 52 | return tag; 53 | }, 54 | 55 | removeFileExtension(name) { 56 | var t = name.split("."); 57 | return t.slice(0, t.length - 1).join("."); 58 | }, 59 | 60 | extractFeaturing(title) { 61 | var ftKeywords = ["feat", "feat.", "ft.", "featuring"]; 62 | var splitTitle = title.toLowerCase().split(" "); 63 | var i = splitTitle.findIndex(w => ftKeywords.indexOf(w) > -1); 64 | if (i > -1) { 65 | return title.split(splitTitle[i]).map(trim); 66 | } 67 | return [title, null]; 68 | }, 69 | 70 | sanitizeCommonKeywords(name) { 71 | var keywordRegex = /[\(\[]([\s\w]+)?(official|music|audio|video|edit|tour|lyric|lyrics)([\s\w]+)?[\)\]]/ig; 72 | name = name.replace(/_/g, " "); 73 | return name.replace(keywordRegex, ""); 74 | }, 75 | 76 | getTooltipForTag(tag) { 77 | let tooltip = tag.title; 78 | if (tag.artist) { 79 | tooltip = `${tag.artist} - ${tooltip}`; 80 | } 81 | if (tag.album) { 82 | tooltip += `\nAlbum: ${tag.album}`; 83 | } 84 | if (tag.genre) { 85 | tooltip += `\nGenre: ${tag.genre}`; 86 | } 87 | return tooltip; 88 | }, 89 | 90 | convertSecondsToDisplay(time) { 91 | var hours = Math.floor(time / 3600); 92 | time = time - hours * 3600; 93 | var minutes = Math.floor(time / 60); 94 | var seconds = Math.floor(time - minutes * 60); 95 | return {hours, minutes, seconds}; 96 | } 97 | }; 98 | 99 | function trim(str) { 100 | return str.trim().replace(/\s+/ig, " ").replace(/\(\s?\)/g, ""); 101 | } 102 | function Element(tagName, attributes) { 103 | var element = document.createElement(tagName); 104 | for (var attr in attributes) { 105 | if (attr == "style" || attr == "css") { 106 | element.style = attributes[attr]; 107 | continue; 108 | } 109 | if (attr == "content") { 110 | element.innerHTML = attributes.content; 111 | continue; 112 | } 113 | if (attr.startsWith("on")) { 114 | element.addEventListener(attr.replace("on", "").toLowerCase(), attributes[attr]); 115 | continue; 116 | } 117 | if (attr == "parent") { 118 | attributes.parent.appendChild(element); 119 | continue; 120 | } 121 | element.setAttribute(attr, attributes[attr]); 122 | } 123 | return element; 124 | } 125 | 126 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* eslint no-undef:0 */ 4 | const { app, BrowserWindow, nativeImage, ipcMain } = require("electron"); 5 | const path = require("path"); 6 | const url = require("url"); 7 | const fs = require("fs"); 8 | 9 | const setupEvents = require("./installer/setup-events"); 10 | if (setupEvents.handleSquirrelEvent()) { 11 | // squirrel event handled and app will exit in 1000ms, so don't do anything else 12 | return; 13 | } 14 | 15 | // Setup file handlers 16 | var osxFile; 17 | app.on("open-file", (event, filePath) => { 18 | if (win && win.webContents && !win.webContents.isLoading()) { 19 | win.webContents.send("file-found", filePath); 20 | } else { 21 | osxFile = filePath; 22 | } 23 | }); 24 | 25 | let win; 26 | 27 | let image = createNativeImage("./img/icon.png"); 28 | if (app.dock) { 29 | app.dock.setIcon(image); 30 | } 31 | 32 | function createNativeImage(relativePath) { 33 | return nativeImage.createFromPath(path.join(__dirname, relativePath)); 34 | } 35 | function setThumbarState(state) { 36 | if (!win) { 37 | return; 38 | } 39 | 40 | if (state == "ended") { 41 | win.setThumbarButtons([]); 42 | return; 43 | } 44 | 45 | let buttons = []; 46 | buttons.push({ 47 | tooltip: "Previous song", 48 | icon: path.join(__dirname, "img", "electron", "previous-song.png"), 49 | click() { 50 | win.webContents.send("request-video-action", "previous-song"); 51 | }, 52 | }); 53 | 54 | if (state == "play") { 55 | buttons.push({ 56 | tooltip: "Pause", 57 | icon: path.join(__dirname, "img", "electron", "pause.png"), 58 | click() { 59 | win.webContents.send("request-video-action", "pause"); 60 | } 61 | }); 62 | } else { 63 | buttons.push({ 64 | tooltip: "Play", 65 | icon: path.join(__dirname, "img", "electron", "play.png"), 66 | click() { 67 | win.webContents.send("request-video-action", "play"); 68 | }, 69 | }); 70 | } 71 | 72 | buttons.push({ 73 | tooltip: "Next song", 74 | icon: path.join(__dirname, "img", "electron", "next-song.png"), 75 | click() { 76 | win.webContents.send("request-video-action", "next-song"); 77 | }, 78 | }); 79 | 80 | win.setThumbarButtons(buttons); 81 | } 82 | 83 | function onMediaStateChange(event, state) { 84 | if (app.dock) { 85 | let badge; 86 | switch (state) { 87 | case "pause": 88 | badge = "⏸"; 89 | break; 90 | case "play": 91 | badge = "▶"; 92 | break; 93 | case "ended": 94 | badge = ""; 95 | break; 96 | } 97 | app.dock.setBadge(badge); 98 | } 99 | if (win.setThumbarButtons) { 100 | setThumbarState(state); 101 | } 102 | } 103 | 104 | function createWindow() { 105 | win = new BrowserWindow({ 106 | width: 800, 107 | height: 600, 108 | minWidth: 290, 109 | minHeight: 290, 110 | icon: __dirname + "/img/icon.png", 111 | title: "Media Player", 112 | hasShadow: true, 113 | frame: false, 114 | center: true, 115 | titleBarStyle: "hidden-inset" 116 | }); 117 | win.setMenu(null); 118 | // win.toggleDevTools(); 119 | win.loadURL(url.format({ 120 | pathname: path.join(__dirname, "./index.html"), 121 | protocol: "file:", 122 | slashes: true, 123 | })); 124 | 125 | win.webContents.once("did-stop-loading", () => { 126 | if (process.platform == "win32") { 127 | let fileDesc = fs.lstatSync(process.argv[process.argv.length - 1]); 128 | if (fileDesc.isFile()) { 129 | var openedFilePath = process.argv[process.argv.length - 1]; 130 | win.webContents.send("file-found", openedFilePath); 131 | } 132 | } else if (osxFile) { 133 | win.webContents.send("file-found", osxFile); 134 | } 135 | ipcMain.on("add-recent-file", (_, mediaPath) => app.addRecentDocument(mediaPath)); 136 | ipcMain.on("media-state-change", onMediaStateChange); 137 | }); 138 | 139 | // Close handler 140 | win.on("closed", () => { 141 | win = null; 142 | ipcMain.removeAllListeners("add-recent-file"); 143 | ipcMain.removeAllListeners("media-state-change"); 144 | 145 | if (app.dock) { 146 | app.dock.setBadge(""); 147 | } 148 | }); 149 | } 150 | 151 | app.on("ready", createWindow); 152 | 153 | app.on("window-all-closed", () => app.quit()); 154 | 155 | app.on("activate", () => (win === null) ? createWindow() : 0); 156 | 157 | -------------------------------------------------------------------------------- /css/playlist.css: -------------------------------------------------------------------------------- 1 | #playlist { 2 | list-style: none; 3 | margin: 0; 4 | padding: 0; 5 | width: 100%; 6 | flex: 1; 7 | min-width: none; 8 | text-align: center; 9 | overflow: auto; 10 | position: relative; 11 | min-height: 250px; 12 | } 13 | #playlist::before { 14 | display: inline-block; 15 | font-size: 12px; 16 | height: 100%; 17 | padding: 10px; 18 | box-sizing: border-box; 19 | white-space: pre-wrap; 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | right: 0; 24 | bottom: 0; 25 | color: #fff; 26 | z-index: 99; 27 | } 28 | #playlist:empty::before { 29 | content: "Your playlist is empty…"; 30 | } 31 | #playlist.loading::before { 32 | content: "Loading…"; 33 | } 34 | #playlist.loading > li { 35 | opacity: 0.4; 36 | } 37 | #playlist > li { 38 | border-bottom: 1px solid rgba(255,255,255,0.08); 39 | margin: 0; 40 | padding: 0; 41 | } 42 | #playlist > li a { 43 | color: inherit; 44 | text-decoration: none; 45 | padding: 10px; 46 | padding-left: 0; 47 | margin: 0; 48 | width: 100%; 49 | box-sizing: border-box; 50 | text-align: left; 51 | cursor: pointer; 52 | display: flex; 53 | align-items: center; 54 | } 55 | 56 | #playlist > li.playing a { 57 | background-color: var(--theme-highlight-color); 58 | color: var(--theme-contrast-color); 59 | } 60 | 61 | #playlist > li:not(.playing):hover a, 62 | #playlist > li:not(.playing) a:focus { 63 | background-color: rgba(255,255,255,0.1); 64 | } 65 | #playlist > li.playing a:focus .cover::before { 66 | border: 1px solid var(--theme-highlight-color); 67 | } 68 | #playlist > li a::before { 69 | content: ""; 70 | width: 16px; 71 | height: 16px; 72 | margin: 0 .5em; 73 | display: inline-block; 74 | border-radius: 50%; 75 | z-index: 1; 76 | background-size: cover; 77 | } 78 | #playlist > li.playing a::before { 79 | background-image: url(../img/play.svg); 80 | background-color: rgba(255, 255, 255, 0.5); 81 | } 82 | #playlist > li p { 83 | margin: 0; 84 | display: block; 85 | position: relative; 86 | flex: 1; 87 | white-space: nowrap; 88 | overflow: hidden; 89 | font-size: 14px; 90 | -webkit-mask-image: linear-gradient(to left, transparent 0, black 20px); 91 | mask-image: linear-gradient(to left, transparent 0, black 20px); 92 | } 93 | #playlist > li p .artist { 94 | display: block; 95 | font-size: 12px; 96 | opacity: 0.5; 97 | } 98 | #playlist > li .cross { 99 | background-color: transparent; 100 | border: none; 101 | width: 16px; 102 | height: 16px; 103 | background-image:url("../img/cross.svg"); 104 | background-size: 10px; 105 | background-position: center; 106 | background-repeat: no-repeat; 107 | opacity: 0.88; 108 | margin-left: 2px; 109 | border-radius: 2px; 110 | -webkit-filter: invert(1); 111 | filter: invert(1); 112 | } 113 | .contrast-black #playlist .playing .cross { 114 | -webkit-filter: none; 115 | filter: none; 116 | } 117 | #playlist .playing .cross:focus, 118 | #playlist .playing .cross:hover { 119 | -webkit-filter: invert(1); 120 | filter: invert(1); 121 | background-color: #ddd; 122 | } 123 | #playlist .cross:focus, 124 | #playlist .cross:hover { 125 | opacity: 1; 126 | background-color: #15ced9; 127 | -webkit-filter: none; 128 | filter: none; 129 | } 130 | 131 | /* File upload input */ 132 | #upload-container { 133 | position: relative; 134 | cursor: pointer; 135 | } 136 | #upload-container div { 137 | padding: 10px; 138 | height: 2em; 139 | margin: 10px 0; 140 | border-radius: 2px; 141 | display: flex; 142 | width: 100%; 143 | box-sizing: border-box; 144 | } 145 | #upload-container div::before { 146 | flex: 1; 147 | align-self: center; 148 | content: "Add music or videos"; 149 | text-transform: uppercase; 150 | text-align: center; 151 | font-size: 0.9em; 152 | z-index: -1; 153 | background-color: var(--theme-highlight-color); 154 | color: var(--theme-contrast-color); 155 | border-radius: 2px; 156 | padding-top: 0.4em; 157 | height: 2em; 158 | box-sizing: border-box; 159 | } 160 | #upfile:hover + div::before { 161 | background-image: linear-gradient(rgba(255,255,255,0.2),transparent); 162 | } 163 | #upfile:hover:active + div::before { 164 | background-image: linear-gradient(rgba(0,0,0,0.3), rgba(0,0,0,0.3)); 165 | } 166 | #upfile:focus + div::before { 167 | box-shadow: 0 0 0 4px rgba(255,255,255,0.15); 168 | } 169 | #upfile { 170 | cursor: pointer; 171 | position: absolute; 172 | top: 0; 173 | left: 0; 174 | right: 0; 175 | bottom: 0; 176 | width: 100%; 177 | height: 100%; 178 | opacity: 0; 179 | z-index: 99; 180 | } 181 | -------------------------------------------------------------------------------- /css/main_content.css: -------------------------------------------------------------------------------- 1 | .main-content { 2 | flex: 1; 3 | min-width: none; 4 | display: flex; 5 | } 6 | #cover { 7 | position: absolute; 8 | margin: auto; 9 | left: 0;right: 0;top: 0;bottom: 0; 10 | transition: filter .2s; 11 | filter: blur(5px) grayscale(50%); 12 | z-index: -1; 13 | max-width: 80%; 14 | max-height: 80%; 15 | } 16 | #cover[src=""] { 17 | display: none; 18 | } 19 | #content-area:hover > #cover{ 20 | filter: blur(0) grayscale(0); 21 | } 22 | #content-area:hover > #cover:not([src=""]) + #visualizer { 23 | opacity: 0.2; 24 | } 25 | #display-container { 26 | flex: 1; 27 | min-width: 0; 28 | position: relative; 29 | min-height: 0; 30 | flex-direction: column; 31 | display: flex; 32 | } 33 | 34 | #content-area { 35 | height: 100%; 36 | flex: 1; 37 | position: relative; 38 | } 39 | 40 | .video #content-area { 41 | margin-top: -54px; 42 | } 43 | 44 | .video #control-overlay { 45 | top: 54px; 46 | } 47 | 48 | #MediaPlayer { 49 | height: 100%; 50 | width: 100%; 51 | position: absolute; 52 | } 53 | 54 | .light #MediaPlayer { 55 | filter: invert(1); 56 | } 57 | 58 | #control-overlay { 59 | position: absolute; 60 | top: 0; 61 | left: 0; 62 | right: 0; 63 | bottom: 0; 64 | z-index: 1999; 65 | } 66 | 67 | #control-overlay::before { 68 | content: ""; 69 | position: absolute; 70 | margin: auto; 71 | top: 0; 72 | bottom: 0; 73 | left: 0; 74 | right: 0; 75 | width: 100px; 76 | height: 100px; 77 | display: block; 78 | border-radius: 100%; 79 | background-color: rgba(255,255,255,0.6); 80 | filter: invert(1); 81 | background-size: 50%; 82 | background-position: center; 83 | background-repeat: no-repeat; 84 | transform: scale(0); 85 | } 86 | 87 | #control-overlay.paused::before, 88 | #control-overlay.playing::before { 89 | animation: pulse 1s; 90 | } 91 | 92 | #control-overlay.paused::before { 93 | background-image: url(../img/pause.svg); 94 | } 95 | 96 | #control-overlay.playing::before { 97 | background-image: url(../img/play.svg); 98 | } 99 | 100 | @keyframes pulse { 101 | 0% { 102 | transform: scale(1); 103 | opacity: 1; 104 | } 105 | 100% { 106 | transform: scale(1); 107 | opacity: 0; 108 | } 109 | } 110 | 111 | /* Canvas with visualizer */ 112 | #visualizer { 113 | display: block; 114 | margin: auto; 115 | position: absolute; 116 | top: 0; 117 | left: 0; 118 | right: 0; 119 | bottom: 0; 120 | image-rendering: -webkit-optimize-contrast; 121 | image-rendering: crisp-edges; 122 | max-width: 90%; 123 | transition: opacity .2s; 124 | } 125 | #visualizer.placeholder { 126 | background: url(../img/musicicon.png) no-repeat center; 127 | background-size: 50%; 128 | fill: var(--theme-highlight-color); 129 | filter: url('data:image/svg+xml,#a'); 130 | } 131 | @supports ((-webkit-mask: url(../img/pause.svg)) or (mask-image: url(../img/pause.svg))) { 132 | #visualizer.placeholder { 133 | filter: none; 134 | background-image: none; 135 | -webkit-mask-image: url(../img/musicicon.png); 136 | -webkit-mask-repeat: no-repeat; 137 | -webkit-mask-position: center; 138 | 139 | mask-image: url(../img/musicicon.png); 140 | mask-repeat: no-repeat; 141 | mask-position: center; 142 | 143 | background-color: var(--theme-highlight-color); 144 | } 145 | } 146 | 147 | /* Settings */ 148 | #settings-overlay { 149 | background: rgba(20, 20, 20, 0.9); 150 | top: 0; 151 | bottom: 64px; 152 | left: 0; 153 | right: 0; 154 | position: fixed; 155 | z-index: 2000; 156 | transition-property: transform, opacity; 157 | transition-duration: .5s; 158 | } 159 | 160 | #settings-overlay.hidden { 161 | -webkit-transform: translateY(100vh) scaleY(0); 162 | transform: translateY(100vh) scaleY(0); 163 | opacity: 0; 164 | pointer-events: none; 165 | visibility: hidden; 166 | max-height: 0; 167 | transition-property: transform, opacity, visibility; 168 | } 169 | 170 | #settings-overlay > div { 171 | padding: 15px; 172 | } 173 | 174 | .setting { 175 | max-width: 400px; 176 | } 177 | .setting input, .setting select { 178 | float: right; 179 | } 180 | .setting input:focus, .setting select:focus { 181 | box-shadow: 0 0 0 5px rgba(255,255,255,0.1); 182 | } 183 | 184 | input[type=checkbox] { 185 | -webkit-appearance: none; 186 | -moz-appearance: none; 187 | width: 15px; 188 | height: 15px; 189 | border: white 1px solid; 190 | } 191 | input[type=checkbox]:checked { 192 | background-color: white; 193 | } 194 | 195 | -------------------------------------------------------------------------------- /js/ElectronApp.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const {webFrame, remote, ipcRenderer} = require("electron"); 4 | const fs = require("fs"); 5 | let MimeTypeUtils = require("mime-types"); 6 | module.exports = { 7 | init() { 8 | webFrame.setZoomLevelLimits(1, 1); 9 | document.documentElement.classList.add("electron"); 10 | 11 | let isMac = navigator.platform.includes("Mac"); 12 | document.documentElement.classList.toggle("osx", isMac); 13 | if (!isMac) { 14 | this.initWindowControls(); 15 | } 16 | 17 | ipcRenderer.on("request-video-action", this.handleVideoAction); 18 | ipcRenderer.on("file-found", (event, path) => { 19 | MediaPlayer.playlist.element.classList.add("loading"); 20 | 21 | this.handleFileFound(path).then(() => { 22 | MediaPlayer.playlist.element.classList.remove("loading"); 23 | }).catch((e) => { 24 | console.error("FS handler - Failed to load media:" + e); 25 | MediaPlayer.playlist.element.classList.remove("loading"); 26 | }); 27 | }); 28 | 29 | MediaPlayer.videoEl.addEventListener("playing", this.notifyVideoStateChange); 30 | MediaPlayer.videoEl.addEventListener("pause", this.notifyVideoStateChange); 31 | MediaPlayer.videoEl.addEventListener("ended", this.notifyVideoStateChange); 32 | MediaPlayer.videoEl.addEventListener("emptied", this.notifyVideoStateChange); 33 | 34 | // Debug 35 | document.addEventListener("keydown", function(e) { 36 | if (e.which === 123) { 37 | remote.getCurrentWindow().toggleDevTools(); 38 | } else if (e.which === 116) { 39 | location.reload(); 40 | } 41 | }); 42 | }, 43 | 44 | initWindowControls() { 45 | let header = Element("div", { 46 | class: "caption-buttons", 47 | parent: document.getElementById("header-container") 48 | }); 49 | 50 | Element("button", { 51 | class: "caption-button minimize", 52 | parent: header, 53 | onclick() { 54 | remote.BrowserWindow.getFocusedWindow().minimize(); 55 | } 56 | }); 57 | 58 | let maximizeBtn = Element("button", { 59 | class: "caption-button maximize", 60 | parent: header, 61 | onclick() { 62 | let focusedWindow = remote.BrowserWindow.getFocusedWindow(); 63 | if (focusedWindow.isMaximized()) { 64 | focusedWindow.unmaximize(); 65 | } else { 66 | focusedWindow.maximize(); 67 | } 68 | } 69 | }); 70 | 71 | Element("button", { 72 | class: "caption-button close", 73 | parent: header, 74 | onclick() { 75 | window.close(); 76 | } 77 | }); 78 | 79 | let focusedWindow = remote.BrowserWindow.getFocusedWindow(); 80 | focusedWindow.on("unmaximize", () => { 81 | maximizeBtn.className = "caption-button maximize"; 82 | }); 83 | focusedWindow.on("maximize", () => { 84 | maximizeBtn.className = "caption-button restore"; 85 | }); 86 | }, 87 | 88 | handleVideoAction(event, action) { 89 | switch (action) { 90 | case "play": 91 | MediaPlayer.videoEl.play(); 92 | break; 93 | case "pause": 94 | MediaPlayer.videoEl.pause(); 95 | break; 96 | case "previous-song": 97 | MediaPlayer.killContext(); 98 | MediaPlayer.playlist.selectPrevious(); 99 | break; 100 | case "next-song": 101 | MediaPlayer.killContext(); 102 | MediaPlayer.playlist.selectNext(); 103 | break; 104 | } 105 | }, 106 | 107 | handleFileFound(path) { 108 | return new Promise((resolve, reject) => { 109 | fs.readFile(path, null, (err, file) => { 110 | if (err) { 111 | return reject(err); 112 | } 113 | try { 114 | let name = this.getFileNameFromPath(path); 115 | let type = MimeTypeUtils.lookup(name); 116 | if (!type) { 117 | return reject("No type"); 118 | } 119 | let blob = new Blob([file.buffer], {type}); 120 | blob.name = name; 121 | ipcRenderer.send("add-recent-file", path); 122 | return MediaPlayer.playlist.add(blob); 123 | } catch (e) { 124 | reject(e); 125 | alert("Could not read audio/video file"); 126 | } 127 | return reject("Unknown error"); 128 | }); 129 | }); 130 | }, 131 | 132 | notifyVideoStateChange() { 133 | if (MediaPlayer.videoEl.ended || isNaN(MediaPlayer.videoEl.duration)) { 134 | ipcRenderer.send("media-state-change", "ended"); 135 | } else if (MediaPlayer.videoEl.paused) { 136 | ipcRenderer.send("media-state-change", "pause"); 137 | } else { 138 | ipcRenderer.send("media-state-change", "play"); 139 | } 140 | }, 141 | 142 | getFileNameFromPath(filePath) { 143 | let fileName = filePath.split("/").pop(); 144 | if (fileName == filePath) { 145 | fileName = filePath.split("\\").pop(); 146 | } 147 | return fileName; 148 | } 149 | }; 150 | -------------------------------------------------------------------------------- /js/Equalizer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const EQUALIZER_PRESETS = [ 4 | { 5 | name: "Acoustic", 6 | data: [5, 5, 4, 1, 2, 2, 3, 4, 3, 2], 7 | }, { 8 | name: "Bass Booster", 9 | data: [6, 5, 4, 3, 2, 0, 0, 0, 0, 0], 10 | }, { 11 | name: "Bass Reducer", 12 | data: [-6, -5, -4, -3, -2, 0, 0, 0, 0, 0], 13 | }, { 14 | name: "Classical", 15 | data: [5, 4, 3, 2, -1, -1, 0, 1, 3, 4] 16 | }, { 17 | name: "Dance", 18 | data: [4, 6, 5, 0, 2, 3, 5, 4, 3, 0], 19 | }, { 20 | name: "Deep", 21 | data: [5, 3, 2, 1, 3, 2, 1, -2, -4, -5] 22 | }, { 23 | name: "Electronic", 24 | data: [4, 4, 1, 0, -2, 2, 1, 2, 4, 5] 25 | }, { 26 | name: "Hip-Hop", 27 | data: [5, 3, 1, 3, -1, -1, 1, -1, 2, 3], 28 | }, { 29 | name: "Jazz", 30 | data: [4, 3, 1, 2, -1, -1, 0, 1, 3, 4], 31 | }, { 32 | name: "Latin", 33 | data: [5, 3, 0, 0, -1, -1, -1, 0, 3, 5] 34 | }, { 35 | name: "Loudness", 36 | data: [6, 4, 0, 0, -2, 0, -1, -5, 4, 1], 37 | }, { 38 | name: "Lounge", 39 | data: [-3, -2, -1, 1, 4, 3, 0, -1, 2, 1], 40 | }, { 41 | name: "Piano", 42 | data: [3, 2, 0, 2, 3, 1, 3, 5, 3, 4], 43 | }, { 44 | name: "Pop", 45 | data: [-2, -1, 0, 2, 4, 4, 2, 0, -1, -2], 46 | }, { 47 | name: "R&B", 48 | data: [2, 7, 6, 1, -2, -1, 2, 3, 3, 4], 49 | }, { 50 | name: "Rock", 51 | data: [5, 4, 3, 2, -1, -2, 0, 2, 3, 4], 52 | }, { 53 | name: "Small Speakers", 54 | data: [5, 4, 3, 2, 1, 0, -2, -3, -4, -5], 55 | }, { 56 | name: "Spoken Word", 57 | data: [-4, -1, 0, 1, 3, 5, 5, 4, 2, 0], 58 | }, { 59 | name: "Treble Booster", 60 | data: [0, 0, 0, 0, 0, 1, 2, 3, 4, 5], 61 | }, { 62 | name: "Treble Reducer", 63 | data: [0, 0, 0, 0, 0, -1, -2, -3, -4, -5], 64 | }, { 65 | name: "Vocal Booster", 66 | data: [-1, -3, -3, 1, 4, 4, 3, 1, 0, -1] 67 | } 68 | ]; 69 | 70 | const FREQUENCIES = [32, 64, 125, 250, 500, 1000, 2000, 4000, 8000, 16000]; 71 | 72 | function Equalizer(props) { 73 | this.props = props; 74 | this.frequencyMap = new Map(); 75 | this.init(); 76 | } 77 | 78 | Equalizer.prototype = { 79 | init() { 80 | let { panel, toggle, audioCtx, destinationNode } = this.props; 81 | toggle.onclick = () => toggle.classList.toggle("checked"); 82 | 83 | this.sumNode = audioCtx.createGain(); 84 | this.sumNode.connect(destinationNode); 85 | 86 | let select = this.createPresetsSelect(); 87 | 88 | this.container = Element("div", { parent: panel, class: "container" }); 89 | this.createScale(); 90 | for (let frequency of FREQUENCIES) { 91 | let audioNode = this.initNodeForFrequency(frequency); 92 | let div = Element("div", { 93 | class: "range-with-label", 94 | parent: this.container, 95 | }); 96 | let input = Element("input", { 97 | type: "range", 98 | parent: div, 99 | min: -20, 100 | max: 20, 101 | value: 0, 102 | orient: "vertical", 103 | onchange: () => { 104 | select.value = "no-preset"; 105 | this.setFrequencyGain(frequency, input.value); 106 | } 107 | }); 108 | 109 | Element("label", { 110 | content: frequency.toString().replace("000", "k"), 111 | parent: div, 112 | }); 113 | this.frequencyMap.set(frequency, {input, audioNode}); 114 | } 115 | }, 116 | 117 | createScale() { 118 | let scale = Element("div", { 119 | parent: this.container, 120 | class: "scale", 121 | }); 122 | 123 | Element("label", { 124 | content: "+20dB", 125 | parent: scale 126 | }); 127 | 128 | Element("div", { 129 | class: "flex", 130 | parent: scale 131 | }); 132 | 133 | Element("label", { 134 | content: "0dB", 135 | parent: scale, 136 | }); 137 | 138 | Element("div", { 139 | class: "flex", 140 | parent: scale 141 | }); 142 | 143 | Element("label", { 144 | content: "-20dB", 145 | parent: scale, 146 | }); 147 | }, 148 | 149 | createPresetsSelect() { 150 | let select = Element("select", { 151 | parent: this.props.panel, 152 | onchange: () => { 153 | if (select.value == "no-preset") { 154 | this.setPresetData([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); 155 | return; 156 | } 157 | this.setPresetData(EQUALIZER_PRESETS[select.value].data); 158 | } 159 | }); 160 | 161 | Element("option", { 162 | content: "No preset", 163 | value: "no-preset", 164 | parent: select, 165 | }); 166 | for (let i = 0; i < EQUALIZER_PRESETS.length; i++) { 167 | let preset = EQUALIZER_PRESETS[i]; 168 | Element("option", { 169 | content: preset.name, 170 | parent: select, 171 | value: i, 172 | }); 173 | } 174 | return select; 175 | }, 176 | 177 | setPresetData(data) { 178 | let inputs = [...document.querySelectorAll(".range-with-label input")]; 179 | for (let i = 0; i < inputs.length; i++) { 180 | let input = inputs[i]; 181 | input.value = data[i]; 182 | this.setFrequencyGain(FREQUENCIES[i], data[i]); 183 | } 184 | }, 185 | 186 | initNodeForFrequency(frequency) { 187 | let { audioCtx, departureNode } = this.props; 188 | 189 | let biquad = audioCtx.createBiquadFilter(); 190 | biquad.type = "peaking"; 191 | biquad.frequency.value = frequency; 192 | 193 | departureNode.connect(biquad); 194 | biquad.connect(this.sumNode); 195 | return biquad; 196 | }, 197 | setFrequencyGain(frequency, gain) { 198 | let { audioNode } = this.frequencyMap.get(frequency); 199 | audioNode.gain.value = gain; 200 | audioNode.Q.value = 0.01; 201 | }, 202 | }; 203 | -------------------------------------------------------------------------------- /app-resources/mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleExecutable 6 | Media Player 7 | CFBundleIconFile 8 | icon.icns 9 | CFBundleIdentifier 10 | com.timnguyen.mediaplayer 11 | CFBundleInfoDictionaryVersion 12 | 0.1 13 | CFBundleName 14 | Media Player 15 | CFBundlePackageType 16 | APPL 17 | CFBundleDevelopmentRegion 18 | English 19 | CFBundleShortVersionString 20 | 0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 0 25 | LSApplicationCategoryType 26 | public.app-category.video 27 | LSMinimumSystemVersion 28 | 10.8 29 | NSAppleScriptEnabled 30 | YES 31 | NSMainNibFile 32 | MainMenu 33 | NSSupportsAutomaticGraphicsSwitching 34 | 35 | SUScheduledCheckInterval 36 | 3600 37 | CFBundleDocumentTypes 38 | 39 | 40 | UTTypeConformsTo 41 | 42 | public.audio 43 | 44 | UTTypeDescription 45 | AIFF file 46 | UTTypeIdentifier 47 | org.videolan.aiff 48 | UTTypeTagSpecification 49 | 50 | public.filename-extension 51 | 52 | aiff 53 | aif 54 | 55 | 56 | 57 | 58 | CFBundleTypeExtensions 59 | 60 | mp3 61 | 62 | CFBundleTypeIconFile 63 | audio.icns 64 | CFBundleTypeName 65 | MPEG Audio Layer 3 66 | CFBundleTypeRole 67 | Viewer 68 | 69 | 70 | CFBundleTypeExtensions 71 | 72 | mp4 73 | mpeg4 74 | m4v 75 | 76 | CFBundleTypeIconFile 77 | m4v.icns 78 | CFBundleTypeName 79 | MPEG-4 File 80 | CFBundleTypeRole 81 | Viewer 82 | 83 | 84 | CFBundleTypeExtensions 85 | 86 | opus 87 | 88 | CFBundleTypeIconFile 89 | ogg.icns 90 | CFBundleTypeMIMETypes 91 | 92 | audio/opus 93 | audio/ogg; codecs=opus 94 | 95 | CFBundleTypeName 96 | OPUS file 97 | CFBundleTypeRole 98 | Viewer 99 | 100 | 101 | CFBundleTypeExtensions 102 | 103 | ogm 104 | 105 | CFBundleTypeIconFile 106 | movie.icns 107 | CFBundleTypeName 108 | Ogg MPEG-4 Video File 109 | CFBundleTypeRole 110 | Viewer 111 | 112 | 113 | CFBundleTypeExtensions 114 | 115 | ogg 116 | 117 | CFBundleTypeIconFile 118 | ogg.icns 119 | CFBundleTypeMIMETypes 120 | 121 | audio/ogg 122 | 123 | CFBundleTypeName 124 | Ogg Vorbis File 125 | CFBundleTypeRole 126 | Viewer 127 | 128 | 129 | CFBundleTypeExtensions 130 | 131 | oga 132 | 133 | CFBundleTypeIconFile 134 | ogg.icns 135 | CFBundleTypeMIMETypes 136 | 137 | audio/ogg 138 | 139 | CFBundleTypeName 140 | Ogg Audio File 141 | CFBundleTypeRole 142 | Viewer 143 | 144 | 145 | CFBundleTypeExtensions 146 | 147 | ogv 148 | 149 | CFBundleTypeIconFile 150 | movie.icns 151 | CFBundleTypeMIMETypes 152 | 153 | video/ogg 154 | 155 | CFBundleTypeName 156 | Ogg Video File 157 | CFBundleTypeRole 158 | Viewer 159 | 160 | 161 | CFBundleTypeExtensions 162 | 163 | wav 164 | 165 | CFBundleTypeIconFile 166 | wav.icns 167 | CFBundleTypeName 168 | WAVE Audio File 169 | CFBundleTypeRole 170 | Viewer 171 | 172 | 173 | CFBundleTypeExtensions 174 | 175 | webm 176 | 177 | CFBundleTypeIconFile 178 | mkv.icns 179 | CFBundleTypeName 180 | WebM Video File 181 | CFBundleTypeRole 182 | Viewer 183 | 184 | 185 | CFBundleTypeExtensions 186 | 187 | flac 188 | 189 | CFBundleTypeIconFile 190 | audio.icns 191 | CFBundleTypeMIMETypes 192 | 193 | audio/flac 194 | 195 | CFBundleTypeName 196 | FLAC Audio File 197 | CFBundleTypeRole 198 | Viewer 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /js/Playlist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function hash(audio) { 4 | const h = [audio.name, audio.lastModified, audio.type, audio.size].map(e=>e+"").join('//'); 5 | if (!h) { 6 | return Date.now(); 7 | } 8 | return encodeURIComponent(h.replace(/\s/g, "")); 9 | } 10 | 11 | 12 | /* 13 | Constructor 14 | @param Object params 15 | - Function@Promise onBeforeAdded 16 | - HTMLElement element 17 | */ 18 | function Playlist(params) { 19 | this.element = params.element; 20 | this.coverEl = document.querySelector("img#cover"); 21 | this.shuffle = false; 22 | this.loop = true; 23 | this.element.scrollTo = function(y, t) { 24 | t = t > 0 ? Math.floor(t / 4) : 40; 25 | let step = (y - this.scrollTop) / t * 40; 26 | let that = this; 27 | function aux() { 28 | t -= 40; 29 | that.scrollTop += step; 30 | if (t > 0) { 31 | setTimeout(aux, 40); 32 | } else { 33 | that.scrollTop = y; 34 | } 35 | } 36 | aux(); 37 | }; 38 | this.params = params; 39 | 40 | this.onItemSelected = this.onItemSelected.bind(this); 41 | this.onItemRemoved = this.onItemRemoved.bind(this); 42 | this.onItemCleared = this.onItemCleared.bind(this); 43 | this.selectPrevious = this.selectPrevious.bind(this); 44 | this.selectNext = this.selectNext.bind(this); 45 | 46 | // Hash Map 47 | this.list = []; 48 | this.orderedlist = []; 49 | this.hashes = []; 50 | return this; 51 | } 52 | 53 | Playlist.prototype = { 54 | addAll(medias) { 55 | for(var i = 0;i < medias.length; i++) 56 | this.add(medias[i]); 57 | }, 58 | 59 | /* 60 | Adds an media file to the playlist 61 | @param File media: The file to add 62 | */ 63 | add(media) { 64 | if (media === null || (media.type.match("audio") != "audio" && 65 | media.type.match("video") != "video") 66 | || this.hashes.includes(hash(media))) { 67 | return; 68 | } 69 | this.element.classList.add("loading"); 70 | 71 | let item = new PlaylistItem({ 72 | type: media.type.match("audio") == "audio" ? "audio" : "video", 73 | media, 74 | playlist: this 75 | }); 76 | this.list.push(item); 77 | this.orderedlist.push(item); 78 | this.hashes.push(item.hash); 79 | if (!this.selectedItem) { 80 | this.onItemSelected(item); 81 | } 82 | this.element.classList.remove("loading"); 83 | return item; 84 | }, 85 | doShuffle() { 86 | for(var i = 0; i < this.list.length; i++) { 87 | var j = i+Math.floor((this.list.length-i)*Math.random()); 88 | var tmp = this.list[i]; 89 | this.list[i] = this.list[j]; 90 | this.list[j] = tmp; 91 | } 92 | }, 93 | toggleShuffle() { 94 | this.shuffle = !this.shuffle; 95 | if (this.shuffle) { 96 | this.doShuffle(); 97 | } else { 98 | this.list = [...this.orderedlist]; 99 | } 100 | }, 101 | selectPrevious() { 102 | var item = this.selectedItem; 103 | var itemIndex = this.list.findIndex(i => i.hash === item.hash); 104 | var nextItemIndex = (this.list.length+itemIndex-1)%this.list.length; 105 | var nextItem = this.list[nextItemIndex]; 106 | item.unselect(); 107 | if (nextItemIndex === this.list.length - 1 && this.shuffle) { 108 | this.doShuffle(); 109 | } 110 | this.onItemSelected(nextItem); 111 | }, 112 | 113 | selectNext() { 114 | var item = this.selectedItem; 115 | var itemIndex = this.list.findIndex(i => i.hash === item.hash); 116 | var nextItemIndex = (itemIndex+1)%this.list.length; 117 | var nextItem = this.list[nextItemIndex]; 118 | item.unselect(); 119 | if (nextItemIndex === 0 && this.shuffle) { 120 | this.doShuffle(); 121 | } 122 | this.onItemSelected(nextItem); 123 | }, 124 | 125 | onItemSelected(item) { 126 | if (this.selectedItem) { 127 | this.selectedItem.unselect(); 128 | } 129 | item.select(); 130 | this.selectedItem = item; 131 | this.params.onItemSelected(item); 132 | }, 133 | 134 | onItemRemoved(item) { 135 | if (this.list.length === 1) { 136 | this.onItemCleared(); 137 | } else if (this.selectedItem.hash === item.hash) { 138 | this.selectNext(item); 139 | } 140 | item.destroy(); 141 | this.list = this.list.filter(i => i.hash !== item.hash); 142 | this.orderedlist = this.orderedlist.filter(i => i.hash !== item.hash); 143 | this.hashes = this.hashes.filter(h => h !== item.hash); 144 | }, 145 | 146 | onItemCleared() { 147 | this.selectedItem = null; 148 | this.params.onItemCleared(); 149 | } 150 | }; 151 | 152 | function PlaylistItem(params) { 153 | this.playlist = params.playlist; 154 | this.hash = hash(params.media); 155 | this.media = params.media; 156 | this.type = params.type; 157 | this.onItemSelected = params.playlist.onItemSelected; 158 | this.onItemRemoved = params.playlist.onItemRemoved; 159 | this.pic = null; 160 | var p; 161 | [this.tag, p] = Utils.readTag(this.media); 162 | this.createDOM(); 163 | p.then(tag => { 164 | if (!tag) return; 165 | this.tag = tag; 166 | this.pic = tag.pic; 167 | this.createDOM(); 168 | }); 169 | return this 170 | } 171 | 172 | PlaylistItem.prototype = { 173 | createDOM() { 174 | var item = this.element ? 175 | this.element : 176 | Element("li", { 177 | title: Utils.getTooltipForTag(this.tag), 178 | onClick: () => this.onItemSelected(this), 179 | parent: this.playlist.element 180 | }); 181 | item.setAttribute('title', Utils.getTooltipForTag(this.tag)); 182 | item.innerHTML = ""; 183 | 184 | var itemWrap = Element("a", { 185 | href: "#", 186 | parent: item 187 | }); 188 | 189 | /* var coverDiv = */ 190 | Element("div", { 191 | class: "cover", 192 | parent: itemWrap 193 | }); 194 | 195 | var textContainer = Element("p", { 196 | class: "text-container", 197 | parent: itemWrap 198 | }); 199 | 200 | Element("span", { 201 | class: "title", 202 | content: this.tag.title, 203 | parent: textContainer 204 | }); 205 | 206 | if (this.tag.artist) { 207 | Element("span", { 208 | class: "artist", 209 | content: this.tag.artist, 210 | parent: textContainer 211 | }); 212 | } 213 | 214 | Element("button", { 215 | class: "cross", 216 | onClick: (e) => { 217 | this.onItemRemoved(this); 218 | e.stopPropagation(); 219 | }, 220 | parent: itemWrap 221 | }); 222 | 223 | this.element = item; 224 | if (this.element.classList.contains("playing")) { 225 | this.onItemSelected(this); 226 | } 227 | return item; 228 | }, 229 | 230 | select() { 231 | this.element.classList.add("playing"); 232 | this.playlist.coverEl.src = this.pic == null ? "" : this.pic; 233 | }, 234 | 235 | unselect() { 236 | this.element.classList.remove("playing"); 237 | this.playlist.coverEl.src = ""; 238 | }, 239 | 240 | destroy() { 241 | this.element.remove(); 242 | this.media = null; 243 | this.playlist.coverEl.src = ""; 244 | } 245 | }; 246 | 247 | -------------------------------------------------------------------------------- /css/controls.css: -------------------------------------------------------------------------------- 1 | /* Media controls */ 2 | #media-controls { 3 | height: 60px; 4 | background-color: #222; 5 | text-align: center; 6 | display: flex; 7 | position: relative; 8 | } 9 | .light #media-controls { 10 | background-color: #1b1b1b; 11 | } 12 | #main-controls { 13 | display: inline-block; 14 | margin: auto; 15 | height: 100%; 16 | } 17 | #secondary-controls { 18 | display: inline-block; 19 | height: 100%; 20 | position: relative; 21 | } 22 | #volume-control { 23 | float: left; 24 | height: 100%; 25 | box-sizing: border-box; 26 | display: flex; 27 | align-items: center; 28 | overflow: hidden; 29 | position: relative; 30 | padding-right: 10px; 31 | } 32 | 33 | /* Media control buttons */ 34 | .media-control { 35 | height: 100%; 36 | width: 60px; 37 | border: none; 38 | background: none; 39 | padding: 0; 40 | text-align: center; 41 | vertical-align: top; 42 | float: left; 43 | cursor: pointer; 44 | position: relative; 45 | } 46 | .media-control:not(:disabled):hover, 47 | .media-control:focus, 48 | .media-control.checked { 49 | background-color: rgba(255,255,255,0.05); 50 | } 51 | .disabled #main-controls, .disabled #speed-btn { 52 | opacity: 0.6; 53 | pointer-events: none; 54 | } 55 | /* This is used to show the icon */ 56 | .media-control::after { 57 | content: ""; 58 | width: 28px; 59 | height: 28px; 60 | display: inline-block; 61 | fill: white; 62 | margin-top: 4px; 63 | background-position: center; 64 | background-repeat: no-repeat; 65 | background-size: cover; 66 | filter: url('data:image/svg+xml,#a'); 67 | } 68 | .media-control.checked::after { 69 | fill: var(--theme-highlight-color); 70 | } 71 | 72 | #loop.loop-one::before { 73 | content: "1"; 74 | display: inline-block; 75 | background: var(--theme-highlight-color); 76 | color: var(--theme-contrast-color); 77 | border-radius: 100%; 78 | width: 1.5em; 79 | height: 1.5em; 80 | line-height: 1.5em; 81 | text-align: center; 82 | position: absolute; 83 | right: 1em; 84 | z-index: 99; 85 | box-shadow: inset 0 0 0 1px #222; 86 | } 87 | @supports ((-webkit-mask: url(../img/pause.svg)) or (mask-image: url(../img/pause.svg))) { 88 | .media-control::after { 89 | background-color: white; 90 | background-image: none !important; 91 | -webkit-mask-repeat: no-repeat; 92 | -webkit-mask-position: center; 93 | -webkit-mask-size: cover; 94 | mask-repeat: no-repeat; 95 | mask-position: center; 96 | mask-size: cover; 97 | filter: none; 98 | } 99 | .media-control.checked::after { 100 | background-color: var(--theme-highlight-color); 101 | } 102 | #play-pause::after { 103 | -webkit-mask-image: url(../img/pause.svg); 104 | mask-image: url(../img/pause.svg); 105 | } 106 | #play-pause.paused::after { 107 | -webkit-mask-image: url(../img/play.svg); 108 | mask-image: url(../img/play.svg); 109 | } 110 | #stop::after { 111 | -webkit-mask-image: url(../img/stop.svg); 112 | mask-image: url(../img/stop.svg); 113 | } 114 | #previous-btn::after { 115 | -webkit-mask-image: url(../img/previous-song.svg); 116 | mask-image: url(../img/previous-song.svg); 117 | } 118 | #next-btn::after { 119 | -webkit-mask-image: url(../img/next-song.svg); 120 | mask-image: url(../img/next-song.svg); 121 | } 122 | #loop::after { 123 | -webkit-mask-image: url(../img/loop.svg); 124 | mask-image: url(../img/loop.svg); 125 | } 126 | #shuffle::after { 127 | -webkit-mask-image: url(../img/shuffle.svg); 128 | mask-image: url(../img/shuffle.svg); 129 | } 130 | #speed-btn::after { 131 | -webkit-mask-image: url(../img/clock.svg); 132 | mask-image: url(../img/clock.svg); 133 | } 134 | #settings-btn::after { 135 | -webkit-mask-image: url(../img/settings.svg); 136 | mask-image: url(../img/settings.svg); 137 | } 138 | #equalizer-btn::after { 139 | -webkit-mask-image: url(../img/equalizer.svg); 140 | } 141 | } 142 | /* Icons */ 143 | #play-pause::after { 144 | background-image: url(../img/pause.svg); 145 | } 146 | #play-pause.paused::after { 147 | background-image: url(../img/play.svg) 148 | } 149 | #stop::after { 150 | background-image: url(../img/stop.svg) 151 | } 152 | #previous-btn::after { 153 | background-image: url(../img/previous-song.svg); 154 | } 155 | #next-btn::after { 156 | background-image: url(../img/next-song.svg); 157 | } 158 | #loop::after { 159 | background-image: url(../img/loop.svg) 160 | } 161 | #shuffle::after { 162 | background-image: url(../img/shuffle.svg) 163 | } 164 | #speed-btn, 165 | #equalizer-btn { 166 | position: relative; 167 | } 168 | #equalizer-btn::after { 169 | background-image: url(../img/equalizer.svg); 170 | } 171 | #speed-btn::after { 172 | background-image: url(../img/clock.svg); 173 | } 174 | .menu { 175 | position: absolute; 176 | transition: all .3s; 177 | left: 0 178 | vertical-align: middle; 179 | border-radius: 2px; 180 | background: black; 181 | z-index: 4000; 182 | color: black; 183 | filter: invert(1); 184 | background-color: #f1f1f1; 185 | box-shadow: 0 0 3px #ccc; 186 | } 187 | 188 | .menu::after{ 189 | content:""; 190 | position: absolute; 191 | left: 20px; 192 | top: 100%; 193 | width: 0; 194 | height: 0; 195 | border-style: solid; 196 | border-width: 10px 10px 0 10px; 197 | border-color: #f1f1f1 transparent transparent transparent; 198 | } 199 | 200 | #equalizer-panel { 201 | padding: 10px 0; 202 | } 203 | #equalizer-panel input{ 204 | -webkit-appearance: slider-vertical; 205 | filter: invert(1); 206 | } 207 | #equalizer-panel input::-moz-range-track { 208 | background: rgba(255,255,255,.05); 209 | border-radius: 2px; 210 | } 211 | #equalizer-panel input::-moz-range-thumb { 212 | border: none; 213 | background: white; 214 | } 215 | #equalizer-panel .container { 216 | display: flex; 217 | align-items: stretch; 218 | } 219 | #equalizer-panel .container .scale { 220 | width: 4ch; 221 | align-items: flex-end; 222 | } 223 | #equalizer-panel .container .scale::after { 224 | content: " "; 225 | height: 1em; 226 | display: inline-block; 227 | text-align: right; 228 | 229 | } 230 | #equalizer-panel .container div { 231 | display: inline-flex; 232 | flex-direction: column; 233 | width: 35px; 234 | text-align: center; 235 | align-items: center; 236 | vertical-align: top; 237 | } 238 | #equalizer-panel .container div.flex { 239 | flex-grow: 1; 240 | } 241 | @supports (-webkit-appearance: slider-vertical) { 242 | #equalizer-panel .container div { 243 | align-items: stretch !important; 244 | } 245 | } 246 | #equalizer-panel label { 247 | font-size: 13px; 248 | } 249 | #speed-menu { 250 | width: 100px; 251 | } 252 | #speed-menu > select { 253 | -moz-appearance: none; 254 | width: 100%; 255 | border: none; 256 | text-align: center; 257 | background: none; 258 | font: inherit; 259 | overflow: hidden; 260 | border-radius: 2px; 261 | } 262 | #speed-menu > select option { 263 | padding: 2px 0; 264 | -moz-appearance: none; 265 | } 266 | 267 | #speed-menu > select option:hover { 268 | background: rgba(0, 0, 0, 0.2); 269 | color: rgba(255, 255, 255, 0.5); 270 | } 271 | 272 | #speed-menu > select:focus > option:checked { 273 | filter: invert(1); 274 | } 275 | .media-control.checked + .menu { 276 | bottom: 100%; 277 | opacity:1; 278 | } 279 | .media-control:not(.checked) + .menu { 280 | transform: scale(0); 281 | bottom: -100%; 282 | opacity: 0; 283 | z-index: 0; 284 | visibility: hidden; 285 | pointer-events: none; 286 | } 287 | #settings-btn::after { 288 | background-image: url(../img/settings.svg); 289 | } 290 | 291 | /* Volume control */ 292 | #volume-icon { 293 | position: relative; 294 | min-width: 20px; 295 | width: 20px; 296 | height: 20px; 297 | margin: 0 15px; 298 | margin-left: 5px; 299 | display: inline-block; 300 | background-size: cover; 301 | background-image: url(../img/volume_up.svg); 302 | -webkit-filter: invert(1); 303 | filter: invert(1); 304 | } 305 | #volume-control:not(:hover) #volume-icon::after { 306 | opacity: 0; 307 | } 308 | #volume-icon:not(.mute)[title]::after { 309 | content: attr(title); 310 | position: absolute; 311 | top: -10px; 312 | margin-left: 4px; 313 | background-color: var(--theme-highlight-color); 314 | color: var(--theme-contrast-color); 315 | filter: invert(1); 316 | font-size: 0.5em; 317 | padding: 1px; 318 | border-radius: 2px; 319 | transition: opacity 0.3s; 320 | } 321 | #volume-icon.half { 322 | background-image: url(../img/volume_down.svg); 323 | } 324 | #volume-icon.mute { 325 | background-image: url(../img/volume_mute.svg); 326 | } 327 | #volume-range { 328 | -webkit-appearance: none; 329 | -moz-appearance: none; 330 | flex: 1; 331 | min-width: none; 332 | cursor: pointer; 333 | height: 3px; 334 | margin: 0; 335 | transition: transform 0.2s; 336 | max-width: 120px !important; 337 | } 338 | #volume-range::-webkit-slider-runnable-track { 339 | background-color: var(--theme-highlight-color); 340 | height: 3px; 341 | } 342 | #volume-range::-moz-range-track { 343 | -moz-appearance: none; 344 | border: none; 345 | background: white; 346 | border-radius: 5px; 347 | height: 3px; 348 | } 349 | input[type=range]::-moz-range-thumb { 350 | background: var(--theme-highlight-color) !important; 351 | border-radius: 50%; 352 | height: 16px; 353 | width: 16px; 354 | border: none; 355 | } 356 | input[type=range]:focus::-moz-range-thumb { 357 | box-shadow: 0 0 0 8px rgba(255,255,255,0.1); 358 | } 359 | input[type=range]::-moz-range-progress { 360 | background-color: var(--theme-highlight-color); 361 | height: 3px; 362 | width: 1em; 363 | } 364 | #volume-range::-webkit-slider-track { 365 | -webkit-appearance: none; 366 | border: none; 367 | background: white; 368 | border-radius: 5px; 369 | height: 3px; 370 | } 371 | #volume-range::-webkit-slider-thumb { 372 | -webkit-appearance: none; 373 | background: var(--theme-highlight-color); 374 | border-radius: 50%; 375 | height: 16px; 376 | width: 16px; 377 | border: none; 378 | transform: translateY(-50%); 379 | } 380 | input[type=range]:focus::-webkit-slider-thumb { 381 | border-radius: 100%; 382 | box-shadow: 0 0 0 10px rgba(255,255,255,0.1); 383 | } 384 | /* "Progress bar" for media */ 385 | #progress-bar { 386 | height: 5px; 387 | background: #121212; 388 | cursor: pointer; 389 | } 390 | #progress { 391 | background: var(--theme-highlight-color); 392 | height: 100%; 393 | width: 0; 394 | display: inline-block; 395 | vertical-align: top; 396 | } 397 | #tooltip { 398 | background: var(--theme-highlight-color); 399 | color: var(--theme-contrast-color); 400 | display: inline-block; 401 | position: absolute; 402 | font-size: 10px; 403 | padding: 2px; 404 | margin-top: -5px; 405 | margin-left: -35px; 406 | box-sizing: border-box; 407 | min-width: 35px; 408 | text-align: center; 409 | border-radius: 1px; 410 | z-index: 9; 411 | opacity: 0; 412 | transition: all 0.3s; 413 | } 414 | body:hover #tooltip { 415 | opacity: 1; 416 | } 417 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | module.exports = { 4 | globals: { 5 | MediaPlayer: true, 6 | Playlist: true, 7 | Element: true, 8 | SettingsOverlay: true, 9 | SettingsStore: true, 10 | Utils: true, 11 | Equalizer: true, 12 | musicmetadata: true 13 | }, 14 | env: { 15 | es6: true, 16 | browser: true, 17 | commonjs: true 18 | }, 19 | "rules": { 20 | // Disallow using variables outside the blocks they are defined (especially 21 | // since only let and const are used, see "no-var"). 22 | "block-scoped-var": 2, 23 | // Enforce one true brace style (opening brace on the same line) and avoid 24 | // start and end braces on the same line. 25 | "brace-style": [2, "1tbs", {"allowSingleLine": false}], 26 | // Require camel case names 27 | "camelcase": 2, 28 | // Allow trailing commas for easy list extension. Having them does not 29 | // impair readability, but also not required either. 30 | "comma-dangle": 0, 31 | // Enforce spacing before and after comma 32 | "comma-spacing": [2, {"before": false, "after": true}], 33 | // Enforce one true comma style. 34 | "comma-style": [2, "last"], 35 | // Warn about cyclomatic complexity in functions. 36 | "complexity": [2, 35], 37 | // Require return statements to either always or never specify values. 38 | "consistent-return": 2, 39 | // Don't warn for inconsistent naming when capturing this (not so important 40 | // with auto-binding fat arrow functions). 41 | "consistent-this": 0, 42 | // Enforce curly brace conventions for all control statements. 43 | "curly": 2, 44 | // Don't require a default case in switch statements. Avoid being forced to 45 | // add a bogus default when you know all possible cases are handled. 46 | "default-case": 0, 47 | // Enforce dots on the next line with property name. 48 | "dot-location": [2, "property"], 49 | // Encourage the use of dot notation whenever possible. 50 | "dot-notation": 2, 51 | // Enforce newline at the end of file, with no multiple empty lines. 52 | "eol-last": 2, 53 | // Allow using == instead of ===, in the interest of landing something since 54 | // the devtools codebase is split on convention here. 55 | "eqeqeq": 0, 56 | // Don't require function expressions to have a name. 57 | // This makes the code more verbose and hard to read. Our engine already 58 | // does a fantastic job assigning a name to the function, which includes 59 | // the enclosing function name, and worst case you have a line number that 60 | // you can just look up. 61 | "func-names": 0, 62 | // Allow use of function declarations and expressions. 63 | "func-style": 0, 64 | // Deprecated, will be removed in 1.0. 65 | "generator-star": 0, 66 | // Enforce the spacing around the * in generator functions. 67 | "generator-star-spacing": [2, "after"], 68 | // Deprecated, will be removed in 1.0. 69 | "global-strict": 0, 70 | // Only useful in a node environment. 71 | "handle-callback-err": 0, 72 | // Tab width. 73 | "indent": [2, 2, {"SwitchCase": 1}], 74 | // Enforces spacing between keys and values in object literal properties. 75 | "key-spacing": [2, {"beforeColon": false, "afterColon": true}], 76 | // Allow mixed 'LF' and 'CRLF' as linebreaks. 77 | "linebreak-style": 0, 78 | // Don't enforce the maximum depth that blocks can be nested. The complexity 79 | // rule is a better rule to check this. 80 | "max-depth": 0, 81 | // Maximum length of a line. 82 | "max-len": [2, 100, 2, {"ignoreUrls": true, "ignorePattern": "data:image\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}], 83 | // Maximum depth callbacks can be nested. 84 | "max-nested-callbacks": [2, 3], 85 | // Don't limit the number of parameters that can be used in a function. 86 | "max-params": 0, 87 | // Don't limit the maximum number of statement allowed in a function. We 88 | // already have the complexity rule that's a better measurement. 89 | "max-statements": 0, 90 | // Require a capital letter for constructors, only check if all new 91 | // operators are followed by a capital letter. Don't warn when capitalized 92 | // functions are used without the new operator. 93 | "new-cap": [2, {"capIsNew": false}], 94 | // Disallow the omission of parentheses when invoking a constructor with no 95 | // arguments. 96 | "new-parens": 2, 97 | // Disallow use of the Array constructor. 98 | "no-array-constructor": 2, 99 | // Allow use of bitwise operators. 100 | "no-bitwise": 0, 101 | // Disallow use of arguments.caller or arguments.callee. 102 | "no-caller": 2, 103 | // Disallow the catch clause parameter name being the same as a variable in 104 | // the outer scope, to avoid confusion. 105 | "no-catch-shadow": 2, 106 | // Deprecated, will be removed in 1.0. 107 | "no-comma-dangle": 0, 108 | // Disallow assignment in conditional expressions. 109 | "no-cond-assign": 2, 110 | // Allow using the console API. 111 | "no-console": 0, 112 | // Allow using constant expressions in conditions like while (true) 113 | "no-constant-condition": 0, 114 | // Allow use of the continue statement. 115 | "no-continue": 0, 116 | // Disallow control characters in regular expressions. 117 | "no-control-regex": 2, 118 | // Disallow use of debugger. 119 | "no-debugger": 2, 120 | // Disallow deletion of variables (deleting properties is fine). 121 | "no-delete-var": 2, 122 | // Allow division operators explicitly at beginning of regular expression. 123 | "no-div-regex": 0, 124 | // Disallow duplicate arguments in functions. 125 | "no-dupe-args": 2, 126 | // Disallow duplicate keys when creating object literals. 127 | "no-dupe-keys": 2, 128 | // Disallow a duplicate case label. 129 | "no-duplicate-case": 2, 130 | // Disallow else after a return in an if. The else around the second return 131 | // here is useless: 132 | // if (something) { return false; } else { return true; } 133 | "no-else-return": 2, 134 | // Disallow empty statements. This will report an error for: 135 | // try { something(); } catch (e) {} 136 | // but will not report it for: 137 | // try { something(); } catch (e) { /* Silencing the error because ...*/ } 138 | // which is a valid use case. 139 | "no-empty": 2, 140 | // Disallow the use of empty character classes in regular expressions. 141 | "no-empty-character-class": 2, 142 | // Disallow use of eval(). We have other APIs to evaluate code in content. 143 | "no-eval": 2, 144 | // Disallow assigning to the exception in a catch block. 145 | "no-ex-assign": 2, 146 | // Disallow adding to native types 147 | "no-extend-native": 2, 148 | // Disallow unnecessary function binding. 149 | "no-extra-bind": 2, 150 | // Disallow double-negation boolean casts in a boolean context. 151 | "no-extra-boolean-cast": 2, 152 | // Allow unnecessary parentheses, as they may make the code more readable. 153 | "no-extra-parens": 0, 154 | // Disallow unnecessary semicolons. 155 | "no-extra-semi": 2, 156 | // Deprecated, will be removed in 1.0. 157 | "no-extra-strict": 0, 158 | // Disallow fallthrough of case statements, except if there is a comment. 159 | "no-fallthrough": 2, 160 | // Allow the use of leading or trailing decimal points in numeric literals. 161 | "no-floating-decimal": 0, 162 | // Disallow comments inline after code. 163 | "no-inline-comments": 2, 164 | // Disallow if as the only statement in an else block. 165 | "no-lonely-if": 2, 166 | // Allow mixing regular variable and require declarations (not a node env). 167 | "no-mixed-requires": 0, 168 | // Disallow mixed spaces and tabs for indentation. 169 | "no-mixed-spaces-and-tabs": 2, 170 | // Disallow use of multiple spaces (sometimes used to align const values, 171 | // array or object items, etc.). It's hard to maintain and doesn't add that 172 | // much benefit. 173 | "no-multi-spaces": 2, 174 | // Disallow use of multiline strings (use template strings instead). 175 | "no-multi-str": 2, 176 | // Disallow multiple empty lines. 177 | "no-multiple-empty-lines": [2, {"max": 1}], 178 | // Disallow reassignments of native objects. 179 | "no-native-reassign": 2, 180 | // Disallow nested ternary expressions, they make the code hard to read. 181 | "no-nested-ternary": 2, 182 | // Allow use of new operator with the require function. 183 | "no-new-require": 0, 184 | // Disallow use of octal literals. 185 | "no-octal": 2, 186 | // Allow reassignment of function parameters. 187 | "no-param-reassign": 0, 188 | // Allow string concatenation with __dirname and __filename (not a node env). 189 | "no-path-concat": 0, 190 | // Allow use of unary operators, ++ and --. 191 | "no-plusplus": 0, 192 | // Allow using process.env (not a node environment). 193 | "no-process-env": 0, 194 | // Allow using process.exit (not a node environment). 195 | "no-process-exit": 0, 196 | // Disallow usage of __proto__ property. 197 | "no-proto": 2, 198 | // Disallow declaring the same variable more than once (we use let anyway). 199 | "no-redeclare": 2, 200 | // Disallow multiple spaces in a regular expression literal. 201 | "no-regex-spaces": 2, 202 | // Allow reserved words being used as object literal keys. 203 | "no-reserved-keys": 0, 204 | // Don't restrict usage of specified node modules (not a node environment). 205 | "no-restricted-modules": 0, 206 | // Disallow use of assignment in return statement. It is preferable for a 207 | // single line of code to have only one easily predictable effect. 208 | "no-return-assign": 2, 209 | // Allow use of javascript: urls. 210 | "no-script-url": 0, 211 | // Disallow comparisons where both sides are exactly the same. 212 | "no-self-compare": 2, 213 | // Disallow use of comma operator. 214 | "no-sequences": 2, 215 | // Warn about declaration of variables already declared in the outer scope. 216 | // This isn't an error because it sometimes is useful to use the same name 217 | // in a small helper function rather than having to come up with another 218 | // random name. 219 | // Still, making this a warning can help people avoid being confused. 220 | "no-shadow": 2, 221 | // Disallow shadowing of names such as arguments. 222 | "no-shadow-restricted-names": 2, 223 | // Deprecated, will be removed in 1.0. 224 | "no-space-before-semi": 0, 225 | // Disallow space between function identifier and application. 226 | "no-spaced-func": 2, 227 | // Disallow sparse arrays, eg. let arr = [,,2]. 228 | // Array destructuring is fine though: 229 | // for (let [, breakpointPromise] of aPromises) 230 | "no-sparse-arrays": 2, 231 | // Allow use of synchronous methods (not a node environment). 232 | "no-sync": 0, 233 | // Allow the use of ternary operators. 234 | "no-ternary": 0, 235 | // Disallow throwing literals (eg. throw "error" instead of 236 | // throw new Error("error")). 237 | "no-throw-literal": 2, 238 | // Disallow trailing whitespace at the end of lines. 239 | "no-trailing-spaces": 2, 240 | // Disallow use of undeclared variables unless mentioned in a /*global */ 241 | // block. Note that globals from head.js are automatically imported in tests 242 | // by the import-headjs-globals rule form the mozilla eslint plugin. 243 | "no-undef": 2, 244 | // Allow dangling underscores in identifiers (for privates). 245 | "no-underscore-dangle": 0, 246 | // Allow use of undefined variable. 247 | "no-undefined": 0, 248 | // Disallow the use of Boolean literals in conditional expressions. 249 | "no-unneeded-ternary": 2, 250 | // Disallow unreachable statements after a return, throw, continue, or break 251 | // statement. 252 | "no-unreachable": 2, 253 | // Disallow global and local variables that aren't used, but allow unused function arguments. 254 | "no-unused-vars": [2, {"vars": "all", "args": "none"}], 255 | // Allow using variables before they are defined. 256 | "no-use-before-define": 0, 257 | // We use var-only-at-top-level instead of no-var as we allow top level 258 | // vars. 259 | "no-var": 0, 260 | // Allow using TODO/FIXME comments. 261 | "no-warning-comments": 0, 262 | // Disallow use of the with statement. 263 | "no-with": 2, 264 | // Require method and property shorthand syntax for object literals. 265 | "object-shorthand": 2, 266 | // Allow more than one variable declaration per function. 267 | "one-var": 0, 268 | // Disallow padding within blocks. 269 | "padded-blocks": [2, "never"], 270 | // Don't require quotes around object literal property names. 271 | "quote-props": 0, 272 | // Double quotes should be used. 273 | "quotes": [2, "double", "avoid-escape"], 274 | // Don't require use of the second argument for parseInt(). 275 | "radix": 0, 276 | // Always require use of semicolons wherever they are valid. 277 | "semi": [2, "always"], 278 | // Enforce spacing after semicolons. 279 | "semi-spacing": [2, {"before": false, "after": true}], 280 | // Don't require to sort variables within the same declaration block. 281 | // Anyway, one-var is disabled. 282 | "sort-vars": 0, 283 | // Deprecated, will be removed in 1.0. 284 | "space-after-function-name": 0, 285 | // Require a space around all keywords. 286 | "keyword-spacing": 2, 287 | // Require a space before the start brace of a block. 288 | "space-before-blocks": [2, "always"], 289 | // Deprecated, will be removed in 1.0. 290 | "space-before-function-parentheses": 0, 291 | // Require space after keyword for anonymous functions, but disallow space 292 | // after name of named functions. 293 | "space-before-function-paren": [2, {"anonymous": "never", "named": "never"}], 294 | // Disable the rule that checks if spaces inside {} and [] are there or not. 295 | // Our code is split on conventions, and it'd be nice to have 2 rules 296 | // instead, one for [] and one for {}. So, disabling until we write them. 297 | "space-in-brackets": 0, 298 | // Disallow spaces inside parentheses. 299 | "space-in-parens": [2, "never"], 300 | // Require spaces around operators, except for a|0. 301 | "space-infix-ops": [2, {"int32Hint": true}], 302 | // Require spaces before/after unary operators (words on by default, 303 | // nonwords off by default). 304 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 305 | // Deprecated, will be removed in 1.0. 306 | "space-unary-word-ops": 0, 307 | // Require a space immediately following the // in a line comment. 308 | "spaced-comment": [2, "always"], 309 | // Require "use strict" to be defined globally in the script. 310 | "strict": [2, "global"], 311 | // Disallow comparisons with the value NaN. 312 | "use-isnan": 2, 313 | // Warn about invalid JSDoc comments. 314 | // Disabled for now because of https://github.com/eslint/eslint/issues/2270 315 | // The rule fails on some jsdoc comments like in: 316 | // devtools/client/webconsole/console-output.js 317 | "valid-jsdoc": 0, 318 | // Ensure that the results of typeof are compared against a valid string. 319 | "valid-typeof": 2, 320 | // Allow vars to be declared anywhere in the scope. 321 | "vars-on-top": 0, 322 | // Don't require immediate function invocation to be wrapped in parentheses. 323 | "wrap-iife": 0, 324 | // Don't require regex literals to be wrapped in parentheses (which 325 | // supposedly prevent them from being mistaken for division operators). 326 | "wrap-regex": 0, 327 | // Disallow Yoda conditions (where literal value comes first). 328 | "yoda": 2, 329 | 330 | // And these are the rules that haven't been discussed so far, and that are 331 | // disabled for now until we introduce them, one at a time. 332 | 333 | // Require for-in loops to have an if statement. 334 | "guard-for-in": 0, 335 | // allow/disallow an empty newline after var statement 336 | "newline-after-var": 0, 337 | // disallow the use of alert, confirm, and prompt 338 | "no-alert": 0, 339 | // disallow comparisons to null without a type-checking operator 340 | "no-eq-null": 0, 341 | // disallow overwriting functions written as function declarations 342 | "no-func-assign": 0, 343 | // disallow use of eval()-like methods 344 | "no-implied-eval": 0, 345 | // disallow function or variable declarations in nested blocks 346 | "no-inner-declarations": 0, 347 | // disallow invalid regular expression strings in the RegExp constructor 348 | "no-invalid-regexp": 0, 349 | // disallow irregular whitespace outside of strings and comments 350 | "no-irregular-whitespace": 0, 351 | // disallow usage of __iterator__ property 352 | "no-iterator": 0, 353 | // disallow labels that share a name with a variable 354 | "no-label-var": 0, 355 | // disallow use of labeled statements 356 | "no-labels": 2, 357 | // disallow unnecessary nested blocks 358 | "no-lone-blocks": 0, 359 | // disallow creation of functions within loops 360 | "no-loop-func": 0, 361 | // disallow negation of the left operand of an in expression 362 | "no-negated-in-lhs": 0, 363 | // disallow use of new operator when not part of the assignment or 364 | // comparison 365 | "no-new": 0, 366 | // disallow use of new operator for Function object 367 | "no-new-func": 0, 368 | // disallow use of the Object constructor 369 | "no-new-object": 0, 370 | // disallows creating new instances of String,Number, and Boolean 371 | "no-new-wrappers": 0, 372 | // disallow the use of object properties of the global object (Math and 373 | // JSON) as functions 374 | "no-obj-calls": 0, 375 | // disallow use of octal escape sequences in string literals, such as 376 | // var foo = "Copyright \251"; 377 | "no-octal-escape": 0, 378 | // disallow use of undefined when initializing variables 379 | "no-undef-init": 0, 380 | // disallow usage of expressions in statement position 381 | "no-unused-expressions": 0, 382 | // disallow use of void operator 383 | "no-void": 0, 384 | // disallow wrapping of non-IIFE statements in parens 385 | "no-wrap-func": 0, 386 | // require assignment operator shorthand where possible or prohibit it 387 | // entirely 388 | "operator-assignment": 0, 389 | // enforce operators to be placed before or after line breaks 390 | "operator-linebreak": 0, 391 | // enforce quote around keys 392 | "quote-props": ["error", "consistent-as-needed"], 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /js/MediaPlayer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var MediaPlayer = { 4 | init() { 5 | this.isElectron = !!(window.process && window.process.type && window.process.versions.electron); 6 | 7 | /* Define elements */ 8 | this.videoEl = document.getElementById("MediaPlayer"); 9 | this.uploadEl = document.getElementById("upfile"); 10 | this.headerEl = document.getElementById("header"); 11 | 12 | this.playPauseEl = document.getElementById("play-pause"); 13 | this.volumeIcon = document.getElementById("volume-icon"); 14 | this.volumeSlider = document.getElementById("volume-range"); 15 | this.loopEl = document.getElementById("loop"); 16 | this.shuffleEl = document.getElementById("shuffle"); 17 | this.speedBtnEl = document.getElementById("speed-btn"); 18 | 19 | this.progressBar = document.getElementById("progress-bar"); 20 | this.progressEl = document.getElementById("progress"); 21 | this.tooltipEl = document.getElementById("tooltip"); 22 | 23 | this.sidebarEl = document.getElementById("sidebar"); 24 | this.controlsEl = document.getElementById("media-controls"); 25 | this.canvasEl = document.getElementById("visualizer"); 26 | 27 | this.controlOverlay = document.getElementById("control-overlay"); 28 | this.videoEl.controls = false; 29 | 30 | this.previousBtn = document.getElementById("previous-btn"); 31 | this.nextBtn = document.getElementById("next-btn"); 32 | this.wirePrevNextControls({ 33 | element: this.previousBtn, 34 | onHold: this.fastrewind.bind(this), 35 | onClick: () => { 36 | this.killContext(); 37 | this.playlist.selectPrevious(); 38 | } 39 | }); 40 | this.wirePrevNextControls({ 41 | element: this.nextBtn, 42 | onHold: this.fastforward.bind(this), 43 | onClick: () => { 44 | this.killContext(); 45 | this.playlist.selectNext(); 46 | } 47 | }); 48 | 49 | /* Initialize playlist */ 50 | this.playlist = new Playlist({ 51 | element: document.getElementById("playlist"), 52 | onItemSelected: this.setMedia.bind(this), 53 | onItemRemoved: () => {}, 54 | onItemCleared: () => { 55 | this.UIEnabled = false; 56 | }, 57 | }); 58 | 59 | this.settingsStore = new SettingsStore({ 60 | isElectron: this.isElectron, 61 | }); 62 | this.settingsOverlay = new SettingsOverlay({ 63 | store: this.settingsStore, 64 | element: document.getElementById("settings-overlay"), 65 | toggle: document.getElementById("settings-btn") 66 | }); 67 | 68 | if (this.isElectron) { 69 | var ElectronApp = require("./js/ElectronApp"); 70 | ElectronApp.init(); 71 | } 72 | let fullscreenchange = (e) => { 73 | document.documentElement.classList.toggle("fullscreen", 74 | !!(document.fullscreenElement 75 | || document.webkitFullscreenElement 76 | || document.msFullscreenElement 77 | || document.mozFullScreenElement)); 78 | }; 79 | addEventListener("fullscreenchange", fullscreenchange); 80 | addEventListener("webkitfullscreenchange", fullscreenchange); 81 | addEventListener("msfullscreenchange", fullscreenchange); 82 | addEventListener("mozfullscreenchange", fullscreenchange); 83 | /* Bind functions */ 84 | this.uploadFiles = this.uploadFiles.bind(this); 85 | 86 | /* Setup uploader */ 87 | this.uploadEl.addEventListener("change", () => this.uploadFiles(this.uploadEl.files)); 88 | 89 | this.sidebarEl.addEventListener("dragenter", () => { 90 | this.sidebarEl.classList.remove("no-drag"); 91 | this.sidebarEl.classList.add("drag"); 92 | }); 93 | this.sidebarEl.addEventListener("dragleave", () => { 94 | this.sidebarEl.classList.remove("drag"); 95 | this.sidebarEl.classList.add("no-drag"); 96 | }); 97 | this.sidebarEl.addEventListener("dragover", (e) => { 98 | e.preventDefault(); 99 | e.stopPropagation(); 100 | }); 101 | this.sidebarEl.addEventListener("drop", (e) => { 102 | e.preventDefault(); 103 | e.stopPropagation(); 104 | this.uploadFiles(e.dataTransfer.files); 105 | this.sidebarEl.classList.remove("drag"); 106 | this.sidebarEl.classList.add("no-drag"); 107 | }); 108 | 109 | this.progressBar.addEventListener("mousedown", function(e) { 110 | this.classList.add("dragging"); 111 | MediaPlayer.onProgressClick(e.pageX); 112 | }); 113 | 114 | let stopProgressBarDrag = (e) => { 115 | if (e.type === "mouseout" && e.target !== document.documentElement) { 116 | return; 117 | } 118 | this.progressBar.classList.remove("dragging"); 119 | }; 120 | document.documentElement.addEventListener("mouseout", stopProgressBarDrag); 121 | window.addEventListener("mouseup", stopProgressBarDrag); 122 | window.addEventListener("mousemove", (e) => { 123 | if (this.progressBar.classList.contains("dragging")) { 124 | this.onProgressClick(e.pageX); 125 | } 126 | }); 127 | this.progressBar.addEventListener("mouseover", function(e) { 128 | MediaPlayer.setProgressTooltip(e.pageX); 129 | }); 130 | 131 | this.controlOverlay.addEventListener("click", () => { 132 | if (!this.UIEnabled) { 133 | return; 134 | } 135 | if (!this.videoEl.hidden) { 136 | this.togglePaused(); 137 | } 138 | }); 139 | 140 | addEventListener("keydown", (e) => { 141 | if (document.activeElement != document.body) { 142 | return; 143 | } 144 | switch (e.keyCode) { 145 | // Spacebar 146 | case 32: 147 | this.togglePaused(); 148 | break; 149 | // Top arrow 150 | case 38: 151 | if (e.ctrlKey || e.metaKey) { 152 | this.changeVolume(Math.min(1, this.videoEl.volume + 0.1)); 153 | e.preventDefault(); 154 | } else { 155 | this.playlist.selectPrevious(); 156 | } 157 | break; 158 | // Down arrow 159 | case 40: 160 | if (e.ctrlKey || e.metaKey) { 161 | this.changeVolume(Math.max(0, this.videoEl.volume - 0.1)); 162 | e.preventDefault(); 163 | } else { 164 | this.playlist.selectNext(); 165 | } 166 | break; 167 | // Left arrow 168 | case 37: 169 | this.fastrewind(); 170 | break; 171 | // Right arrow 172 | case 39: 173 | this.fastforward(); 174 | break; 175 | } 176 | }); 177 | this.videoEl.addEventListener("timeupdate", function() { 178 | MediaPlayer.updateProgressBar(); 179 | MediaPlayer.tooltipEl.textContent = MediaPlayer.getTooltip(this.currentTime); 180 | }); 181 | this.videoEl.addEventListener("play", () => { 182 | this.playPauseEl.classList.remove("paused"); 183 | }); 184 | this.videoEl.addEventListener("pause", () => { 185 | this.playPauseEl.classList.add("paused"); 186 | }); 187 | this.videoEl.addEventListener("ended", () => { 188 | if (this.playlist.loop) { 189 | this.killContext(); 190 | this.playlist.selectNext(); 191 | } 192 | }); 193 | 194 | this.UIEnabled = false; 195 | this.changeLoopState(1); 196 | }, 197 | set UIEnabled(value) { 198 | if (!value) { 199 | document.body.classList.add("controls-disabled"); 200 | this.controlsEl.classList.add("disabled"); 201 | this.headerEl.textContent = ""; 202 | this.headerEl.title = ""; 203 | document.title = "Media Player"; 204 | this.videoEl.hidden = true; 205 | this.canvasEl.hidden = false; 206 | this.videoEl.src = ""; 207 | this.pause(true); 208 | this.videoEl.currentTime = 0; 209 | } else { 210 | document.body.classList.remove("controls-disabled"); 211 | this.controlsEl.classList.remove("disabled"); 212 | } 213 | }, 214 | get UIEnabled() { 215 | return !this.controlsEl.classList.contains("disabled"); 216 | }, 217 | 218 | toggleFullscreen() { 219 | let shouldGoFullscreen = !document.documentElement.classList.contains("fullscreen"); 220 | 221 | if (shouldGoFullscreen) { 222 | if (!document.documentElement.requestFullScreen) { 223 | document.documentElement.requestFullScreen = document.documentElement.mozRequestFullScreen 224 | || document.documentElement.webkitRequestFullscreen; 225 | } 226 | document.documentElement.requestFullScreen(); 227 | } else { 228 | if (!document.exitFullscreen) { 229 | document.exitFullscreen = document.mozCancelFullScreen 230 | || document.webkitExitFullscreen; 231 | } 232 | document.exitFullscreen(); 233 | } 234 | }, 235 | 236 | /** Sidebar **/ 237 | uploadFiles(uploadedMedia) { 238 | if (!this.ctx) this.initAudioContext(); 239 | uploadedMedia = Array.from(uploadedMedia) || []; 240 | 241 | return this.playlist.addAll(uploadedMedia); 242 | }, 243 | setMedia(item) { 244 | document.querySelector("#display-container") 245 | .className = item.type; 246 | this.videoEl.hidden = item.type != "video"; 247 | this.canvasEl.hidden = item.type == "video"; 248 | URL.revokeObjectURL(this.videoEl.src); 249 | this.videoEl.src = URL.createObjectURL(item.media); 250 | this.updateHeader(item.tag); 251 | // Scroll to the selected item 252 | this.playlist.element.scrollTo(item.element.offsetTop, 1000); 253 | this.UIEnabled = true; 254 | this.play(true); 255 | }, 256 | updateHeader(tag) { 257 | let artistAndTitle = tag.artist ? tag.artist + " - " + tag.title 258 | : tag.title; 259 | this.headerEl.textContent = artistAndTitle; 260 | document.title = this.headerEl.textContent; 261 | 262 | this.headerEl.title = Utils.getTooltipForTag(tag); 263 | }, 264 | 265 | /** Audio controls **/ 266 | initAudioContext() { 267 | var AudioContext = window.AudioContext || window.webkitAudioContext; 268 | var ctx = new AudioContext(); 269 | var media = this.videoEl; 270 | var mediaSrc = ctx.createMediaElementSource(media); 271 | 272 | var analyser = ctx.createAnalyser(); 273 | analyser.connect(ctx.destination); 274 | 275 | this.equalizer = new Equalizer({ 276 | panel: document.querySelector("#equalizer-panel"), 277 | toggle: document.querySelector("#equalizer-btn"), 278 | audioCtx: ctx, 279 | departureNode: mediaSrc, 280 | destinationNode: analyser, 281 | }); 282 | 283 | this.analyser = analyser; 284 | this.ctx = ctx; 285 | }, 286 | animateIndicator(playing) { 287 | this.controlOverlay.className = playing ? "playing" : "paused"; 288 | setTimeout(() => { 289 | this.controlOverlay.className = ""; 290 | }, 500); 291 | }, 292 | wirePrevNextControls(params) { 293 | let element = params.element; 294 | let onClick = params.onClick; 295 | let onHold = params.onHold; 296 | let holded = false; 297 | element.addEventListener("mousedown", () => { 298 | this.prevOrNextPressed = true; 299 | this.prevNextTimeout = setTimeout(() => { 300 | if (this.prevNextTimeout) { 301 | this.prevNextInterval = setInterval(() => { 302 | holded = true; 303 | onHold(); 304 | }, 500); 305 | } 306 | }); 307 | }, 1000); 308 | 309 | element.addEventListener("mouseup", () => { 310 | if (!this.prevOrNextPressed) { 311 | return; 312 | } 313 | this.prevOrNextPressed = false; 314 | if (this.prevNextTimeout) { 315 | clearTimeout(this.prevNextTimeout); 316 | this.prevNextTimeout = null; 317 | } 318 | if (!holded) { 319 | onClick(); 320 | } 321 | if (this.prevNextInterval) { 322 | clearInterval(this.prevNextInterval); 323 | this.prevNextInterval = null; 324 | } 325 | }); 326 | }, 327 | get paused() { 328 | return this.videoEl.paused; 329 | }, 330 | play(disableAnimation) { 331 | this.videoEl.play(); 332 | if (!this.canvasEl.hidden) { 333 | this.recordContext(); 334 | } 335 | this.controlOverlay.className = "playing"; 336 | this.canvasEl.classList.remove("placeholder"); 337 | if (!disableAnimation) { 338 | this.animateIndicator(true); 339 | } 340 | }, 341 | pause(disableAnimation) { 342 | this.videoEl.pause(); 343 | this.canvasEl.classList.add("placeholder"); 344 | this.killContext(); 345 | if (!disableAnimation) { 346 | this.animateIndicator(false); 347 | } 348 | }, 349 | togglePaused() { 350 | if (!this.UIEnabled) { 351 | return; 352 | } 353 | if (this.paused) { 354 | this.play(); 355 | } else { 356 | this.pause(); 357 | } 358 | }, 359 | fastrewind() { 360 | let newTime = this.videoEl.currentTime - 5; 361 | if (newTime > -2) { 362 | this.videoEl.currentTime = Math.max(newTime, 0); 363 | } else { 364 | this.killContext(); 365 | this.playlist.selectPrevious(); 366 | clearInterval(this.prevNextInterval); 367 | this.prevNextInterval = null; 368 | } 369 | }, 370 | fastforward() { 371 | let newTime = this.videoEl.currentTime + 5; 372 | if (newTime < this.videoEl.duration + 2) { 373 | this.videoEl.currentTime = Math.min(this.videoEl.duration, newTime); 374 | } else { 375 | this.killContext(); 376 | this.playlist.selectNext(); 377 | clearInterval(this.prevNextInterval); 378 | this.prevNextInterval = null; 379 | } 380 | }, 381 | toggleShuffle() { 382 | this.playlist.toggleShuffle(); 383 | if (this.playlist.shuffle) { 384 | this.shuffleEl.classList.add("checked"); 385 | } else { 386 | this.shuffleEl.classList.remove("checked"); 387 | } 388 | }, 389 | changeLoopState(state) { 390 | if (!state) { 391 | this.loopState = this.loopState == 2 ? 0 : this.loopState + 1; 392 | } else { 393 | this.loopState = state; 394 | } 395 | 396 | switch (this.loopState) { 397 | // No loop 398 | case 0: 399 | this.playlist.loop = false; 400 | this.videoEl.loop = false; 401 | this.loopEl.classList.remove("checked", "loop-one"); 402 | break; 403 | 404 | // Loop playlist (default) 405 | case 1: 406 | this.playlist.loop = true; 407 | this.videoEl.loop = false; 408 | this.loopEl.classList.remove("loop-one"); 409 | this.loopEl.classList.add("checked"); 410 | break; 411 | 412 | // Loop one song 413 | case 2: 414 | this.playlist.loop = false; 415 | this.videoEl.loop = true; 416 | this.loopEl.classList.add("checked", "loop-one"); 417 | break; 418 | } 419 | }, 420 | toggleSpeedBtn() { 421 | if (this.speedBtnEl.classList.contains("checked")) { 422 | this.speedBtnEl.classList.remove("checked"); 423 | this.speedBtnEl.classList.add("not-checked"); 424 | } else { 425 | this.speedBtnEl.classList.remove("not-checked"); 426 | this.speedBtnEl.classList.add("checked"); 427 | } 428 | }, 429 | changeVolume(volume) { 430 | if (volume == this.videoEl.volume) { 431 | return; 432 | } 433 | this.videoEl.volume = volume * volume; 434 | if (volume == 0) { 435 | this.volumeIcon.className = "mute"; 436 | } else if (volume <= 0.5) { 437 | this.volumeIcon.className = "half"; 438 | } else { 439 | this.volumeIcon.className = ""; 440 | } 441 | this.volumeSlider.value = volume; 442 | this.volumeSlider.title = this.volumeIcon.title = Math.round(volume * volume * 1000) / 10 + "%"; 443 | if (this.settingsStore) { 444 | this.settingsStore.setItem("volume", volume); 445 | } 446 | }, 447 | changeSpeed(value) { 448 | var values = [.5, .75, 1, 1.25, 1.5, 2]; 449 | this.videoEl.playbackRate = values[value]; 450 | this.videoEl.defaultPlaybackRate = values[value]; 451 | }, 452 | 453 | /** Progress bar **/ 454 | setCurrentTime(time) { 455 | this.videoEl.currentTime = time; 456 | }, 457 | updateProgressBar() { 458 | var width = (this.videoEl.currentTime * document.body.clientWidth) 459 | / this.videoEl.duration; 460 | this.progressEl.style.width = width + "px"; 461 | }, 462 | onProgressClick(x) { 463 | var duration = (x * this.videoEl.duration) / document.body.clientWidth; 464 | this.setCurrentTime(duration); 465 | }, 466 | setProgressTooltip(x) { 467 | var duration = (x * this.videoEl.duration) / document.body.clientWidth; 468 | this.progressBar.title = this.getTooltip(duration); 469 | }, 470 | getTooltip(time) { 471 | var data = Utils.convertSecondsToDisplay(time); 472 | var display = ""; 473 | if (data.hours !== 0) { 474 | display = data.hours; 475 | } 476 | if (data.minutes < 10) { 477 | data.minutes = "0" + data.minutes; 478 | } 479 | if (data.seconds < 10) { 480 | data.seconds = "0" + data.seconds; 481 | } 482 | display = display + data.minutes + ":" + data.seconds; 483 | return display; 484 | }, 485 | 486 | /** Visualizer **/ 487 | recordContext() { 488 | this.visualize(this.analyser); 489 | }, 490 | killContext() { 491 | cancelAnimationFrame(this.animationId); 492 | this.animationId = null; 493 | var canvas = this.canvasEl; 494 | var ctx = canvas.getContext("2d"); 495 | ctx.clearRect(0, 0, canvas.width, canvas.height); 496 | }, 497 | visualize(analyser) { 498 | var that = this, 499 | canvas = this.canvasEl, 500 | cwidth = canvas.width, 501 | cheight = canvas.height - 2, 502 | meterWidth = 12, 503 | capHeight = 3, 504 | totalWidth = meterWidth + capHeight, 505 | capStyle = "#fff", 506 | meterNum = 600 / (meterWidth + capHeight), 507 | capYPositionArray = [], 508 | ctx = canvas.getContext("2d"); 509 | var drawMeter = function() { 510 | var array = new Uint8Array(analyser.frequencyBinCount); 511 | analyser.getByteFrequencyData(array); 512 | if (that.status === 0) { 513 | for (let i = array.length - 1; i >= 0; i--) { 514 | array[i] = 0; 515 | } 516 | var allCapsReachBottom = true; 517 | for (let i = capYPositionArray.length - 1; i >= 0; i--) { 518 | allCapsReachBottom = allCapsReachBottom && (capYPositionArray[i] === 0); 519 | } 520 | if (allCapsReachBottom) { 521 | cancelAnimationFrame(that.animationId); 522 | return; 523 | } 524 | } 525 | var step = Math.round(array.length / meterNum); 526 | ctx.clearRect(0, 0, cwidth, cheight); 527 | for (let i = 0; i < meterNum; i++) { 528 | var value = array[i * step]; 529 | if (capYPositionArray.length < Math.round(meterNum)) { 530 | capYPositionArray.push(value); 531 | } 532 | ctx.fillStyle = capStyle; 533 | if (value < capYPositionArray[i]) { 534 | ctx.fillRect(i * totalWidth, 535 | cheight - (--capYPositionArray[i]), 536 | meterWidth, capHeight); 537 | } else { 538 | ctx.fillRect(i * totalWidth, cheight - value, meterWidth, capHeight); 539 | capYPositionArray[i] = value; 540 | } 541 | ctx.fillStyle = getComputedStyle(document.documentElement) 542 | .getPropertyValue("--theme-highlight-color"); 543 | ctx.fillRect(i * totalWidth, cheight - value + capHeight, meterWidth, cheight); 544 | } 545 | that.animationId = requestAnimationFrame(drawMeter); 546 | }; 547 | if (!this.animationId) { 548 | this.animationId = requestAnimationFrame(drawMeter); 549 | } 550 | } 551 | }; 552 | 553 | window.addEventListener("load", function() { 554 | MediaPlayer.init(); 555 | }, false); 556 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@electron/asar@^3.2.1": 6 | version "3.2.1" 7 | resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.1.tgz#c4143896f3dd43b59a80a9c9068d76f77efb62ea" 8 | dependencies: 9 | chromium-pickle-js "^0.2.0" 10 | commander "^5.0.0" 11 | glob "^7.1.6" 12 | minimatch "^3.0.4" 13 | optionalDependencies: 14 | "@types/glob" "^7.1.1" 15 | 16 | "@electron/get@^2.0.0": 17 | version "2.0.2" 18 | resolved "https://registry.yarnpkg.com/@electron/get/-/get-2.0.2.tgz#ae2a967b22075e9c25aaf00d5941cd79c21efd7e" 19 | dependencies: 20 | debug "^4.1.1" 21 | env-paths "^2.2.0" 22 | fs-extra "^8.1.0" 23 | got "^11.8.5" 24 | progress "^2.0.3" 25 | semver "^6.2.0" 26 | sumchecker "^3.0.1" 27 | optionalDependencies: 28 | global-agent "^3.0.0" 29 | 30 | "@electron/notarize@^1.2.3": 31 | version "1.2.3" 32 | resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-1.2.3.tgz#38056a629e5a0b5fd56c975c4828c0f74285b644" 33 | dependencies: 34 | debug "^4.1.1" 35 | fs-extra "^9.0.1" 36 | 37 | "@electron/osx-sign@^1.0.1": 38 | version "1.0.1" 39 | resolved "https://registry.yarnpkg.com/@electron/osx-sign/-/osx-sign-1.0.1.tgz#ab4fceded7fed9f2f18c25650f46c1e3a6f17054" 40 | dependencies: 41 | compare-version "^0.1.2" 42 | debug "^4.3.4" 43 | fs-extra "^10.0.0" 44 | isbinaryfile "^4.0.8" 45 | minimist "^1.2.6" 46 | plist "^3.0.5" 47 | 48 | "@electron/universal@^1.3.2": 49 | version "1.3.3" 50 | resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.3.3.tgz#f22088dce7f2e808130fd1bbcd43925246adfa59" 51 | dependencies: 52 | "@electron/asar" "^3.2.1" 53 | "@malept/cross-spawn-promise" "^1.1.0" 54 | debug "^4.3.1" 55 | dir-compare "^2.4.0" 56 | fs-extra "^9.0.1" 57 | minimatch "^3.0.4" 58 | plist "^3.0.4" 59 | 60 | "@malept/cross-spawn-promise@^1.1.0": 61 | version "1.1.1" 62 | resolved "https://registry.yarnpkg.com/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz#504af200af6b98e198bce768bc1730c6936ae01d" 63 | dependencies: 64 | cross-spawn "^7.0.1" 65 | 66 | "@sindresorhus/is@^4.0.0": 67 | version "4.6.0" 68 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" 69 | 70 | "@szmarczak/http-timer@^4.0.5": 71 | version "4.0.6" 72 | resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" 73 | dependencies: 74 | defer-to-connect "^2.0.0" 75 | 76 | "@types/cacheable-request@^6.0.1": 77 | version "6.0.2" 78 | resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" 79 | dependencies: 80 | "@types/http-cache-semantics" "*" 81 | "@types/keyv" "*" 82 | "@types/node" "*" 83 | "@types/responselike" "*" 84 | 85 | "@types/glob@^7.1.1": 86 | version "7.2.0" 87 | resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" 88 | dependencies: 89 | "@types/minimatch" "*" 90 | "@types/node" "*" 91 | 92 | "@types/http-cache-semantics@*": 93 | version "4.0.1" 94 | resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" 95 | 96 | "@types/keyv@*": 97 | version "4.2.0" 98 | resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-4.2.0.tgz#65b97868ab757906f2dbb653590d7167ad023fa0" 99 | dependencies: 100 | keyv "*" 101 | 102 | "@types/minimatch@*": 103 | version "5.1.2" 104 | resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" 105 | 106 | "@types/node@*": 107 | version "18.11.8" 108 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.8.tgz#16d222a58d4363a2a359656dd20b28414de5d265" 109 | 110 | "@types/node@^16.11.26": 111 | version "16.18.3" 112 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc" 113 | 114 | "@types/responselike@*", "@types/responselike@^1.0.0": 115 | version "1.0.0" 116 | resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" 117 | dependencies: 118 | "@types/node" "*" 119 | 120 | "@types/yauzl@^2.9.1": 121 | version "2.10.0" 122 | resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599" 123 | dependencies: 124 | "@types/node" "*" 125 | 126 | abbrev@1: 127 | version "1.1.0" 128 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" 129 | 130 | ajv@^4.9.1: 131 | version "4.11.8" 132 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" 133 | dependencies: 134 | co "^4.6.0" 135 | json-stable-stringify "^1.0.1" 136 | 137 | asar@^0.11.0: 138 | version "0.11.0" 139 | resolved "https://registry.yarnpkg.com/asar/-/asar-0.11.0.tgz#b926e792c315f8c048c43371e325b09c97a76464" 140 | dependencies: 141 | chromium-pickle-js "^0.1.0" 142 | commander "^2.9.0" 143 | cuint "^0.2.1" 144 | glob "^6.0.4" 145 | minimatch "^3.0.0" 146 | mkdirp "^0.5.0" 147 | mksnapshot "^0.3.0" 148 | 149 | asn1@~0.2.3: 150 | version "0.2.4" 151 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 152 | dependencies: 153 | safer-buffer "~2.1.0" 154 | 155 | assert-plus@1.0.0, assert-plus@^1.0.0: 156 | version "1.0.0" 157 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 158 | 159 | assert-plus@^0.2.0: 160 | version "0.2.0" 161 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 162 | 163 | asynckit@^0.4.0: 164 | version "0.4.0" 165 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 166 | 167 | at-least-node@^1.0.0: 168 | version "1.0.0" 169 | resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" 170 | 171 | author-regex@^1.0.0: 172 | version "1.0.0" 173 | resolved "https://registry.yarnpkg.com/author-regex/-/author-regex-1.0.0.tgz#d08885be6b9bbf9439fe087c76287245f0a81450" 174 | 175 | aws-sign2@~0.6.0: 176 | version "0.6.0" 177 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 178 | 179 | aws4@^1.2.1: 180 | version "1.6.0" 181 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" 182 | 183 | balanced-match@^1.0.0: 184 | version "1.0.2" 185 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 186 | 187 | base64-js@^1.5.1: 188 | version "1.5.1" 189 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 190 | 191 | bcrypt-pbkdf@^1.0.0: 192 | version "1.0.2" 193 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 194 | dependencies: 195 | tweetnacl "^0.14.3" 196 | 197 | binary@^0.3.0: 198 | version "0.3.0" 199 | resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" 200 | dependencies: 201 | buffers "~0.1.1" 202 | chainsaw "~0.1.0" 203 | 204 | bluebird@^3.1.1, bluebird@^3.3.4: 205 | version "3.5.0" 206 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" 207 | 208 | boolean@^3.0.1: 209 | version "3.0.1" 210 | resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.1.tgz#35ecf2b4a2ee191b0b44986f14eb5f052a5cbb4f" 211 | 212 | boom@2.x.x: 213 | version "2.10.1" 214 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 215 | dependencies: 216 | hoek "2.x.x" 217 | 218 | brace-expansion@^1.1.7: 219 | version "1.1.11" 220 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 221 | dependencies: 222 | balanced-match "^1.0.0" 223 | concat-map "0.0.1" 224 | 225 | buffer-crc32@~0.2.3: 226 | version "0.2.13" 227 | resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" 228 | 229 | buffer-equal@1.0.0: 230 | version "1.0.0" 231 | resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" 232 | 233 | buffers@~0.1.1: 234 | version "0.1.1" 235 | resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" 236 | 237 | builtin-modules@^1.0.0: 238 | version "1.1.1" 239 | resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" 240 | 241 | cacheable-lookup@^5.0.3: 242 | version "5.0.4" 243 | resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" 244 | 245 | cacheable-request@^7.0.2: 246 | version "7.0.2" 247 | resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" 248 | dependencies: 249 | clone-response "^1.0.2" 250 | get-stream "^5.1.0" 251 | http-cache-semantics "^4.0.0" 252 | keyv "^4.0.0" 253 | lowercase-keys "^2.0.0" 254 | normalize-url "^6.0.1" 255 | responselike "^2.0.0" 256 | 257 | caseless@~0.12.0: 258 | version "0.12.0" 259 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 260 | 261 | chainsaw@~0.1.0: 262 | version "0.1.0" 263 | resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" 264 | dependencies: 265 | traverse ">=0.3.0 <0.4" 266 | 267 | chromium-pickle-js@^0.1.0: 268 | version "0.1.0" 269 | resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.1.0.tgz#1d48b107d82126a2f3e211c2ea25f803ba551b21" 270 | 271 | chromium-pickle-js@^0.2.0: 272 | version "0.2.0" 273 | resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205" 274 | 275 | clone-response@^1.0.2: 276 | version "1.0.2" 277 | resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" 278 | dependencies: 279 | mimic-response "^1.0.0" 280 | 281 | co@^4.6.0: 282 | version "4.6.0" 283 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 284 | 285 | colors@1.0.3: 286 | version "1.0.3" 287 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" 288 | 289 | combined-stream@^1.0.5, combined-stream@~1.0.5: 290 | version "1.0.5" 291 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 292 | dependencies: 293 | delayed-stream "~1.0.0" 294 | 295 | commander@2.9.0: 296 | version "2.9.0" 297 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" 298 | dependencies: 299 | graceful-readlink ">= 1.0.0" 300 | 301 | commander@^2.9.0: 302 | version "2.11.0" 303 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" 304 | 305 | commander@^5.0.0: 306 | version "5.1.0" 307 | resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" 308 | 309 | compare-version@^0.1.2: 310 | version "0.1.2" 311 | resolved "https://registry.yarnpkg.com/compare-version/-/compare-version-0.1.2.tgz#0162ec2d9351f5ddd59a9202cba935366a725080" 312 | 313 | concat-map@0.0.1: 314 | version "0.0.1" 315 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 316 | 317 | core-util-is@1.0.2, core-util-is@~1.0.0: 318 | version "1.0.2" 319 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 320 | 321 | cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0: 322 | version "1.2.0" 323 | resolved "https://registry.yarnpkg.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz#46253b0f497676e766faf4a7061004618b5ac5ec" 324 | dependencies: 325 | "@malept/cross-spawn-promise" "^1.1.0" 326 | is-wsl "^2.2.0" 327 | which "^2.0.2" 328 | 329 | cross-spawn@^7.0.1: 330 | version "7.0.3" 331 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 332 | dependencies: 333 | path-key "^3.1.0" 334 | shebang-command "^2.0.0" 335 | which "^2.0.1" 336 | 337 | cryptiles@2.x.x: 338 | version "2.0.5" 339 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 340 | dependencies: 341 | boom "2.x.x" 342 | 343 | cuint@^0.2.1: 344 | version "0.2.2" 345 | resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" 346 | 347 | dashdash@^1.12.0: 348 | version "1.14.1" 349 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 350 | dependencies: 351 | assert-plus "^1.0.0" 352 | 353 | debug@^2.2.0: 354 | version "2.6.8" 355 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" 356 | dependencies: 357 | ms "2.0.0" 358 | 359 | debug@^3.1.0: 360 | version "3.2.7" 361 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" 362 | dependencies: 363 | ms "^2.1.1" 364 | 365 | debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: 366 | version "4.3.4" 367 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 368 | dependencies: 369 | ms "2.1.2" 370 | 371 | decompress-response@^6.0.0: 372 | version "6.0.0" 373 | resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" 374 | dependencies: 375 | mimic-response "^3.1.0" 376 | 377 | decompress-zip@0.3.0: 378 | version "0.3.0" 379 | resolved "https://registry.yarnpkg.com/decompress-zip/-/decompress-zip-0.3.0.tgz#ae3bcb7e34c65879adfe77e19c30f86602b4bdb0" 380 | dependencies: 381 | binary "^0.3.0" 382 | graceful-fs "^4.1.3" 383 | mkpath "^0.1.0" 384 | nopt "^3.0.1" 385 | q "^1.1.2" 386 | readable-stream "^1.1.8" 387 | touch "0.0.3" 388 | 389 | defer-to-connect@^2.0.0: 390 | version "2.0.1" 391 | resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" 392 | 393 | define-properties@^1.1.3: 394 | version "1.1.3" 395 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 396 | dependencies: 397 | object-keys "^1.0.12" 398 | 399 | delayed-stream@~1.0.0: 400 | version "1.0.0" 401 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 402 | 403 | detect-node@^2.0.4: 404 | version "2.0.4" 405 | resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" 406 | 407 | dir-compare@^2.4.0: 408 | version "2.4.0" 409 | resolved "https://registry.yarnpkg.com/dir-compare/-/dir-compare-2.4.0.tgz#785c41dc5f645b34343a4eafc50b79bac7f11631" 410 | dependencies: 411 | buffer-equal "1.0.0" 412 | colors "1.0.3" 413 | commander "2.9.0" 414 | minimatch "3.0.4" 415 | 416 | ecc-jsbn@~0.1.1: 417 | version "0.1.2" 418 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 419 | dependencies: 420 | jsbn "~0.1.0" 421 | safer-buffer "^2.1.0" 422 | 423 | electron-packager@^17.1.0: 424 | version "17.1.0" 425 | resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-17.1.0.tgz#e9653aa57dc523cdb3e0de7ec1a8e0112a1befec" 426 | dependencies: 427 | "@electron/asar" "^3.2.1" 428 | "@electron/get" "^2.0.0" 429 | "@electron/notarize" "^1.2.3" 430 | "@electron/osx-sign" "^1.0.1" 431 | "@electron/universal" "^1.3.2" 432 | cross-spawn-windows-exe "^1.2.0" 433 | debug "^4.0.1" 434 | extract-zip "^2.0.0" 435 | filenamify "^4.1.0" 436 | fs-extra "^10.1.0" 437 | galactus "^0.2.1" 438 | get-package-info "^1.0.0" 439 | junk "^3.1.0" 440 | parse-author "^2.0.0" 441 | plist "^3.0.0" 442 | rcedit "^3.0.1" 443 | resolve "^1.1.6" 444 | semver "^7.1.3" 445 | yargs-parser "^21.1.1" 446 | 447 | electron-winstaller@^2.5.2: 448 | version "2.6.3" 449 | resolved "https://registry.yarnpkg.com/electron-winstaller/-/electron-winstaller-2.6.3.tgz#d54f77c0cececc4fc55eeb5968d345cf69645ea4" 450 | dependencies: 451 | asar "^0.11.0" 452 | bluebird "^3.3.4" 453 | debug "^2.2.0" 454 | fs-extra "^0.26.7" 455 | lodash.template "^4.2.2" 456 | temp "^0.8.3" 457 | 458 | electron@^22.0.0: 459 | version "22.0.0" 460 | resolved "https://registry.yarnpkg.com/electron/-/electron-22.0.0.tgz#ef84ab9cf23aa3f8c2f42a1e8e000ad7fd941058" 461 | dependencies: 462 | "@electron/get" "^2.0.0" 463 | "@types/node" "^16.11.26" 464 | extract-zip "^2.0.1" 465 | 466 | end-of-stream@^1.1.0: 467 | version "1.4.4" 468 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 469 | dependencies: 470 | once "^1.4.0" 471 | 472 | env-paths@^2.2.0: 473 | version "2.2.0" 474 | resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" 475 | 476 | error-ex@^1.2.0: 477 | version "1.3.1" 478 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" 479 | dependencies: 480 | is-arrayish "^0.2.1" 481 | 482 | es6-error@^4.1.1: 483 | version "4.1.1" 484 | resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" 485 | 486 | escape-string-regexp@^1.0.2: 487 | version "1.0.5" 488 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 489 | 490 | escape-string-regexp@^4.0.0: 491 | version "4.0.0" 492 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 493 | 494 | extend@~3.0.0: 495 | version "3.0.2" 496 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 497 | 498 | extract-zip@^2.0.0, extract-zip@^2.0.1: 499 | version "2.0.1" 500 | resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" 501 | dependencies: 502 | debug "^4.1.1" 503 | get-stream "^5.1.0" 504 | yauzl "^2.10.0" 505 | optionalDependencies: 506 | "@types/yauzl" "^2.9.1" 507 | 508 | extsprintf@1.3.0: 509 | version "1.3.0" 510 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 511 | 512 | extsprintf@^1.2.0: 513 | version "1.4.1" 514 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" 515 | 516 | fd-slicer@~1.1.0: 517 | version "1.1.0" 518 | resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" 519 | dependencies: 520 | pend "~1.2.0" 521 | 522 | filename-reserved-regex@^2.0.0: 523 | version "2.0.0" 524 | resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" 525 | 526 | filenamify@^4.1.0: 527 | version "4.3.0" 528 | resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-4.3.0.tgz#62391cb58f02b09971c9d4f9d63b3cf9aba03106" 529 | dependencies: 530 | filename-reserved-regex "^2.0.0" 531 | strip-outer "^1.0.1" 532 | trim-repeated "^1.0.0" 533 | 534 | find-up@^2.0.0: 535 | version "2.1.0" 536 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" 537 | dependencies: 538 | locate-path "^2.0.0" 539 | 540 | flora-colossus@^1.0.0: 541 | version "1.0.1" 542 | resolved "https://registry.yarnpkg.com/flora-colossus/-/flora-colossus-1.0.1.tgz#aba198425a8185341e64f9d2a6a96fd9a3cbdb93" 543 | dependencies: 544 | debug "^4.1.1" 545 | fs-extra "^7.0.0" 546 | 547 | forever-agent@~0.6.1: 548 | version "0.6.1" 549 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 550 | 551 | form-data@~2.1.1: 552 | version "2.1.4" 553 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" 554 | dependencies: 555 | asynckit "^0.4.0" 556 | combined-stream "^1.0.5" 557 | mime-types "^2.1.12" 558 | 559 | fs-extra@0.26.7, fs-extra@^0.26.7: 560 | version "0.26.7" 561 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" 562 | dependencies: 563 | graceful-fs "^4.1.2" 564 | jsonfile "^2.1.0" 565 | klaw "^1.0.0" 566 | path-is-absolute "^1.0.0" 567 | rimraf "^2.2.8" 568 | 569 | fs-extra@^10.0.0, fs-extra@^10.1.0: 570 | version "10.1.0" 571 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" 572 | dependencies: 573 | graceful-fs "^4.2.0" 574 | jsonfile "^6.0.1" 575 | universalify "^2.0.0" 576 | 577 | fs-extra@^4.0.0: 578 | version "4.0.3" 579 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" 580 | dependencies: 581 | graceful-fs "^4.1.2" 582 | jsonfile "^4.0.0" 583 | universalify "^0.1.0" 584 | 585 | fs-extra@^7.0.0: 586 | version "7.0.1" 587 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" 588 | dependencies: 589 | graceful-fs "^4.1.2" 590 | jsonfile "^4.0.0" 591 | universalify "^0.1.0" 592 | 593 | fs-extra@^8.1.0: 594 | version "8.1.0" 595 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" 596 | dependencies: 597 | graceful-fs "^4.2.0" 598 | jsonfile "^4.0.0" 599 | universalify "^0.1.0" 600 | 601 | fs-extra@^9.0.1: 602 | version "9.1.0" 603 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" 604 | dependencies: 605 | at-least-node "^1.0.0" 606 | graceful-fs "^4.2.0" 607 | jsonfile "^6.0.1" 608 | universalify "^2.0.0" 609 | 610 | fs.realpath@^1.0.0: 611 | version "1.0.0" 612 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 613 | 614 | galactus@^0.2.1: 615 | version "0.2.1" 616 | resolved "https://registry.yarnpkg.com/galactus/-/galactus-0.2.1.tgz#cbed2d20a40c1f5679a35908e2b9415733e78db9" 617 | dependencies: 618 | debug "^3.1.0" 619 | flora-colossus "^1.0.0" 620 | fs-extra "^4.0.0" 621 | 622 | get-package-info@^1.0.0: 623 | version "1.0.0" 624 | resolved "https://registry.yarnpkg.com/get-package-info/-/get-package-info-1.0.0.tgz#6432796563e28113cd9474dbbd00052985a4999c" 625 | dependencies: 626 | bluebird "^3.1.1" 627 | debug "^2.2.0" 628 | lodash.get "^4.0.0" 629 | read-pkg-up "^2.0.0" 630 | 631 | get-stream@^5.1.0: 632 | version "5.2.0" 633 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" 634 | dependencies: 635 | pump "^3.0.0" 636 | 637 | getpass@^0.1.1: 638 | version "0.1.7" 639 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 640 | dependencies: 641 | assert-plus "^1.0.0" 642 | 643 | glob@^6.0.4: 644 | version "6.0.4" 645 | resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" 646 | dependencies: 647 | inflight "^1.0.4" 648 | inherits "2" 649 | minimatch "2 || 3" 650 | once "^1.3.0" 651 | path-is-absolute "^1.0.0" 652 | 653 | glob@^7.0.5: 654 | version "7.1.2" 655 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 656 | dependencies: 657 | fs.realpath "^1.0.0" 658 | inflight "^1.0.4" 659 | inherits "2" 660 | minimatch "^3.0.4" 661 | once "^1.3.0" 662 | path-is-absolute "^1.0.0" 663 | 664 | glob@^7.1.6: 665 | version "7.2.3" 666 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" 667 | dependencies: 668 | fs.realpath "^1.0.0" 669 | inflight "^1.0.4" 670 | inherits "2" 671 | minimatch "^3.1.1" 672 | once "^1.3.0" 673 | path-is-absolute "^1.0.0" 674 | 675 | global-agent@^3.0.0: 676 | version "3.0.0" 677 | resolved "https://registry.yarnpkg.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" 678 | dependencies: 679 | boolean "^3.0.1" 680 | es6-error "^4.1.1" 681 | matcher "^3.0.0" 682 | roarr "^2.15.3" 683 | semver "^7.3.2" 684 | serialize-error "^7.0.1" 685 | 686 | globalthis@^1.0.1: 687 | version "1.0.1" 688 | resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.1.tgz#40116f5d9c071f9e8fb0037654df1ab3a83b7ef9" 689 | dependencies: 690 | define-properties "^1.1.3" 691 | 692 | got@^11.8.5: 693 | version "11.8.5" 694 | resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" 695 | dependencies: 696 | "@sindresorhus/is" "^4.0.0" 697 | "@szmarczak/http-timer" "^4.0.5" 698 | "@types/cacheable-request" "^6.0.1" 699 | "@types/responselike" "^1.0.0" 700 | cacheable-lookup "^5.0.3" 701 | cacheable-request "^7.0.2" 702 | decompress-response "^6.0.0" 703 | http2-wrapper "^1.0.0-beta.5.2" 704 | lowercase-keys "^2.0.0" 705 | p-cancelable "^2.0.0" 706 | responselike "^2.0.0" 707 | 708 | graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9: 709 | version "4.1.11" 710 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 711 | 712 | graceful-fs@^4.2.0: 713 | version "4.2.4" 714 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" 715 | 716 | "graceful-readlink@>= 1.0.0": 717 | version "1.0.1" 718 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 719 | 720 | har-schema@^1.0.5: 721 | version "1.0.5" 722 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" 723 | 724 | har-validator@~4.2.1: 725 | version "4.2.1" 726 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" 727 | dependencies: 728 | ajv "^4.9.1" 729 | har-schema "^1.0.5" 730 | 731 | hawk@~3.1.3: 732 | version "3.1.3" 733 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 734 | dependencies: 735 | boom "2.x.x" 736 | cryptiles "2.x.x" 737 | hoek "2.x.x" 738 | sntp "1.x.x" 739 | 740 | hoek@2.x.x: 741 | version "2.16.3" 742 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 743 | 744 | hosted-git-info@^2.1.4: 745 | version "2.8.9" 746 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" 747 | 748 | http-cache-semantics@^4.0.0: 749 | version "4.1.0" 750 | resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" 751 | 752 | http-signature@~1.1.0: 753 | version "1.1.1" 754 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 755 | dependencies: 756 | assert-plus "^0.2.0" 757 | jsprim "^1.2.2" 758 | sshpk "^1.7.0" 759 | 760 | http2-wrapper@^1.0.0-beta.5.2: 761 | version "1.0.3" 762 | resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" 763 | dependencies: 764 | quick-lru "^5.1.1" 765 | resolve-alpn "^1.0.0" 766 | 767 | id3js@^1.1.3: 768 | version "1.1.3" 769 | resolved "https://registry.yarnpkg.com/id3js/-/id3js-1.1.3.tgz#6bb3442640a323606a7dbeea20f7cb935ea17066" 770 | 771 | inflight@^1.0.4: 772 | version "1.0.6" 773 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 774 | dependencies: 775 | once "^1.3.0" 776 | wrappy "1" 777 | 778 | inherits@2, inherits@~2.0.1: 779 | version "2.0.3" 780 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 781 | 782 | is-arrayish@^0.2.1: 783 | version "0.2.1" 784 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 785 | 786 | is-builtin-module@^1.0.0: 787 | version "1.0.0" 788 | resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" 789 | dependencies: 790 | builtin-modules "^1.0.0" 791 | 792 | is-docker@^2.0.0: 793 | version "2.2.1" 794 | resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" 795 | 796 | is-typedarray@~1.0.0: 797 | version "1.0.0" 798 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 799 | 800 | is-wsl@^2.2.0: 801 | version "2.2.0" 802 | resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" 803 | dependencies: 804 | is-docker "^2.0.0" 805 | 806 | isarray@0.0.1: 807 | version "0.0.1" 808 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 809 | 810 | isbinaryfile@^4.0.8: 811 | version "4.0.10" 812 | resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" 813 | 814 | isexe@^2.0.0: 815 | version "2.0.0" 816 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 817 | 818 | isstream@~0.1.2: 819 | version "0.1.2" 820 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 821 | 822 | jsbn@~0.1.0: 823 | version "0.1.1" 824 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 825 | 826 | jsmediatags@^3.4.0: 827 | version "3.6.2" 828 | resolved "https://registry.yarnpkg.com/jsmediatags/-/jsmediatags-3.6.2.tgz#acd5b80374ff7fb557cdad37c216981854cd2452" 829 | dependencies: 830 | xhr2 "~0.1.3" 831 | 832 | json-buffer@3.0.1: 833 | version "3.0.1" 834 | resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" 835 | 836 | json-schema@0.4.0: 837 | version "0.4.0" 838 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5" 839 | 840 | json-stable-stringify@^1.0.1: 841 | version "1.0.1" 842 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 843 | dependencies: 844 | jsonify "~0.0.0" 845 | 846 | json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: 847 | version "5.0.1" 848 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 849 | 850 | jsonfile@^2.1.0: 851 | version "2.4.0" 852 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" 853 | optionalDependencies: 854 | graceful-fs "^4.1.6" 855 | 856 | jsonfile@^4.0.0: 857 | version "4.0.0" 858 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 859 | optionalDependencies: 860 | graceful-fs "^4.1.6" 861 | 862 | jsonfile@^6.0.1: 863 | version "6.1.0" 864 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" 865 | dependencies: 866 | universalify "^2.0.0" 867 | optionalDependencies: 868 | graceful-fs "^4.1.6" 869 | 870 | jsonify@~0.0.0: 871 | version "0.0.0" 872 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 873 | 874 | jsprim@^1.2.2: 875 | version "1.4.2" 876 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb" 877 | dependencies: 878 | assert-plus "1.0.0" 879 | extsprintf "1.3.0" 880 | json-schema "0.4.0" 881 | verror "1.10.0" 882 | 883 | junk@^3.1.0: 884 | version "3.1.0" 885 | resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" 886 | 887 | keyv@*, keyv@^4.0.0: 888 | version "4.5.0" 889 | resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.0.tgz#dbce9ade79610b6e641a9a65f2f6499ba06b9bc6" 890 | dependencies: 891 | json-buffer "3.0.1" 892 | 893 | klaw@^1.0.0: 894 | version "1.3.1" 895 | resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" 896 | optionalDependencies: 897 | graceful-fs "^4.1.9" 898 | 899 | load-json-file@^2.0.0: 900 | version "2.0.0" 901 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" 902 | dependencies: 903 | graceful-fs "^4.1.2" 904 | parse-json "^2.2.0" 905 | pify "^2.0.0" 906 | strip-bom "^3.0.0" 907 | 908 | locate-path@^2.0.0: 909 | version "2.0.0" 910 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" 911 | dependencies: 912 | p-locate "^2.0.0" 913 | path-exists "^3.0.0" 914 | 915 | lodash._reinterpolate@^3.0.0: 916 | version "3.0.0" 917 | resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" 918 | 919 | lodash.get@^4.0.0: 920 | version "4.4.2" 921 | resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" 922 | 923 | lodash.template@^4.2.2: 924 | version "4.5.0" 925 | resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" 926 | dependencies: 927 | lodash._reinterpolate "^3.0.0" 928 | lodash.templatesettings "^4.0.0" 929 | 930 | lodash.templatesettings@^4.0.0: 931 | version "4.2.0" 932 | resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" 933 | dependencies: 934 | lodash._reinterpolate "^3.0.0" 935 | 936 | lowercase-keys@^2.0.0: 937 | version "2.0.0" 938 | resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" 939 | 940 | lru-cache@^6.0.0: 941 | version "6.0.0" 942 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 943 | dependencies: 944 | yallist "^4.0.0" 945 | 946 | matcher@^3.0.0: 947 | version "3.0.0" 948 | resolved "https://registry.yarnpkg.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" 949 | dependencies: 950 | escape-string-regexp "^4.0.0" 951 | 952 | mime-db@~1.27.0: 953 | version "1.27.0" 954 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" 955 | 956 | mime-types@^2.1.12, mime-types@^2.1.15, mime-types@~2.1.7: 957 | version "2.1.15" 958 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" 959 | dependencies: 960 | mime-db "~1.27.0" 961 | 962 | mimic-response@^1.0.0: 963 | version "1.0.1" 964 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" 965 | 966 | mimic-response@^3.1.0: 967 | version "3.1.0" 968 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" 969 | 970 | "minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.4, minimatch@^3.1.1: 971 | version "3.1.2" 972 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 973 | dependencies: 974 | brace-expansion "^1.1.7" 975 | 976 | minimatch@3.0.4: 977 | version "3.0.4" 978 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 979 | dependencies: 980 | brace-expansion "^1.1.7" 981 | 982 | minimist@0.0.8: 983 | version "0.0.8" 984 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 985 | 986 | minimist@^1.2.6: 987 | version "1.2.7" 988 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" 989 | 990 | mkdirp@^0.5.0: 991 | version "0.5.1" 992 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 993 | dependencies: 994 | minimist "0.0.8" 995 | 996 | mkpath@^0.1.0: 997 | version "0.1.0" 998 | resolved "https://registry.yarnpkg.com/mkpath/-/mkpath-0.1.0.tgz#7554a6f8d871834cc97b5462b122c4c124d6de91" 999 | 1000 | mksnapshot@^0.3.0: 1001 | version "0.3.1" 1002 | resolved "https://registry.yarnpkg.com/mksnapshot/-/mksnapshot-0.3.1.tgz#2501c05657436d742ce958a4ff92c77e40dd37e6" 1003 | dependencies: 1004 | decompress-zip "0.3.0" 1005 | fs-extra "0.26.7" 1006 | request "^2.79.0" 1007 | 1008 | ms@2.0.0: 1009 | version "2.0.0" 1010 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 1011 | 1012 | ms@2.1.2: 1013 | version "2.1.2" 1014 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 1015 | 1016 | ms@^2.1.1: 1017 | version "2.1.3" 1018 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 1019 | 1020 | nopt@^3.0.1: 1021 | version "3.0.6" 1022 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" 1023 | dependencies: 1024 | abbrev "1" 1025 | 1026 | nopt@~1.0.10: 1027 | version "1.0.10" 1028 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" 1029 | dependencies: 1030 | abbrev "1" 1031 | 1032 | normalize-package-data@^2.3.2: 1033 | version "2.4.0" 1034 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" 1035 | dependencies: 1036 | hosted-git-info "^2.1.4" 1037 | is-builtin-module "^1.0.0" 1038 | semver "2 || 3 || 4 || 5" 1039 | validate-npm-package-license "^3.0.1" 1040 | 1041 | normalize-url@^6.0.1: 1042 | version "6.1.0" 1043 | resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" 1044 | 1045 | oauth-sign@~0.8.1: 1046 | version "0.8.2" 1047 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 1048 | 1049 | object-keys@^1.0.12: 1050 | version "1.1.1" 1051 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 1052 | 1053 | once@^1.3.0, once@^1.3.1, once@^1.4.0: 1054 | version "1.4.0" 1055 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 1056 | dependencies: 1057 | wrappy "1" 1058 | 1059 | os-tmpdir@^1.0.0: 1060 | version "1.0.2" 1061 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 1062 | 1063 | p-cancelable@^2.0.0: 1064 | version "2.1.1" 1065 | resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" 1066 | 1067 | p-limit@^1.1.0: 1068 | version "1.1.0" 1069 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" 1070 | 1071 | p-locate@^2.0.0: 1072 | version "2.0.0" 1073 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" 1074 | dependencies: 1075 | p-limit "^1.1.0" 1076 | 1077 | parse-author@^2.0.0: 1078 | version "2.0.0" 1079 | resolved "https://registry.yarnpkg.com/parse-author/-/parse-author-2.0.0.tgz#d3460bf1ddd0dfaeed42da754242e65fb684a81f" 1080 | dependencies: 1081 | author-regex "^1.0.0" 1082 | 1083 | parse-json@^2.2.0: 1084 | version "2.2.0" 1085 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" 1086 | dependencies: 1087 | error-ex "^1.2.0" 1088 | 1089 | path-exists@^3.0.0: 1090 | version "3.0.0" 1091 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 1092 | 1093 | path-is-absolute@^1.0.0: 1094 | version "1.0.1" 1095 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1096 | 1097 | path-key@^3.1.0: 1098 | version "3.1.1" 1099 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 1100 | 1101 | path-parse@^1.0.5: 1102 | version "1.0.7" 1103 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 1104 | 1105 | path-type@^2.0.0: 1106 | version "2.0.0" 1107 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" 1108 | dependencies: 1109 | pify "^2.0.0" 1110 | 1111 | pend@~1.2.0: 1112 | version "1.2.0" 1113 | resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" 1114 | 1115 | performance-now@^0.2.0: 1116 | version "0.2.0" 1117 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" 1118 | 1119 | pify@^2.0.0: 1120 | version "2.3.0" 1121 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 1122 | 1123 | plist@^3.0.0, plist@^3.0.4, plist@^3.0.5: 1124 | version "3.0.6" 1125 | resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.6.tgz#7cfb68a856a7834bca6dbfe3218eb9c7740145d3" 1126 | dependencies: 1127 | base64-js "^1.5.1" 1128 | xmlbuilder "^15.1.1" 1129 | 1130 | progress@^2.0.3: 1131 | version "2.0.3" 1132 | resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" 1133 | 1134 | pump@^3.0.0: 1135 | version "3.0.0" 1136 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 1137 | dependencies: 1138 | end-of-stream "^1.1.0" 1139 | once "^1.3.1" 1140 | 1141 | punycode@^1.4.1: 1142 | version "1.4.1" 1143 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 1144 | 1145 | q@^1.1.2: 1146 | version "1.5.0" 1147 | resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" 1148 | 1149 | qs@~6.4.0: 1150 | version "6.4.1" 1151 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.1.tgz#2bad97710a5b661c366b378b1e3a44a592ff45e6" 1152 | 1153 | quick-lru@^5.1.1: 1154 | version "5.1.1" 1155 | resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" 1156 | 1157 | rcedit@^3.0.1: 1158 | version "3.0.1" 1159 | resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-3.0.1.tgz#ae21b43e49c075f4d84df1929832a12c302f3c90" 1160 | dependencies: 1161 | cross-spawn-windows-exe "^1.1.0" 1162 | 1163 | read-pkg-up@^2.0.0: 1164 | version "2.0.0" 1165 | resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" 1166 | dependencies: 1167 | find-up "^2.0.0" 1168 | read-pkg "^2.0.0" 1169 | 1170 | read-pkg@^2.0.0: 1171 | version "2.0.0" 1172 | resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" 1173 | dependencies: 1174 | load-json-file "^2.0.0" 1175 | normalize-package-data "^2.3.2" 1176 | path-type "^2.0.0" 1177 | 1178 | readable-stream@^1.1.8: 1179 | version "1.1.14" 1180 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 1181 | dependencies: 1182 | core-util-is "~1.0.0" 1183 | inherits "~2.0.1" 1184 | isarray "0.0.1" 1185 | string_decoder "~0.10.x" 1186 | 1187 | request@^2.79.0: 1188 | version "2.81.0" 1189 | resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" 1190 | dependencies: 1191 | aws-sign2 "~0.6.0" 1192 | aws4 "^1.2.1" 1193 | caseless "~0.12.0" 1194 | combined-stream "~1.0.5" 1195 | extend "~3.0.0" 1196 | forever-agent "~0.6.1" 1197 | form-data "~2.1.1" 1198 | har-validator "~4.2.1" 1199 | hawk "~3.1.3" 1200 | http-signature "~1.1.0" 1201 | is-typedarray "~1.0.0" 1202 | isstream "~0.1.2" 1203 | json-stringify-safe "~5.0.1" 1204 | mime-types "~2.1.7" 1205 | oauth-sign "~0.8.1" 1206 | performance-now "^0.2.0" 1207 | qs "~6.4.0" 1208 | safe-buffer "^5.0.1" 1209 | stringstream "~0.0.4" 1210 | tough-cookie "~2.3.0" 1211 | tunnel-agent "^0.6.0" 1212 | uuid "^3.0.0" 1213 | 1214 | resolve-alpn@^1.0.0: 1215 | version "1.2.1" 1216 | resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" 1217 | 1218 | resolve@^1.1.6: 1219 | version "1.3.3" 1220 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" 1221 | dependencies: 1222 | path-parse "^1.0.5" 1223 | 1224 | responselike@^2.0.0: 1225 | version "2.0.1" 1226 | resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" 1227 | dependencies: 1228 | lowercase-keys "^2.0.0" 1229 | 1230 | rimraf@^2.2.8: 1231 | version "2.6.1" 1232 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" 1233 | dependencies: 1234 | glob "^7.0.5" 1235 | 1236 | rimraf@~2.2.6: 1237 | version "2.2.8" 1238 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" 1239 | 1240 | roarr@^2.15.3: 1241 | version "2.15.4" 1242 | resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" 1243 | dependencies: 1244 | boolean "^3.0.1" 1245 | detect-node "^2.0.4" 1246 | globalthis "^1.0.1" 1247 | json-stringify-safe "^5.0.1" 1248 | semver-compare "^1.0.0" 1249 | sprintf-js "^1.1.2" 1250 | 1251 | safe-buffer@^5.0.1: 1252 | version "5.1.1" 1253 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 1254 | 1255 | safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 1256 | version "2.1.2" 1257 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1258 | 1259 | semver-compare@^1.0.0: 1260 | version "1.0.0" 1261 | resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" 1262 | 1263 | "semver@2 || 3 || 4 || 5": 1264 | version "5.3.0" 1265 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" 1266 | 1267 | semver@^6.2.0: 1268 | version "6.3.0" 1269 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 1270 | 1271 | semver@^7.1.3, semver@^7.3.2: 1272 | version "7.3.8" 1273 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" 1274 | dependencies: 1275 | lru-cache "^6.0.0" 1276 | 1277 | serialize-error@^7.0.1: 1278 | version "7.0.1" 1279 | resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" 1280 | dependencies: 1281 | type-fest "^0.13.1" 1282 | 1283 | shebang-command@^2.0.0: 1284 | version "2.0.0" 1285 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 1286 | dependencies: 1287 | shebang-regex "^3.0.0" 1288 | 1289 | shebang-regex@^3.0.0: 1290 | version "3.0.0" 1291 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 1292 | 1293 | sntp@1.x.x: 1294 | version "1.0.9" 1295 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 1296 | dependencies: 1297 | hoek "2.x.x" 1298 | 1299 | spdx-correct@~1.0.0: 1300 | version "1.0.2" 1301 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" 1302 | dependencies: 1303 | spdx-license-ids "^1.0.2" 1304 | 1305 | spdx-expression-parse@~1.0.0: 1306 | version "1.0.4" 1307 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" 1308 | 1309 | spdx-license-ids@^1.0.2: 1310 | version "1.2.2" 1311 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" 1312 | 1313 | sprintf-js@^1.1.2: 1314 | version "1.1.2" 1315 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" 1316 | 1317 | sshpk@^1.7.0: 1318 | version "1.16.1" 1319 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 1320 | dependencies: 1321 | asn1 "~0.2.3" 1322 | assert-plus "^1.0.0" 1323 | bcrypt-pbkdf "^1.0.0" 1324 | dashdash "^1.12.0" 1325 | ecc-jsbn "~0.1.1" 1326 | getpass "^0.1.1" 1327 | jsbn "~0.1.0" 1328 | safer-buffer "^2.0.2" 1329 | tweetnacl "~0.14.0" 1330 | 1331 | string_decoder@~0.10.x: 1332 | version "0.10.31" 1333 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 1334 | 1335 | stringstream@~0.0.4: 1336 | version "0.0.6" 1337 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" 1338 | 1339 | strip-bom@^3.0.0: 1340 | version "3.0.0" 1341 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 1342 | 1343 | strip-outer@^1.0.1: 1344 | version "1.0.1" 1345 | resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" 1346 | dependencies: 1347 | escape-string-regexp "^1.0.2" 1348 | 1349 | sumchecker@^3.0.1: 1350 | version "3.0.1" 1351 | resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" 1352 | dependencies: 1353 | debug "^4.1.0" 1354 | 1355 | temp@^0.8.3: 1356 | version "0.8.3" 1357 | resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59" 1358 | dependencies: 1359 | os-tmpdir "^1.0.0" 1360 | rimraf "~2.2.6" 1361 | 1362 | touch@0.0.3: 1363 | version "0.0.3" 1364 | resolved "https://registry.yarnpkg.com/touch/-/touch-0.0.3.tgz#51aef3d449571d4f287a5d87c9c8b49181a0db1d" 1365 | dependencies: 1366 | nopt "~1.0.10" 1367 | 1368 | tough-cookie@~2.3.0: 1369 | version "2.3.4" 1370 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" 1371 | dependencies: 1372 | punycode "^1.4.1" 1373 | 1374 | "traverse@>=0.3.0 <0.4": 1375 | version "0.3.9" 1376 | resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" 1377 | 1378 | trim-repeated@^1.0.0: 1379 | version "1.0.0" 1380 | resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" 1381 | dependencies: 1382 | escape-string-regexp "^1.0.2" 1383 | 1384 | tunnel-agent@^0.6.0: 1385 | version "0.6.0" 1386 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 1387 | dependencies: 1388 | safe-buffer "^5.0.1" 1389 | 1390 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 1391 | version "0.14.5" 1392 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 1393 | 1394 | type-fest@^0.13.1: 1395 | version "0.13.1" 1396 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" 1397 | 1398 | universalify@^0.1.0: 1399 | version "0.1.0" 1400 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.0.tgz#9eb1c4651debcc670cc94f1a75762332bb967778" 1401 | 1402 | universalify@^2.0.0: 1403 | version "2.0.0" 1404 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" 1405 | 1406 | uuid@^3.0.0: 1407 | version "3.1.0" 1408 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" 1409 | 1410 | validate-npm-package-license@^3.0.1: 1411 | version "3.0.1" 1412 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" 1413 | dependencies: 1414 | spdx-correct "~1.0.0" 1415 | spdx-expression-parse "~1.0.0" 1416 | 1417 | verror@1.10.0: 1418 | version "1.10.0" 1419 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" 1420 | dependencies: 1421 | assert-plus "^1.0.0" 1422 | core-util-is "1.0.2" 1423 | extsprintf "^1.2.0" 1424 | 1425 | which@^2.0.1, which@^2.0.2: 1426 | version "2.0.2" 1427 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1428 | dependencies: 1429 | isexe "^2.0.0" 1430 | 1431 | wrappy@1: 1432 | version "1.0.2" 1433 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1434 | 1435 | xhr2@~0.1.3: 1436 | version "0.1.4" 1437 | resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" 1438 | 1439 | xmlbuilder@^15.1.1: 1440 | version "15.1.1" 1441 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" 1442 | 1443 | yallist@^4.0.0: 1444 | version "4.0.0" 1445 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1446 | 1447 | yargs-parser@^21.1.1: 1448 | version "21.1.1" 1449 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" 1450 | 1451 | yauzl@^2.10.0: 1452 | version "2.10.0" 1453 | resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" 1454 | dependencies: 1455 | buffer-crc32 "~0.2.3" 1456 | fd-slicer "~1.1.0" 1457 | --------------------------------------------------------------------------------