shredbeat
Version:
Music beat reacts to your keyboard shredding speed 🤘
1,816 lines (1,524 loc) • 5.59 MB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
const React = require('react');
const ReactDOM = require('react-dom');
const { ipcRenderer } = window.require('electron');
const {
HashRouter: Router,
Route,
Link
} = require('react-router-dom');
const Main = require('./Main.jsx');
class App extends React.Component {
render() {
return React.createElement(
Router,
null,
React.createElement(
'div',
{ className: 'ui grid stackable padded' },
React.createElement(
'div',
{ className: 'DragRegion' },
React.createElement(
'a',
{
onClick: this.onMinimizeWindowClick.bind(this),
className: 'MinimizeHandle' },
React.createElement('i', { className: 'fa fa-window-minimize', 'aria-hidden': 'true' })
),
React.createElement(
'span',
null,
'hold and drag'
),
React.createElement(
'span',
{ className: 'ResizeHandle' },
'resize \u2197'
)
),
React.createElement(Route, { exact: true, path: '/', component: Main })
)
);
}
onMinimizeWindowClick(event) {
event.preventDefault();
ipcRenderer.send('minimize-window', true);
}
}
module.exports = App;
},{"./Main.jsx":3,"react":601,"react-dom":424,"react-router-dom":562}],2:[function(require,module,exports){
const React = require('react');
const ReactDOM = require('react-dom');
const Slider = require('react-slider');
const store = require('../lib/storeService');
class ConfigSettings extends React.Component {
constructor() {
super();
this.state = {
sensitivity: (store.get('sensitivity') || 0.10) * 100 | 0,
startingVolume: store.get('startingVolume') || -50,
volumeSensitivity: (store.get('volumeSensitivity') || 0.10) * 100 | 0
};
}
render() {
const {
sensitivity,
startingVolume,
volumeSensitivity
} = this.state;
return React.createElement(
'div',
{ className: 'ui grid ConfigSettings', id: 'ConfigSettings' },
React.createElement(
'form',
{ className: 'ui form' },
React.createElement(
'div',
{ className: 'ui field' },
React.createElement(
'label',
null,
'Shredometer Sensitivity'
),
React.createElement(
Slider,
{
className: 'SettingRangeSlider',
min: 0,
step: 1,
max: 100,
defaultValue: sensitivity,
orientation: 'horizontal',
onChange: this.onSensitivityChange.bind(this),
withBars: true
},
React.createElement(
'div',
{ className: 'handle-label' },
sensitivity,
'%'
)
)
),
React.createElement(
'div',
{ className: 'ui field' },
React.createElement(
'label',
null,
'Starting volume'
),
React.createElement(
Slider,
{
className: 'SettingRangeSlider',
min: -100,
step: 1,
max: 0,
defaultValue: startingVolume,
orientation: 'horizontal',
onChange: this.onStartingVolumeChange.bind(this),
withBars: true
},
React.createElement(
'div',
{ className: 'handle-label' },
startingVolume,
'%'
)
)
),
React.createElement(
'div',
{ className: 'ui field' },
React.createElement(
'label',
null,
'Volume Sensitivity'
),
React.createElement(
Slider,
{
className: 'SettingRangeSlider',
min: 0,
step: 1,
max: 100,
defaultValue: volumeSensitivity,
orientation: 'horizontal',
onChange: this.onVolumeSensitivityChange.bind(this),
withBars: true
},
React.createElement(
'div',
{ className: 'handle-label' },
volumeSensitivity,
'%'
)
)
)
)
);
}
onSensitivityChange(value) {
const sensitivity = value / 100;
store.set('sensitivity', sensitivity);
this.setState({ sensitivity: value });
}
onStartingVolumeChange(startingVolume) {
store.set('startingVolume', startingVolume);
this.setState({ startingVolume });
}
onVolumeSensitivityChange(value) {
const sensitivity = value / 100;
store.set('volumeSensitivity', sensitivity);
this.setState({ volumeSensitivity: value });
}
}
module.exports = ConfigSettings;
},{"../lib/storeService":15,"react":601,"react-dom":424,"react-slider":575}],3:[function(require,module,exports){
const React = require('react');
const ReactDOM = require('react-dom');
const _ = require('lodash');
// https://github.com/electron/electron/issues/7300
const { remote, shell } = window.require('electron');
const musicService = require('../lib/musicService');
const shredometerService = require('../lib/shredometerService');
const store = require('../lib/storeService');
const player = require('../lib/playerService');
const Shredometer = require('./Shredometer.jsx');
const ConfigSettings = require('./ConfigSettings.jsx');
const Player = require('./Player.jsx');
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
playlistUrlInput: '',
playlistUrlSubmitDisabled: false,
playButtonDisabled: true,
stopButtonDisabled: true,
nextButtonDisabled: true,
prevButtonDisabled: true,
randomButtonDisabled: false,
repeatButtonDisabled: false,
randomActive: false,
repeatActive: false,
isMuted: false,
maxVolume: 1,
isPlaying: true,
shredProgress: 0,
errorMessage: null,
isFetchingPlaylist: false
};
player.on('EmptyQueue', () => {
this.setState({
playButtonDisabled: true,
stopButtonDisabled: true,
nextButtonDisabled: true,
prevButtonDisabled: true,
randomButtonDisabled: false,
repeatButtonDisabled: false
});
});
player.on('Enqueue', () => {
this.setState({ playButtonDisabled: false });
player.hasPrevious().then(hasPrevious => {
this.setState({
previousButtonDisabled: !hasPrevious
});
});
player.hasNext().then(hasNext => {
this.setState({
nextButtonDisabled: !hasNext
});
});
});
player.on('Play', () => {
this.setState({
playButtonDisabled: true,
stopButtonDisabled: false
});
});
player.on('Stop', () => {
this.setState({
playButtonDisabled: false,
stopButtonDisabled: true
});
});
player.on('Previous', () => {
player.hasPrevious().then(hasPrevious => {
this.setState({
previousButtonDisabled: !hasPrevious
});
});
player.hasNext().then(hasNext => {
this.setState({
nextButtonDisabled: !hasNext
});
});
});
player.on('Next', () => {
player.hasPrevious().then(hasPrevious => {
this.setState({
previousButtonDisabled: !hasPrevious
});
});
player.hasNext().then(hasNext => {
this.setState({
nextButtonDisabled: !hasNext
});
});
});
shredometerService.setSensitivity(store.get('sensitivity'));
shredometerService.setStartingAmplitude(store.get('startingVolume'));
shredometerService.setAmplitudeSensitivity(store.get('volumeSensitivity'));
shredometerService.on('ShredProgress', _.throttle(shredProgress => {
this.setState({ shredProgress });
}, 100));
shredometerService.on('ShredRate', _.throttle(shredRate => {
player.setPlaybackRate(shredRate);
}, 100));
shredometerService.on('AmplitudeRate', _.throttle(volume => {
player.setVolume(volume);
}, 100));
store.on('sensitivity', _.throttle(value => {
shredometerService.setSensitivity(value);
}, 100));
store.on('startingVolume', _.throttle(value => {
shredometerService.setStartingAmplitude(value);
}, 100));
store.on('volumeSensitivity', _.throttle(value => {
shredometerService.setAmplitudeSensitivity(value);
}, 100));
this.handleError = this.handleError.bind(this);
this.onExternalClick = this.onExternalClick.bind(this);
this.setPlaylistUrl = this.setPlaylistUrl.bind(this);
const playlistUrl = store.get('playlistUrl');
if (playlistUrl) {
this.state.playlistUrlInput = playlistUrl;
this.setPlaylistUrl(playlistUrl);
}
}
render() {
const {
playlistUrlInput,
playlistUrlSubmitDisabled,
playButtonDisabled,
stopButtonDisabled,
nextButtonDisabled,
previousButtonDisabled,
randomButtonDisabled,
repeatButtonDisabled,
randomActive,
repeatActive,
isMuted,
maxVolume,
shredProgress,
errorMessage,
isFetchingPlaylist
} = this.state;
return React.createElement(
'div',
{ className: 'ui grid stackable padded MainView' },
React.createElement(
'div',
{ className: 'ui large header MainTitle' },
'Shredbeat'
),
errorMessage ? React.createElement(
'div',
{ className: 'ui message error' },
errorMessage
) : null,
React.createElement(
'div',
{ className: 'PlaylistFormContainer' },
React.createElement(
'form',
{
className: 'ui form PlaylistForm',
onSubmit: this.onPlaylistFormSubmit.bind(this) },
React.createElement(
'div',
{ className: 'PlaylistUrlInputContainer' },
React.createElement('input', {
type: 'text',
className: 'ui input PlaylistUrlInput',
defaultValue: playlistUrlInput,
onInput: this.onPlaylistUrlInput.bind(this),
placeholder: 'Playlist URL' }),
React.createElement(
'button',
{
className: `ui button PlaylistUrlSubmit ${isFetchingPlaylist ? 'loading' : ''}`,
type: 'submit',
disabled: playlistUrlSubmitDisabled },
isFetchingPlaylist ? React.createElement('i', { className: 'fa fa-circle-o-notch fa-spin' }) : React.createElement(
'span',
null,
'Set'
)
)
),
React.createElement(
'div',
{ className: 'PlaylistUrlInputHelp' },
React.createElement(
'p',
null,
React.createElement(
'small',
null,
'Use\xA0',
React.createElement(
'a',
{
onClick: this.onExternalClick,
href: 'https://www.youtube.com/' },
'YouTube'
),
',\xA0',
React.createElement(
'a',
{
onClick: this.onExternalClick,
href: 'https://soundcloud.com/' },
'SoundCloud'
),
',\xA0',
React.createElement(
'a',
{
onClick: this.onExternalClick,
href: 'http://hypem.com/' },
'Hype Machine'
),
',\xA0',
React.createElement(
'a',
{
onClick: this.onExternalClick,
href: 'https://www.mixcloud.com/' },
'Mixcloud'
),
',\xA0 or\xA0',
React.createElement(
'a',
{
onClick: this.onExternalClick,
href: 'https://fanburst.com/' },
'Fanburst'
),
'\xA0 playlist urls'
)
)
)
),
React.createElement(
'div',
{ className: 'PlayerContainer' },
React.createElement(Player, {
playButtonDisabled: playButtonDisabled,
pauseButtonDisabled: stopButtonDisabled,
stopButtonDisabled: stopButtonDisabled,
previousButtonDisabled: previousButtonDisabled,
nextButtonDisabled: nextButtonDisabled,
randomButtonDisabled: randomButtonDisabled,
repeatButtonDisabled: repeatButtonDisabled,
onPlayClick: this.onPlay.bind(this),
onPauseClick: this.onStop.bind(this),
onStopClick: this.onStop.bind(this),
onPreviousClick: this.onPrev.bind(this),
onNextClick: this.onNext.bind(this),
onRandomClick: this.onRandom.bind(this),
onRepeatClick: this.onRepeat.bind(this),
onVolumeClick: this.onVolumeClick.bind(this),
onVolumeChange: this.onVolumeChange.bind(this),
repeatActive: repeatActive,
randomActive: randomActive
})
)
),
React.createElement(Shredometer, { shredProgress: shredProgress }),
React.createElement(
'div',
{ id: 'bottom' },
React.createElement(
'a',
{ id: 'settings',
onClick: this.onSettingsClick },
React.createElement('i', { className: 'fa fa-cog', 'aria-hidden': 'true' }),
'\xA0 settings'
),
React.createElement(
'a',
{ id: 'quit', onClick: this.onQuitClick.bind(this) },
'quit'
)
),
React.createElement(ConfigSettings, null)
);
}
onExternalClick(event) {
event.preventDefault();
shell.openExternal(event.target.href);
}
onPlaylistUrlInput(event) {
const value = event.target.value;
this.setState({
playlistUrlInput: value
});
}
setPlaylistUrl(url) {
this.setState({
errorMessage: null,
isFetchingPlaylist: true
});
player.stop();
// timeout used because of player stop/play events listener
setTimeout(() => {
this.setState({
//playlistUrlSubmitDisabled: true,
playButtonDisabled: true,
stopButtonDisabled: true
});
}, 0);
if (/youtube\.com/gi.test(url)) {
setTimeout(() => {
this.setState({
playlistUrlSubmitDisabled: false,
playButtonDisabled: false,
isFetchingPlaylist: false
});
}, 1e3);
return player.setPlaylist(url).catch(this.handleError);
} else {
musicService.getPlaylist(url).then(tracks => {
console.log(tracks);
this.setState({
playButtonDisabled: false
});
setTimeout(() => {
this.setState({
playlistUrlSubmitDisabled: false,
isFetchingPlaylist: false
});
}, 10);
return player.setPlaylist(tracks);
}).catch(error => {
setTimeout(() => {
this.setState({
isFetchingPlaylist: false
});
}, 10);
this.handleError(error);
});
}
}
onPlaylistFormSubmit(event) {
event.preventDefault();
const url = this.state.playlistUrlInput;
store.set('playlistUrl', url);
this.setPlaylistUrl(url);
}
onPlay() {
player.play();
this.setState({
isPlaying: true,
stopButtonDisabled: false,
playButtonDisabled: true
});
}
onRandom() {
const enabled = !this.state.randomActive;
player.setRandom(enabled);
this.setState({
randomActive: enabled
});
}
onRepeat() {
const enabled = !this.state.repeatActive;
player.setRepeat(enabled);
this.setState({
repeatActive: enabled
});
}
onVolumeChange(volume) {
player.setMaxVolume(volume);
}
onVolumeClick() {
const enabled = !this.state.isMuted;
player.setMute(enabled);
}
onStop() {
player.pause();
this.setState({
stopButtonDisabled: true,
playButtonDisabled: false
});
}
onPrev() {
player.previous();
}
onNext() {
player.next();
}
onSettingsClick(event) {
event.preventDefault();
document.body.scrollTop = window.outerHeight + 200;
}
onQuitClick(event) {
event.preventDefault();
remote.app.quit();
}
handleError(error) {
console.error(error);
this.setState({
errorMessage: error
});
}
}
module.exports = Main;
},{"../lib/musicService":12,"../lib/playerService":13,"../lib/shredometerService":14,"../lib/storeService":15,"./ConfigSettings.jsx":2,"./Player.jsx":4,"./Shredometer.jsx":5,"lodash":355,"react":601,"react-dom":424}],4:[function(require,module,exports){
const React = require('react');
const ReactDOM = require('react-dom');
const VolumeSlider = require('./VolumeSlider.jsx');
class Player extends React.Component {
constructor(props) {
super(props);
const {
playButtonDisabled,
pauseButtonDisabled,
stopButtonDisabled,
previousButtonDisabled,
nextButtonDisabled,
randomButtonDisabled,
repeatButtonDisabled,
randomActive,
repeatActive
} = props;
this.state = {
playButtonDisabled,
pauseButtonDisabled,
stopButtonDisabled,
previousButtonDisabled,
nextButtonDisabled,
randomButtonDisabled,
repeatButtonDisabled,
randomActive,
repeatActive
};
this.onPlayClick = props.onPlayClick.bind(this);
this.onPauseClick = props.onPauseClick.bind(this);
this.onStopClick = props.onStopClick.bind(this);
this.onPreviousClick = props.onPreviousClick.bind(this);
this.onNextClick = props.onNextClick.bind(this);
this.onRandomClick = props.onRandomClick.bind(this);
this.onRepeatClick = props.onRepeatClick.bind(this);
this.onVolumeChange = props.onVolumeChange.bind(this);
this.onVolumeClick = props.onVolumeClick.bind(this);
}
componentWillReceiveProps(nextProps) {
const {
playButtonDisabled,
pauseButtonDisabled,
stopButtonDisabled,
previousButtonDisabled,
nextButtonDisabled,
randomButtonDisabled,
repeatButtonDisabled,
randomActive,
repeatActive
} = nextProps;
this.setState({
playButtonDisabled,
pauseButtonDisabled,
stopButtonDisabled,
previousButtonDisabled,
nextButtonDisabled,
randomButtonDisabled,
repeatButtonDisabled,
randomActive,
repeatActive
});
}
render() {
const {
playButtonDisabled,
pauseButtonDisabled,
stopButtonDisabled,
previousButtonDisabled,
nextButtonDisabled,
randomButtonDisabled,
repeatButtonDisabled,
randomActive,
repeatActive
} = this.state;
return React.createElement(
'div',
{ className: 'ui grid Player' },
React.createElement(
'button',
{
className: 'PlayerControlButton PlayerControlPreviousButton',
onClick: this.onPreviousClick,
disabled: previousButtonDisabled },
React.createElement('i', { className: 'fa fa-step-backward', 'aria-hidden': 'true' })
),
React.createElement(
'button',
{
className: 'PlayerControlButton PlayerControlPlayButton',
onClick: this.onPlayClick,
disabled: playButtonDisabled },
React.createElement('i', { className: 'fa fa-play-circle-o', 'aria-hidden': 'true' })
),
React.createElement(
'button',
{
className: 'PlayerControlButton PlayerControlPauseButton',
onClick: this.onPauseClick,
disabled: pauseButtonDisabled },
React.createElement('i', { className: 'fa fa-pause-circle-o', 'aria-hidden': 'true' })
),
React.createElement(
'button',
{
className: 'PlayerControlButton PlayerControlNextButton',
onClick: this.onNextClick,
disabled: nextButtonDisabled },
React.createElement('i', { className: 'fa fa-step-forward', 'aria-hidden': 'true' })
),
React.createElement(
'button',
{
className: `PlayerControlButton PlayerControlRandomButton ${randomActive ? 'active' : ''}`,
onClick: this.onRandomClick,
disabled: randomButtonDisabled },
React.createElement('i', { className: 'fa fa-random', 'aria-hidden': 'true' })
),
React.createElement(
'button',
{
className: `PlayerControlButton PlayerControlRepeatButton ${repeatActive ? 'active' : ''}`,
onClick: this.onRepeatClick,
disabled: repeatButtonDisabled },
React.createElement('i', { className: 'fa fa-repeat', 'aria-hidden': 'true' })
),
React.createElement(
'div',
{ className: 'PlayerVolumeContainer' },
React.createElement(VolumeSlider, {
onVolumeChange: this.onVolumeChange,
onClick: this.onVolumeClick })
)
);
}
}
module.exports = Player;
},{"./VolumeSlider.jsx":6,"react":601,"react-dom":424}],5:[function(require,module,exports){
const React = require('react');
const ReactDOM = require('react-dom');
function Shredometer(props) {
const shredProgress = props.shredProgress;
var level = null;
if (shredProgress >= 0 && shredProgress < 30) {
level = 'low';
} else if (shredProgress >= 30 && shredProgress < 70) {
level = 'medium';
} else if (shredProgress >= 70) {
level = 'high';
}
return React.createElement(
'div',
{ className: 'Shredometer' },
React.createElement(
'div',
{ className: 'ui header ShredometerTitle' },
'Shredometer'
),
React.createElement(
'div',
{ className: 'ShredometerProgress' },
'|',
React.createElement('div', {
className: `ShredometerProgressFill ${level}`,
style: { width: `${shredProgress}%` } })
)
);
}
module.exports = Shredometer;
},{"react":601,"react-dom":424}],6:[function(require,module,exports){
const React = require('react');
const ReactDOM = require('react-dom');
const Slider = require('react-slider');
const store = require('../lib/storeService');
class VolumeSlider extends React.Component {
constructor(props) {
super(props);
let volume = 1;
if (typeof props.volume === 'number') {
volume = props.volume;
}
let muted = false;
if (typeof props.muted === 'boolean') {
muted = props.muted;
}
this.state = {
volume,
showSlider: false,
muted
};
this.onVolumeChange = (props.onVolumeChange || (() => {})).bind(this);
this.onMuteToggle = (props.onMutetoggle || (() => {})).bind(this);
}
render() {
const { showSlider, muted, volume } = this.state;
let volumeValue = 1 - volume;
const volumeLow = volume <= 0.5;
let volumeIcon = 'fa-volume-up';
if (muted) {
volumeValue = 1;
}
if (muted || volume <= 0) {
volumeIcon = 'fa-volume-off';
} else if (volumeLow) {
volumeIcon = 'fa-volume-down';
}
return React.createElement(
'div',
{
onMouseOver: this.onVolumeMouseOver.bind(this),
onMouseOut: this.onVolumeMouseOut.bind(this),
className: 'VolumeSliderContainer' },
React.createElement(
'div',
{
className: 'VolumeSliderWrapper' },
React.createElement(Slider, {
className: `VolumeSlider ${showSlider ? 'visible' : 'hidden'}`,
min: 0,
step: .1,
max: 1,
defaultValue: volumeValue,
value: volumeValue,
orientation: 'vertical',
onChange: this.onSliderChange.bind(this),
withBars: true
}),
React.createElement(
'button',
{
className: 'PlayerControlButton PlayerControlVolumeButton',
onClick: this.onVolumeClick.bind(this) },
React.createElement('i', { className: `fa ${volumeIcon}`, 'aria-hidden': 'true' })
)
)
);
}
onSliderChange(value) {
const volume = 1 - value;
let muted = this.state.muted;
if (volume > 0 && muted) {
muted = false;
}
this.setState({
volume,
muted
});
this.onVolumeChange(volume);
}
onVolumeMouseOver(event) {
this.setState({ showSlider: true });
}
onVolumeMouseOut(event) {
this.setState({ showSlider: false });
}
onVolumeClick(event) {
event.preventDefault();
const muted = !this.state.muted;
const volume = this.state.volume;
this.setState({ muted });
this.onMuteToggle(muted);
this.onVolumeChange(muted ? 0 : volume);
}
}
module.exports = VolumeSlider;
},{"../lib/storeService":15,"react":601,"react-dom":424,"react-slider":575}],7:[function(require,module,exports){
const React = require('react');
const ReactDOM = require('react-dom');
const App = require('./App.jsx');
ReactDOM.render(React.createElement(App, null), document.getElementById('root'));
},{"./App.jsx":1,"react":601,"react-dom":424}],8:[function(require,module,exports){
var getStreamUrls = require('fanburst-audio').getStreamUrls;
class FanburstService {
constructor() {}
getPlaylist(playlistUrl) {
return getStreamUrls(playlistUrl);
}
}
module.exports = new FanburstService();
},{"fanburst-audio":238}],9:[function(require,module,exports){
var getStreamUrls = require('hypem-audio').getStreamUrls;
class HypemService {
constructor() {}
getPlaylist(playlistUrl) {
return getStreamUrls(playlistUrl);
}
}
module.exports = new HypemService();
},{"hypem-audio":322}],10:[function(require,module,exports){
var getStreamUrls = require('mixcloud-audio').getStreamUrls;
class MixcloudService {
constructor() {}
getPlaylist(playlistUrl) {
return getStreamUrls(playlistUrl);
}
}
module.exports = new MixcloudService();
},{"mixcloud-audio":363}],11:[function(require,module,exports){
var config = {
clientId: 'a0cbcf3a37c999ef9eae76469ea8db92'
};
class SoundCloudService {
constructor(config) {
this.clientId = config.clientId;
this.handleTrackResponse = this.handleTrackResponse.bind(this);
this.getPlaylist = this.getPlaylist.bind(this);
}
clientIdParam() {
return 'client_id=' + this.clientId;
}
handleTrackResponse(response) {
if (response.kind === 'track') {
if (response.stream_url) {
return `${response.stream_url}?${this.clientIdParam()}`;
}
}
return null;
}
getPlaylist(url) {
return fetch(`https://api.soundcloud.com/resolve?url=${url}&${this.clientIdParam()}`).then(response => response.json()).then(json => {
var { kind } = json;
if (kind === 'track') {
return [this.handleTrackResponse(json)];
} else if (kind === 'user') {
return fetch(`${json.uri}/tracks?${this.clientIdParam()}`).then(response => response.json()).then(json => {
return json.map(this.handleTrackResponse);
});
} else if (kind === 'playlist') {
return json.tracks.map(this.handleTrackResponse);
} else if (Array.isArray(json)) {
return json.map(this.handleTrackResponse);
}
return [];
}).then(tracks => {
return tracks.filter(x => x);
});
}
}
module.exports = new SoundCloudService(config);
},{}],12:[function(require,module,exports){
var url = require('url');
var soundcloud = require('./apis/soundcloud');
var mixcloud = require('./apis/mixcloud');
var hypem = require('./apis/hypem');
var fanburst = require('./apis/fanburst');
class MusicService {
constructor() {}
getPlaylist(playlistUrl) {
if (!playlistUrl) {
return Promise.reject('Playlist URL is required');
}
var hostname = url.parse(playlistUrl).hostname;
if (/soundcloud/gi.test(hostname)) {
return soundcloud.getPlaylist(playlistUrl);
} else if (/mixcloud/gi.test(hostname)) {
return mixcloud.getPlaylist(playlistUrl);
} else if (/fanburst/gi.test(hostname)) {
return fanburst.getPlaylist(playlistUrl);
} else if (/hypem/gi.test(hostname)) {
return hypem.getPlaylist(playlistUrl);
} else {
return Promise.reject(hostname + ' music service is not supported.');
}
}
}
module.exports = new MusicService();
},{"./apis/fanburst":8,"./apis/hypem":9,"./apis/mixcloud":10,"./apis/soundcloud":11,"url":1037}],13:[function(require,module,exports){
const EventEmitter = require('events');
const { Player, YoutubePlayer } = require('audio-director');
const PlayerEventTypes = Player.EventTypes;
const playerST = new Player();
const playerYT = new YoutubePlayer();
let player = playerYT;
// Facade
class PlayerService extends EventEmitter {
constructor() {
super();
playerST.on(PlayerEventTypes.ENQUEUE, () => {
this.emit('Enqueue');
});
playerYT.on(PlayerEventTypes.ENQUEUE, () => {
this.emit('Enqueue');
});
playerST.on(PlayerEventTypes.EMPTY_QUEUE, () => {
this.emit('EmptyQueue');
});
playerYT.on(PlayerEventTypes.EMPTY_QUEUE, () => {
this.emit('EmptyQueue');
});
playerST.on(PlayerEventTypes.PLAY, () => {
this.emit('Play');
});
playerYT.on(PlayerEventTypes.PLAY, () => {
this.emit('Play');
});
playerST.on(PlayerEventTypes.STOP, () => {
this.emit('Stop');
});
playerYT.on(PlayerEventTypes.STOP, () => {
this.emit('Stop');
});
playerST.on(PlayerEventTypes.PREVIOUS, () => {
this.emit('Next');
});
playerYT.on(PlayerEventTypes.PREVIOUS, () => {
this.emit('Previous');
});
playerST.on(PlayerEventTypes.NEXT, () => {
this.emit('Next');
});
playerYT.on(PlayerEventTypes.NEXT, () => {
this.emit('Next');
});
}
play() {
return player.play();
}
pause() {
return player.pause();
}
stop() {
return player.stop();
}
next() {
return player.next();
}
previous() {
return player.previous();
}
setRandom(enabled) {
return player.setRandom(enabled);
}
setRepeat(enabled) {
return player.setRepeat(enabled);
}
hasPrevious() {
return player.hasPrevious();
}
hasNext() {
return player.hasNext();
}
setPlaybackRate(rate) {
if (playerST.isReady) {
playerST.setPlaybackRate(rate);
}
if (playerYT.isReady) {
playerYT.setPlaybackRate(rate);
}
return Promise.resolve();
}
setVolume(volume) {
if (playerST.isReady) {
playerST.setVolume(volume);
}
if (playerYT.isReady) {
playerYT.setVolume(volume);
}
return Promise.resolve();
}
setMaxVolume(maxVolume) {
if (playerST.isReady) {
playerST.setMaxVolume(maxVolume);
}
if (playerYT.isReady) {
playerYT.setMaxVolume(maxVolume);
}
return Promise.resolve();
}
setMuted(enabled) {
if (playerST.isReady) {
playerST.setMuted(enabled);
}
if (playerYT.isReady) {
playerYT.setMuted(enabled);
}
return Promise.resolve();
}
enqueue(data) {
return player.enqueue(data);
}
emptyQueue() {
return player.emptyQueue();
}
setPlaylist(url) {
if (/youtube\.com/gi.test(url)) {
player = playerYT;
} else {
player = playerST;
}
player.emptyQueue();
return player.enqueue(url);
}
}
module.exports = new PlayerService();
},{"audio-director":80,"events":234}],14:[function(require,module,exports){
const EventEmitter = require('events');
const Rx = require('rxjs');
const _ = require('lodash');
const d3 = require('d3');
const keyup$ = new Rx.Subject();
// from index.html
const shredbeat = window.shredbeat || {};
if (typeof shredbeat.osGlobalKeyPress === 'function') {
shredbeat.osGlobalKeyPress(event => {
keyup$.next(event);
});
}
class ShredometerService extends EventEmitter {
constructor() {
super();
this.sensitivity = 0.001;
this.sensitivityScale = d3.scaleLinear().domain([0, 1]).range([0.0001, 0.003]).clamp(true);
this.playbackRange = [1, 3];
this.startingAmplitude = 0.5;
this.amplitudeRange = [this.startingAmplitude, 1];
this.amplitudeScale = d3.scaleLinear().domain([-100, 0]).range([0, 1]).clamp(true);
this.amplitude = this.amplitudeRange[0];
this.amplitudeSensitivity = 0.005;
this.amplitudeSensitivityScale = d3.scaleLinear().domain([0, 1]).range([0.0001, 0.01]).clamp(true);
this.deltaScale = d3.scaleLinear().domain([0, 1000]) // milliseconds
.range([3, 1]).clamp(true);
this.progressScale = d3.scaleLinear().domain([1, 2.5]).range([0, 100]).clamp(true);
this.currentRate = this.playbackRange[0];
this.direction = -1;
this.directionTimeout = null;
this.rate = 1;
keyup$.timestamp().map(obj => obj.timestamp).pairwise().map(([a, b]) => b - a).subscribe(delta => {
this.rate = this.deltaScale(delta);
this.direction = 1;
// reverse direction after a second of no activity
clearTimeout(this.directionTimeout);
this.directionTimeout = setTimeout(() => {
this.direction = -1;
}, 1e3);
});
setInterval(() => {
this.currentRate += this.rate * this.sensitivity * this.direction;
const shredProgress = this.progressScale(this.currentRate);
// clamping
if (this.currentRate >= this.playbackRange[1]) {
this.currentRate = this.playbackRange[1];
} else if (this.currentRate <= this.playbackRange[0]) {
this.currentRate = this.playbackRange[0];
}
this.amplitude += this.amplitudeSensitivity * this.direction;
// clamping
if (this.amplitude >= this.amplitudeRange[1]) {
this.amplitude = this.amplitudeRange[1];
} else if (this.amplitude <= this.amplitudeRange[0]) {
this.amplitude = this.amplitudeRange[0];
}
this.emit('ShredProgress', shredProgress);
this.emit('ShredRate', this.currentRate);
this.emit('AmplitudeRate', this.amplitude);
}, 50);
}
setSensitivity(value) {
if (typeof value === 'number') {
this.sensitivity = this.sensitivityScale(value);
}
}
setAmplitudeSensitivity(value) {
if (typeof value === 'number') {
this.amplitudeSensitivity = this.amplitudeSensitivityScale(value);
}
}
setStartingAmplitude(value) {
if (typeof value === 'number') {
this.startingAmplitude = this.amplitudeScale(value);
this.amplitudeRange = [this.startingAmplitude, 1];
this.amplitude = this.amplitudeRange[0];
}
}
}
module.exports = new ShredometerService();
},{"d3":178,"events":234,"lodash":355,"rxjs":624}],15:[function(require,module,exports){
let Store = null;
if (typeof window !== 'undefined' && window.require) {
// https://github.com/electron/electron/issues/7300
Store = window.require('electron-store');
} else {
Store = require('electron-store');
}
const EventEmitter = require('events');
class StoreService extends EventEmitter {
constructor() {
super();
this._store = new Store();
}
get(key) {
return this._store.get(key);
}
set(key, value) {
this._store.set(key, value);
this.emit(key, value);
return this._store.get(key);
}
delete(key) {
return this._store.delete(key);
}
}
const storeService = new StoreService();
module.exports = storeService;
},{"electron-store":207,"events":234}],16:[function(require,module,exports){
'use strict';
var compileSchema = require('./compile')
, resolve = require('./compile/resolve')
, Cache = require('./cache')
, SchemaObject = require('./compile/schema_obj')
, stableStringify = require('json-stable-stringify')
, formats = require('./compile/formats')
, rules = require('./compile/rules')
, v5 = require('./v5')
, util = require('./compile/util')
, async = require('./async')
, co = require('co');
module.exports = Ajv;
Ajv.prototype.compileAsync = async.compile;
var customKeyword = require('./keyword');
Ajv.prototype.addKeyword = customKeyword.add;
Ajv.prototype.getKeyword = customKeyword.get;
Ajv.prototype.removeKeyword = customKeyword.remove;
Ajv.ValidationError = require('./compile/validation_error');
var META_SCHEMA_ID = 'http://json-schema.org/draft-04/schema';
var SCHEMA_URI_FORMAT = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i;
function SCHEMA_URI_FORMAT_FUNC(str) {
return SCHEMA_URI_FORMAT.test(str);
}
var META_IGNORE_OPTIONS = [ 'removeAdditional', 'useDefaults', 'coerceTypes' ];
/**
* Creates validator instance.
* Usage: `Ajv(opts)`
* @param {Object} opts optional options
* @return {Object} ajv instance
*/
function Ajv(opts) {
if (!(this instanceof Ajv)) return new Ajv(opts);
var self = this;
opts = this._opts = util.copy(opts) || {};
this._schemas = {};
this._refs = {};
this._fragments = {};
this._formats = formats(opts.format);
this._cache = opts.cache || new Cache;
this._loadingSchemas = {};
this._compilations = [];
this.RULES = rules();
// this is done on purpose, so that methods are bound to the instance
// (without using bind) so that they can be used without the instance
this.validate = validate;
this.compile = compile;
this.addSchema = addSchema;
this.addMetaSchema = addMetaSchema;
this.validateSchema = validateSchema;
this.getSchema = getSchema;
this.removeSchema = removeSchema;
this.addFormat = addFormat;
this.errorsText = errorsText;
this._addSchema = _addSchema;
this._compile = _compile;
opts.loopRequired = opts.loopRequired || Infinity;
if (opts.async || opts.transpile) async.setup(opts);
if (opts.beautify === true) opts.beautify = { indent_size: 2 };
if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true;
this._metaOpts = getMetaSchemaOptions();
if (opts.formats) addInitialFormats();
addDraft4MetaSchema();
if (opts.v5) v5.enable(this);
if (typeof opts.meta == 'object') addMetaSchema(opts.meta);
addInitialSchemas();
/**
* Validate data using schema
* Schema will be compiled and cached (using serialized JSON as key. [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used to serialize.
* @param {String|Object} schemaKeyRef key, ref or schema object
* @param {Any} data to be validated
* @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`).
*/
function validate(schemaKeyRef, data) {
var v;
if (typeof schemaKeyRef == 'string') {
v = getSchema(schemaKeyRef);
if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"');
} else {
var schemaObj = _addSchema(schemaKeyRef);
v = schemaObj.validate || _compile(schemaObj);
}
var valid = v(data);
if (v.$async === true)
return self._opts.async == '*' ? co(valid) : valid;
self.errors = v.errors;
return valid;
}
/**
* Create validating function for passed schema.
* @param {Object} schema schema object
* @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords.
* @return {Function} validating function
*/
function compile(schema, _meta) {
var schemaObj = _addSchema(schema, undefined, _meta);
return schemaObj.validate || _compile(schemaObj);
}
/**
* Adds schema to the instance.
* @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored.
* @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`.
* @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead.
* @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead.
*/
function addSchema(schema, key, _skipValidation, _meta) {
if (Array.isArray(schema)){
for (var i=0; i<schema.length; i++) addSchema(schema[i], undefined, _skipValidation, _meta);
return;
}
// can key/id have # inside?
key = resolve.normalizeId(key || schema.id);
checkUnique(key);
self._schemas[key] = _addSchema(schema, _skipValidation, _meta, true);
}
/**
* Add schema that will be used to validate other schemas
* options in META_IGNORE_OPTIONS are alway set to false
* @param {Object} schema schema object
* @param {String} key optional schema key
* @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema
*/
function addMetaSchema(schema, key, skipValidation) {
addSchema(schema, key, skipValidation, true);
}
/**
* Validate schema
* @param {Object} schema schema to validate
* @param {Boolean} throwOrLogError pass true to throw (or log) an error if invalid
* @return {Boolean} true if schema is valid
*/
function validateSchema(schema, throwOrLogError) {
var $schema = schema.$schema || self._opts.defaultMeta || defaultMeta();
var currentUriFormat = self._formats.uri;
self._formats.uri = typeof currentUriFormat == 'function'
? SCHEMA_URI_FORMAT_FUNC
: SCHEMA_URI_FORMAT;
var valid;
try { valid = validate($schema, schema); }
finally { self._formats.uri = currentUriFormat; }
if (!valid && throwOrLogError) {
var message = 'schema is invalid: ' + errorsText();
if (self._opts.validateSchema == 'log') console.error(message);
else throw new Error(message);
}
return valid;
}
function defaultMeta() {
var meta = self._opts.meta;
self._opts.defaultMeta = typeof meta == 'object'
? meta.id || meta
: self._opts.v5
? v5.META_SCHEMA_ID
: META_SCHEMA_ID;
return self._opts.defaultMeta;
}
/**
* Get compiled schema from the instance by `key` or `ref`.
* @param {String} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id).
* @return {Function} schema validating function (with property `schema`).
*/
function getSchema(keyRef) {
var schemaObj = _getSchemaObj(keyRef);
switch (typeof schemaObj) {
case 'object': return schemaObj.validate || _compile(schemaObj);
case 'string': return getSchema(schemaObj);
case 'undefined': return _getSchemaFragment(keyRef);
}
}
function _getSchemaFragment(ref) {
var res = resolve.schema.call(self, { schema: {} }, ref);
if (res) {
var schema = res.schema
, root = res.root
, baseId = res.baseId;
var v = compileSchema.call(self, schema, root, undefined, baseId);
self._fragments[ref] = new SchemaObject({
ref: ref,
fragment: true,
schema: schema,
root: root,
baseId: baseId,
validate: v
});
return v;
}
}
function _getSchemaObj(keyRef) {
keyRef = resolve.normalizeId(keyRef);
return self._schemas[keyRef] || self._refs[keyRef] || self._fragments[keyRef];
}
/**
* Remove cached schema(s).
* If no parameter is passed all schemas but meta-schemas are removed.
* If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed.
* Even if schema is referenced by other schemas it still can be removed as other schemas have local references.
* @param {String|Object|RegExp} schemaKeyRef key, ref, pattern to match key/ref or schema object
*/
function removeSchema(schemaKeyRef) {
if (schemaKeyRef instanceof RegExp) {
_removeAllSchemas(self._schemas, schemaKeyRef);
_removeAllSchemas(self._refs, schemaKeyRef);
return;
}
switch (typeof schemaKeyRef) {
case 'undefined':
_removeAllSchemas(self._schemas);
_removeAllSchemas(self._refs);
self._cache.clear();
return;
case 'string':
var schemaObj = _getSchemaObj(schemaKeyRef);
if (schemaObj) self._cache.del(schemaObj.jsonStr);
delete self._schemas[schemaKeyRef];
delete self._refs[schemaKeyRef];
return;
case 'object':
var jsonStr = stableStringify(schemaKeyRef);
self._cache.del(jsonStr);
var id = schemaKeyRef.id;
if (id) {
id = resolve.normalizeId(id);
delete self._schemas[id];
delete self._refs[id];
}
}
}
function _removeAllSchemas(schemas, regex) {
for (var keyRef in schemas) {
var schemaObj = schemas[keyRef];
if (!schemaObj.meta && (!regex || regex.test(keyRef))) {
self._cache.del(schemaObj.jsonStr);
delete schemas[keyRef];
}
}
}
function _addSchema(schema, skipValidation, meta, shouldAddSchema) {
if (typeof schema != 'object') throw new Error('schema should be object');
var jsonStr = stableStringify(schema);
var cached = self._cache.get(jsonStr);
if (cached) return cached;
shouldAddSchema = shouldAddSchema || self._opts.addUsedSchema !== false;
var id = resolve.normalizeId(schema.id);
if (id && shouldAddSchema) checkUnique(id);
var willValidate = self._opts.validateSchema !== false && !skipValidation;
var recursiveMeta;
if (willValidate && !(recursiveMeta = schema.id && schema.id == schema.$schema))
validateSchema(schema, true);
var localRefs = resolve.ids.call(self, schema);
var schemaObj = new SchemaObject({
id: id,
schema: schema,
localRefs: localRefs,
jsonStr: jsonStr,
meta: meta
});
if (id[0] != '#' && shouldAddSchema) self._refs[id] = schemaObj;
self._cache.put(jsonStr, schemaObj);
if (willValidate && recursiveMeta) validateSchema(schema, true);
return schemaObj;
}
function _compile(schemaObj, root) {
if (schemaObj.compiling) {
schemaObj.validate = callValidate;
callValidate.schema = schemaObj.schema;
callValidate.errors = null;
callValidate.root = root ? root : callValidate;
if (schemaObj.schema.$async === true)
callValidate.$async = true;
return callValidate;
}
schemaObj.compiling = true;
var currentOpts;
if (schemaObj.meta) {
currentOpts = self._opts;
self._opts = self._metaOpts;
}
var v;
try { v = compileSchema.call(self, schemaObj.schema, root, schemaObj.localRefs); }
finally {
schemaObj.compiling = false;
if (schemaObj.meta) self._opts = currentOpts;
}
schemaObj.validate = v;
schemaObj.refs = v.refs;
schemaObj.refVal = v.refVal;
schemaObj.root = v.root;
return v;
function callValidate() {
var _validate = schemaObj.validate;
var result = _validate.apply(null, arguments);
callValidate.errors = _validate.errors;
return result;
}
}
/**
* Convert array of error message objects to string
* @param {Array<Object>} errors optional array of validation errors, if not passed errors from the instance are used.
* @param {Object} options optional options with properties `separator` and `dataVar`.
* @return {String} human readable string with all errors descriptions
*/
function errorsText(errors, options) {
errors = errors || self.errors;
if (!errors) return 'No errors';
options = options || {};
var separator = options.separator === undefined ? ', ' : options.separator;
var dataVar = options.dataVar === undefined ? 'data' : options.dataVar;
var text = '';
for (var i=0; i<errors.length; i++) {
var e = errors[i];
if (e) text += dataVar + e.dataPath + ' ' + e.message + separator;
}
return text.slice(0, -separator.length);
}
/**
* Add custom format
* @param {String} name format name
* @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid)
*/
function addFormat(name, format) {
if (typeof format == 'string') format = new RegExp(format);
self._formats[name] = format;
}
function addDraft4MetaSchema() {
if (self._opts.meta !== false) {
var metaSchema = require('./refs/json-schema-draft-04.json');
addMetaSchema(metaSchema, META_SCHEMA_ID, true);
self._refs['http://json-schema.org/schema'] = META_SCHEMA_ID;
}
}
function addInitialSchemas() {
var optsSchemas = self._opts.schemas;
if (!optsSchemas) return;
if (Array.isArray(optsSchemas)) addSchema(optsSchemas);
else for (var key in optsSchemas) addSchema(optsSchemas[key], key);
}
function addInitialFormats() {
for (var name in self._opts.formats) {
var format = sel