From b4bb64eba34df2b07f8b218b1801bc5165fb8167 Mon Sep 17 00:00:00 2001 From: Egor Date: Wed, 15 Feb 2017 23:44:58 -0800 Subject: [PATCH] WIP - chrome --- chrome/app/pods/application/controller.js | 14 +- chrome/app/pods/application/template.hbs | 4 +- .../components/bridge-finder/component.js | 6 +- .../pods/components/hue-controls/component.js | 124 +--- .../pods/components/hue-controls/template.hbs | 21 +- .../pods/components/huegasm-app/component.js | 8 +- .../pods/components/huegasm-app/template.hbs | 4 +- .../components/huegasm-footer/component.js | 21 - .../components/huegasm-footer/template.hbs | 13 - .../pods/components/light-group/component.js | 52 +- .../add-soundcloud-sound-modal/component.js | 46 -- .../add-soundcloud-sound-modal/template.hbs | 14 - .../pods/components/music-tab/component.js | 664 +++--------------- .../components/music-tab/mixins/helpers.js | 412 ----------- .../components/music-tab/mixins/visualizer.js | 94 --- .../pods/components/music-tab/template.hbs | 170 +---- chrome/app/styles/app.scss | 20 - chrome/app/styles/introjs.scss | 18 - chrome/app/styles/music-tab.scss | 199 +----- chrome/bower.json | 6 +- chrome/ember-cli-build.js | 6 - .../assets/images/google-play-badge.png | Bin 4219 -> 0 bytes chrome/public/assets/images/sc-white-sm.png | Bin 292 -> 0 bytes chrome/public/assets/images/sc-white.png | Bin 3452 -> 0 bytes chrome/public/assets/images/soundcloudUrl.png | Bin 56037 -> 0 bytes chrome/public/background.js | 11 + chrome/public/manifest.json | 10 +- 27 files changed, 202 insertions(+), 1735 deletions(-) delete mode 100644 chrome/app/pods/components/huegasm-footer/component.js delete mode 100644 chrome/app/pods/components/huegasm-footer/template.hbs delete mode 100644 chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/component.js delete mode 100644 chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/template.hbs delete mode 100644 chrome/app/pods/components/music-tab/mixins/helpers.js delete mode 100644 chrome/app/pods/components/music-tab/mixins/visualizer.js delete mode 100644 chrome/app/styles/introjs.scss delete mode 100644 chrome/public/assets/images/google-play-badge.png delete mode 100644 chrome/public/assets/images/sc-white-sm.png delete mode 100644 chrome/public/assets/images/sc-white.png delete mode 100644 chrome/public/assets/images/soundcloudUrl.png create mode 100644 chrome/public/background.js diff --git a/chrome/app/pods/application/controller.js b/chrome/app/pods/application/controller.js index 77fa65f..d1497ec 100644 --- a/chrome/app/pods/application/controller.js +++ b/chrome/app/pods/application/controller.js @@ -10,13 +10,11 @@ export default Controller.extend({ dimmerOn: false, lightsIconsOn: true, - init(){ + init() { this._super(...arguments); - let storage = new window.Locally.Store({compress: true}), - dimmerOn = storage.get('huegasm.dimmerOn'), - lightsIconsOn = storage.get('huegasm.lightsIconsOn'); - this.set('storage', storage); + let dimmerOn = chrome.storage.local.get('huegasm.dimmerOn'), + lightsIconsOn = chrome.storage.local.get('huegasm.lightsIconsOn'); if (!isEmpty(dimmerOn) && dimmerOn) { this.send('toggleDimmer'); @@ -33,9 +31,9 @@ export default Controller.extend({ let lightsIconsOn = this.get('lightsIconsOn'); - this.get('storage').set('huegasm.lightsIconsOn', lightsIconsOn); + chrome.storage.local.set('huegasm.lightsIconsOn', lightsIconsOn); }, - toggleDimmer(){ + toggleDimmer() { this.toggleProperty('dimmerOn'); let dimmerOn = this.get('dimmerOn'); @@ -48,7 +46,7 @@ export default Controller.extend({ $('html').removeClass('dimmerOn'); } - this.get('storage').set('huegasm.dimmerOn', dimmerOn); + chrome.storage.local.set('huegasm.dimmerOn', dimmerOn); } } }); diff --git a/chrome/app/pods/application/template.hbs b/chrome/app/pods/application/template.hbs index 38cb29e..3132013 100644 --- a/chrome/app/pods/application/template.hbs +++ b/chrome/app/pods/application/template.hbs @@ -1,3 +1 @@ -{{huegasm-app toggleLightsIcons="toggleLightsIcons" toggleDimmer="toggleDimmer" dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn storage=storage}} - -{{huegasm-footer action="toggleDimmer" dimmerOn=dimmerOn storage=storage}} \ No newline at end of file +{{huegasm-app toggleLightsIcons="toggleLightsIcons" toggleDimmer="toggleDimmer" dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn}} \ No newline at end of file diff --git a/chrome/app/pods/components/bridge-finder/component.js b/chrome/app/pods/components/bridge-finder/component.js index 4f69cd7..603ceac 100644 --- a/chrome/app/pods/components/bridge-finder/component.js +++ b/chrome/app/pods/components/bridge-finder/component.js @@ -61,7 +61,7 @@ export default Component.extend({ if (status === 'success' && result.length === 1) { this.set('bridgeIp', result[0].internalipaddress); - this.get('storage').set('huegasm.bridgeIp', result[0].internalipaddress); + chrome.storage.local.set('huegasm.bridgeIp', result[0].internalipaddress); bridgeFindStatus = 'success'; } else if (result.length > 1) { let multipleBridgeIps = this.get('multipleBridgeIps'); @@ -99,7 +99,7 @@ export default Component.extend({ if (status === 'success' && !result[0].error) { this.clearBridgePingIntervalHandle(); - this.get('storage').set('huegasm.bridgeUsername', result[0].success.username); + chrome.storage.local.set('huegasm.bridgeUsername', result[0].success.username); this.set('bridgeUsername', result[0].success.username); } } @@ -122,7 +122,7 @@ export default Component.extend({ }, chooseBridge(bridge) { this.set('bridgeIp', bridge); - this.get('storage').set('huegasm.bridgeIp', bridge); + chrome.storage.local.set('huegasm.bridgeIp', bridge); }, findBridgeByIp() { let manualBridgeIp = this.get('manualBridgeIp'); diff --git a/chrome/app/pods/components/hue-controls/component.js b/chrome/app/pods/components/hue-controls/component.js index a3a5f9f..dd5c04d 100644 --- a/chrome/app/pods/components/hue-controls/component.js +++ b/chrome/app/pods/components/hue-controls/component.js @@ -78,8 +78,8 @@ export default Component.extend({ setInterval(this.updateLightData.bind(this), 2000); } - if (!isNone(this.get('storage').get('huegasm.selectedTab'))) { - this.set('selectedTab', this.get('storage').get('huegasm.selectedTab')); + if (!isNone(chrome.storage.local.get('huegasm.selectedTab'))) { + this.set('selectedTab', chrome.storage.local.get('huegasm.selectedTab')); } }, @@ -112,12 +112,11 @@ export default Component.extend({ changeTab(tabName) { let index = this.get('tabList').indexOf(tabName); this.set('selectedTab', index); - this.get('storage').set('huegasm.selectedTab', index); + chrome.storage.local.set('huegasm.selectedTab', index); }, clearBridge() { - let storage = this.get('storage'); - storage.remove('huegasm.bridgeUsername'); - storage.remove('huegasm.bridgeIp'); + chrome.storage.local.remove('huegasm.bridgeUsername'); + chrome.storage.local.remove('huegasm.bridgeIp'); location.reload(); }, toggleDimmer() { @@ -127,119 +126,8 @@ export default Component.extend({ this.sendAction('toggleLightsIcons'); }, clearAllSettings() { - this.get('storage').clear(); + chrome.storage.local.clear(); location.reload(); - }, - startIntro() { - let intro = introJs(), - playerBottom = $('#player-bottom'); - - if (this.get('dimmerOn')) { - this.send('toggleDimmer'); - } - - intro.setOptions({ - steps: [ - { - intro: 'Welcome! This short tutorial will introduce you to Huegasm.' - }, - { - element: '#music-tab', - intro: 'This is the music player. You\'ll use this to play music and synchronize it with your active lights.

' + - 'TIP: Control which lights are active through the Lights tab.' - }, - { - element: '#playlist', - intro: 'You can add and select music to play from your playlist here. You may listen to local audio files, stream music from soundcloud or stream directly from a connected microphone.

' + - 'TIP: Songs added through Soundcloud will be saved for when you visit this page again.' - }, - { - element: $('#playlist md-menu')[0], - intro: 'You can add songs from SoundCloud by copy and pasting the URL shown here' - }, - { - element: '#player-area', - intro: 'The audio playback may be controlled with the controls here. Basic music visualization effects may be shown here by selecting them from the menu ( eyeball icon in the bottom right ).' - }, - { - element: '#beat-option-row', - intro: 'These are the settings for the music tab:
' + - 'Sensitivity - The sensitivity of the beat detector ( more sensitivity results in more registered beats )
' + - 'Hue Range - The hue range that the lights may change to on beat.
' + - 'Brightness Range - The minimum ( off-beat ) and maximum ( on-beat ) brightness of the lights.
' + - 'Flashing Transitions - Quickly flash the lights on beat
' + - 'Colorloop - Slowly cycle the lights through all the colors while the music is playing
' + - 'TIP: Your sensitivity settings are saved per song as indicated by the red star icon in the top left corner. These settings they will be restored if you ever listen to the same song again.', - position: 'top' - }, - { - element: '#beat-container', - intro: 'An interactive speaker that will bump when a beat is registered.

' + - 'TIP: Click on the center of the speaker to simulate a beat.', - position: 'top' - }, - { - element: '#lights-tab', - intro: 'This is the lights tab. Here you\'ll be able to change various light properties:
' + - 'Power - Turn the selected lights on/off
' + - 'Brightness - The brightness level of the selected lights
' + - 'Color - The color of the selected lights
' + - 'Strobe - Selected lights will flash in sequential order
' + - 'Colorloop - Selected lights will slowly cycle through all the colors
' - }, - { - element: '#active-lights', - intro: 'These icons represent the hue lights in your system. Active lights will be controlled by the application while the inactive lights will have a red X over them and will not be controlled.
' + - 'You may toggle a light\'s state by clicking on it.' - }, - { - element: $('#navigation .ember-basic-dropdown-trigger')[0], - intro: 'A few miscellaneous settings can be found here.

' + - 'WARNING: clearing application settings will restore the application to its original state. This will even delete your playlist and any saved song beat preferences.' - }, - { - intro: 'And that\'s it...Hope you enjoy the application. ;)' - } - ] - }); - - intro.onexit(() => { - $('body').velocity('scroll', { duration: 200 }); - }); - - intro.onchange((element) => { - if (element.id === '' || element.id === 'music-tab' || element.id === 'playlist' || element.id === 'player-area' || element.id === 'beat-option-row' || element.id === 'beat-option-button-group' || element.id === 'beat-container' || element.id === 'using-mic-audio-tooltip' || element.nodeName === 'MD-MENU') { - $('.navigation-item').eq(1).click(); - } else { - $('.navigation-item').eq(0).click(); - } - - if (element.id === 'music-tab' || element.id === 'playlist' || element.id === 'player-area') { - playerBottom.hide(); - } else if (element.id === 'beat-option-row' || element.id === 'beat-option-button-group' || element.id === 'beat-container') { - playerBottom.show(); - } else if (element.id === 'dimmer') { - $(document).click(); - } - }); - - // skip hidden/missing elements - intro.onafterchange((element) => { - let elem = $(element); - if (elem.html() === '') { - $('.introjs-nextbutton').click(); - } - - if (element.id === '') { - later(this, () => { - $('body').velocity('scroll'); - }, 500); - } else { - later(this, () => { - $('.introjs-tooltip').velocity('scroll', { offset: -100 }); - }, 500); - } - }).start(); } } }); diff --git a/chrome/app/pods/components/hue-controls/template.hbs b/chrome/app/pods/components/hue-controls/template.hbs index 8eb84f0..061c459 100644 --- a/chrome/app/pods/components/hue-controls/template.hbs +++ b/chrome/app/pods/components/hue-controls/template.hbs @@ -11,23 +11,19 @@ {{/paper-button}} {{/menu.trigger}} {{#menu.content width=3 as |content|}} - {{#content.menu-item onClick="toggleDimmer"}} + {{#content.menu-item onClick="toggleDimmer" }} {{paper-icon "highlight" class=dimmerOnClass}} Dark Mode: {{if dimmerOn "On" "Off"}} {{/content.menu-item}} - {{#content.menu-item onClick="toggleLightsIcons"}} + {{#content.menu-item onClick="toggleLightsIcons" }} {{paper-icon "lightbulb outline" class=dimmerOnClass}} Active Lights: {{if lightsIconsOn "Icons" "Text"}} {{/content.menu-item}} - {{#content.menu-item onClick="clearBridge"}} + {{#content.menu-item onClick="clearBridge" }} {{paper-icon "compare arrows" class=dimmerOnClass}} Switch bridge {{/content.menu-item}} - {{#content.menu-item onClick="startIntro"}} - {{paper-icon "cached" class=dimmerOnClass}} Restart tutorial - {{/content.menu-item}} - - {{#content.menu-item onClick="clearAllSettings"}} + {{#content.menu-item onClick="clearAllSettings" }} {{paper-icon "settings backup restore" class=dimmerOnClass}} Reset settings {{/content.menu-item}} {{/menu.content}} @@ -35,12 +31,13 @@ - {{light-group lightsData=lightsData activeLights=activeLights syncLight=syncLight apiURL=apiURL dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn storage=storage}} + {{light-group lightsData=lightsData activeLights=activeLights syncLight=syncLight apiURL=apiURL dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn}}
- {{lights-tab active=(eq selectedTab 0) apiURL=apiURL lightsData=lightsData activeLights=activeLights syncLight=syncLight trial=trial colorLoopOn=colorLoopOn dimmerOn=dimmerOn playing=playing pauseLightUpdates=pauseLightUpdates}} - - {{music-tab active=(eq selectedTab 1) apiURL=apiURL lightsData=lightsData activeLights=activeLights pauseLightUpdates=pauseLightUpdates dimmerOn=dimmerOn storage=storage colorLoopOn=colorLoopOn playing=playing action="startIntro"}} + {{lights-tab active=(eq selectedTab 0) apiURL=apiURL lightsData=lightsData activeLights=activeLights syncLight=syncLight + trial=trial colorLoopOn=colorLoopOn dimmerOn=dimmerOn playing=playing pauseLightUpdates=pauseLightUpdates}} {{music-tab + active=(eq selectedTab 1) apiURL=apiURL lightsData=lightsData activeLights=activeLights pauseLightUpdates=pauseLightUpdates + dimmerOn=dimmerOn colorLoopOn=colorLoopOn playing=playing action="startIntro"}}
{{else}} {{paper-progress-circular diameter=100}} diff --git a/chrome/app/pods/components/huegasm-app/component.js b/chrome/app/pods/components/huegasm-app/component.js index 023d9f6..6c951a1 100644 --- a/chrome/app/pods/components/huegasm-app/component.js +++ b/chrome/app/pods/components/huegasm-app/component.js @@ -15,12 +15,10 @@ export default Component.extend({ init() { this._super(...arguments); - let storage = this.get('storage'); - - if (!isEmpty(storage.get('huegasm.bridgeIp')) && !isEmpty(storage.get('huegasm.bridgeUsername'))) { + if (!isEmpty(chrome.storage.local.get('huegasm.bridgeIp')) && !isEmpty(chrome.storage.local.get('huegasm.bridgeUsername'))) { this.setProperties({ - bridgeIp: storage.get('huegasm.bridgeIp'), - bridgeUsername: storage.get('huegasm.bridgeUsername') + bridgeIp: chrome.storage.local.get('huegasm.bridgeIp'), + bridgeUsername: chrome.storage.local.get('huegasm.bridgeUsername') }); } }, diff --git a/chrome/app/pods/components/huegasm-app/template.hbs b/chrome/app/pods/components/huegasm-app/template.hbs index 41ecb42..78c19e7 100644 --- a/chrome/app/pods/components/huegasm-app/template.hbs +++ b/chrome/app/pods/components/huegasm-app/template.hbs @@ -1,6 +1,6 @@ {{#if bridgeUsername}} {{hue-controls bridgeIp=bridgeIp bridgeUsername=bridgeUsername trial=trial dimmerOn=dimmerOn lightsIconsOn=lightsIconsOn - storage=storage toggleDimmer="toggleDimmer" toggleLightsIcons="toggleLightsIcons"}} + toggleDimmer="toggleDimmer" toggleLightsIcons="toggleLightsIcons"}} {{else}} - {{bridge-finder bridgeIp=bridgeIp bridgeUsername=bridgeUsername trial=trial storage=storage}} + {{bridge-finder bridgeIp=bridgeIp bridgeUsername=bridgeUsername trial=trial}} {{/if}} \ No newline at end of file diff --git a/chrome/app/pods/components/huegasm-footer/component.js b/chrome/app/pods/components/huegasm-footer/component.js deleted file mode 100644 index 0d4aa3a..0000000 --- a/chrome/app/pods/components/huegasm-footer/component.js +++ /dev/null @@ -1,21 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - computed -} = Ember; - -export default Component.extend({ - tagName: 'footer', - classNames: ['footer'], - - year: computed(function(){ - return new Date().getFullYear(); - }), - - actions: { - toggleDimmer(){ - this.sendAction(); - } - } -}); diff --git a/chrome/app/pods/components/huegasm-footer/template.hbs b/chrome/app/pods/components/huegasm-footer/template.hbs deleted file mode 100644 index 9ad0fb3..0000000 --- a/chrome/app/pods/components/huegasm-footer/template.hbs +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - Get it on the Google Play Store - \ No newline at end of file diff --git a/chrome/app/pods/components/light-group/component.js b/chrome/app/pods/components/light-group/component.js index 3f86d23..6def914 100644 --- a/chrome/app/pods/components/light-group/component.js +++ b/chrome/app/pods/components/light-group/component.js @@ -17,7 +17,7 @@ export default Component.extend({ activeLights: A(), // list of all the lights in the hue system - lightsList: computed('lightsData', 'activeLights.[]', 'dimmerOn', function(){ + lightsList: computed('lightsData', 'activeLights.[]', 'dimmerOn', function () { let lightsData = this.get('lightsData'), activeLights = this.get('activeLights'), dimmerOn = this.get('dimmerOn'), @@ -29,7 +29,7 @@ export default Component.extend({ activeClass = 'light-active'; if (lightsData.hasOwnProperty(key) && lightsData[key].state.reachable) { - switch(lightsData[key].modelid){ + switch (lightsData[key].modelid) { case 'LCT001': type = 'a19'; break; @@ -61,7 +61,7 @@ export default Component.extend({ type = 'storylight'; break; case 'LWB004': - type ='a19'; + type = 'a19'; break; case 'LLC020': type = 'huego'; @@ -70,34 +70,34 @@ export default Component.extend({ type = 'a19'; } - if(dimmerOn){ + if (dimmerOn) { type += 'w'; } - if(!activeLights.includes(key)){ + if (!activeLights.includes(key)) { activeClass = 'light-inactive'; } - lightsList.push({type: type, name: lightsData[key].name, id: key, data: lightsData[key], activeClass: activeClass}); + lightsList.push({ type: type, name: lightsData[key].name, id: key, data: lightsData[key], activeClass: activeClass }); } } return lightsList; }), - onActiveLightsChange: observer('activeLights.[]', function(){ - this.get('storage').set('huegasm.activeLights', this.get('activeLights')); + onActiveLightsChange: observer('activeLights.[]', function () { + chrome.storage.local.set('huegasm.activeLights', this.get('activeLights')); }), - init(){ + init() { this._super(...arguments); let lightsData = this.get('lightsData'), activeLights = this.get('activeLights'), - activeLightsCache = this.get('storage').get('huegasm.activeLights'); + activeLightsCache = chrome.storage.local.get('huegasm.activeLights'); - if(!isNone(activeLightsCache)){ - activeLightsCache.forEach(function(i){ + if (!isNone(activeLightsCache)) { + activeLightsCache.forEach(function (i) { if (!isNone(lightsData) && lightsData.hasOwnProperty(i) && lightsData[i].state.reachable) { activeLights.pushObject(i); } @@ -112,26 +112,26 @@ export default Component.extend({ }, actions: { - clickLight(id){ + clickLight(id) { let activeLights = this.get('activeLights'), lightId = activeLights.indexOf(id); - if(lightId !== -1){ + if (lightId !== -1) { activeLights.removeObject(id); } else { activeLights.pushObject(id); this.set('syncLight', id); } }, - lightStartHover(id){ - if(!window.matchMedia || (window.matchMedia("(min-width: 768px)").matches)){ - let hoveredLight = this.get('lightsList').filter(function(light){ + lightStartHover(id) { + if (!window.matchMedia || (window.matchMedia("(min-width: 768px)").matches)) { + let hoveredLight = this.get('lightsList').filter(function (light) { return light.activeClass !== 'unreachable' && light.id === id[0]; }); - if(!isEmpty(hoveredLight) && this.get('noHover') !== true){ - $.ajax(this.get('apiURL') + '/lights/' + id + '/state', { - data: JSON.stringify({"alert": "lselect"}), + if (!isEmpty(hoveredLight) && this.get('noHover') !== true) { + $.ajax(this.get('apiURL') + '/lights/' + id + '/state', { + data: JSON.stringify({ "alert": "lselect" }), contentType: 'application/json', type: 'PUT' }); @@ -140,15 +140,15 @@ export default Component.extend({ this.set('isHovering', true); } }, - lightStopHover(id){ - if(!window.matchMedia || (window.matchMedia("(min-width: 768px)").matches)){ - let hoveredLight = this.get('lightsList').filter(function(light){ + lightStopHover(id) { + if (!window.matchMedia || (window.matchMedia("(min-width: 768px)").matches)) { + let hoveredLight = this.get('lightsList').filter(function (light) { return light.activeClass !== 'unreachable' && light.id === id[0]; }); - if(!isEmpty(hoveredLight) && this.get('noHover') !== true){ - $.ajax(this.get('apiURL') + '/lights/' + id + '/state', { - data: JSON.stringify({"alert": "none"}), + if (!isEmpty(hoveredLight) && this.get('noHover') !== true) { + $.ajax(this.get('apiURL') + '/lights/' + id + '/state', { + data: JSON.stringify({ "alert": "none" }), contentType: 'application/json', type: 'PUT' }); diff --git a/chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/component.js b/chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/component.js deleted file mode 100644 index 072e4a9..0000000 --- a/chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/component.js +++ /dev/null @@ -1,46 +0,0 @@ -import Ember from 'ember'; - -const { - Component, - observer, - computed, - isEmpty, - isNone, - run: { later }, - $ -} = Ember; - -export default Component.extend({ - url: null, - - onIsShowingModalChange: observer('isShowingModal', function(){ - if(this.get('isShowingModal')){ - this.set('url', null); - later(function(){ - $('md-input-container input').focus(); - }, 500); - } - - }), - - saveDisabled: computed('url', function(){ - return isNone(this.get('url')) || isEmpty(this.get('url').trim()); - }), - - didInsertElement: function() { - $(document).keypress((event)=>{ - if(!this.get('saveDisabled') && event.which === 13) { - this.send('add'); - } - }); - }, - - actions: { - close () { - this.sendAction(); - }, - add (){ - this.sendAction('action', this.get('url')); - } - } -}); diff --git a/chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/template.hbs b/chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/template.hbs deleted file mode 100644 index d889152..0000000 --- a/chrome/app/pods/components/music-tab/add-soundcloud-sound-modal/template.hbs +++ /dev/null @@ -1,14 +0,0 @@ -{{#if isShowingModal}} - {{#modal-dialog close="close" alignment="center" translucentOverlay=true attachment="center" targetAttachment="center"}} - -

Enter a SoundCloud track or playlist/set URL

-

( ex. https://soundcloud.com/mrsuicidesheep/tracks )

- - {{paper-input label="SoundCloud URL" icon="search" value=url onChange=(action (mut url))}} - -
- {{paper-button onClick=(action "close") label="Close"}} - {{paper-button class="pull-right" onClick=(action "add") disabled=saveDisabled primary=true label="Add Music"}} -
- {{/modal-dialog}} -{{/if}} \ No newline at end of file diff --git a/chrome/app/pods/components/music-tab/component.js b/chrome/app/pods/components/music-tab/component.js index 92dc0b4..cb62fa5 100644 --- a/chrome/app/pods/components/music-tab/component.js +++ b/chrome/app/pods/components/music-tab/component.js @@ -1,118 +1,128 @@ import Ember from 'ember'; -import helperMixin from './mixins/helpers'; -import visualizerMixin from './mixins/visualizer'; const { + A, Component, observer, isEmpty, isNone, $, + inject: { service }, run: { later, next } } = Ember; -export default Component.extend(helperMixin, visualizerMixin, { - updatePageTitle: observer('playQueuePointer', function () { - let title = 'Huegasm', - playQueuePointer = this.get('playQueuePointer'), - playQueue = this.get('playQueue'); +export default Component.extend({ + classNames: ['col-sm-10', 'col-sm-offset-1', 'col-xs-12'], + classNameBindings: ['active::hidden'], + elementId: 'music-tab', - if (playQueuePointer !== -1) { - let song = playQueue[playQueuePointer]; - if (song.title) { - title = song.title; + dancer: null, - if (song.artist) { - title += (' - ' + song.artist); + notify: service(), + + beatOptions: { + threshold: { + range: { min: 0, max: 0.5 }, + step: 0.01, + defaultValue: 0.3, + pips: { + mode: 'values', + values: [0, 0.25, 0.5], + density: 10, + format: { + to: function (value) { + if (value === 0) { + value = 'More'; + } else if (value === 0.25) { + value = ''; + } else { + value = 'Less'; + } + + return value; + }, + from: function (value) { return value; } } - } else { - title = song.fileName; } + }, + hueRange: { + range: { min: 0, max: 65535 }, + step: 1, + defaultValue: 0.3, + pips: { + mode: 'values', + values: [0, 25500, 46920, 65535], + density: 10, + format: { + to: function (value) { + if (value === 0 || value === 65535) { + value = 'Red'; + } else if (value === 25500) { + value = 'Green'; + } else { + value = 'Blue'; + } - title += '- Huegasm'; + return value; + }, + from: function (value) { return value; } + } + } + }, + brightnessRange: { + range: { min: 1, max: 254 }, + step: 1, + defaultValue: 0, + pips: { + mode: 'values', + values: [1, 50, 100, 150, 200, 254], + density: 10, + format: { + to: function (value) { return value; }, + from: function (value) { return value; } + } + } } + }, - document.title = title; - }), + threshold: 0.3, + hueRange: [0, 65535], + brightnessRange: [1, 254], + oldThreshold: null, - changePlayerControl(name, value, saveBeatPrefs) { + lastLightBopIndex: 0, + + playerBottomDisplayed: true, + audioStream: null, + dimmerOn: false, + + colorloopMode: false, + flashingTransitions: false, + + // 0 - no repeat, 1 - repeat all, 2 - repeat one + repeat: 0, + shuffle: false, + volumeMuted: false, + volume: 100, + // beat detection related pausing + paused: false, + songBeatPreferences: {}, + usingBeatPreferences: false, + oldBeatPrefCache: null, + firstVisit: true, + + // noUiSlider connection specification + filledConnect: [true, false], + hueRangeConnect: [false, true, false], + + changePlayerControl(name, value) { this.set(name, value); if (name === 'threshold') { this.get('kick').set({ threshold: value }); } - if (saveBeatPrefs && this.get('playQueuePointer') !== -1) { - this.saveSongBeatPreferences(); - } - - this.get('storage').set('huegasm.' + name, value); - }, - - saveSongBeatPreferences() { - let song = this.get('playQueue')[this.get('playQueuePointer')]; - if (song) { - let title = isEmpty(song.artist) ? song.fileName : song.artist + '-' + song.title, - songBeatPreferences = this.get('songBeatPreferences'); - - songBeatPreferences[title] = { threshold: this.get('threshold') }; - - this.set('usingBeatPreferences', true); - this.get('storage').set('huegasm.songBeatPreferences', songBeatPreferences); - } - }, - - loadSongBeatPreferences() { - let song = this.get('playQueue')[this.get('playQueuePointer')], - title = isEmpty(song.artist) ? song.fileName : song.artist + '-' + song.title, - songBeatPreferences = this.get('songBeatPreferences'), - preference = songBeatPreferences[title], - oldBeatPrefCache = this.get('oldBeatPrefCache'), - newOldBeatPrefCache = null; - - if (!isNone(preference)) { // load existing beat prefs - newOldBeatPrefCache = { threshold: this.get('threshold') }; - - this.changePlayerControl('threshold', preference.threshold); - this.set('usingBeatPreferences', true); - } else if (!isNone(oldBeatPrefCache)) { // revert to using beat prefs before the remembered song - this.changePlayerControl('threshold', oldBeatPrefCache.threshold); - this.set('usingBeatPreferences', false); - } - - this.set('oldBeatPrefCache', newOldBeatPrefCache); - }, - - clearCurrentAudio(resetPointer) { - let dancer = this.get('dancer'); - - if (dancer.audio.pause) { - dancer.pause(); - } - - if (resetPointer) { - this.set('playQueuePointer', -1); - } - - this.setProperties({ - timeElapsed: 0, - timeTotal: 0, - playing: false - }); - }, - - dragOver() { - let dragLeaveTimeoutHandle = this.get('dragLeaveTimeoutHandle'); - this.set('dragging', true); - - if (dragLeaveTimeoutHandle) { - clearTimeout(dragLeaveTimeoutHandle); - } - }, - - dragLeave() { - // need to delay the dragLeave notification to avoid flickering ( hovering over some page elements causes this event to be sent ) - this.set('dragLeaveTimeoutHandle', setTimeout(() => { this.set('dragging', false); }, 500)); + chrome.storage.local.set('huegasm.' + name, value); }, simulateKick(/*mag, ratioKickMag*/) { @@ -196,7 +206,6 @@ export default Component.extend(helperMixin, visualizerMixin, { navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; let dancer = new Dancer(), - storage = this.get('storage'), kick = dancer.createKick({ threshold: this.get('threshold'), onKick: (mag, ratioKickMag) => { @@ -213,9 +222,9 @@ export default Component.extend(helperMixin, visualizerMixin, { kick: kick }); - ['volume', 'shuffle', 'repeat', 'volumeMuted', 'threshold', 'playerBottomDisplayed', 'songBeatPreferences', 'firstVisit', 'currentVisName', 'playQueue', 'playQueuePointer', 'flashingTransitions', 'colorloopMode', 'hueRange', 'brightnessRange'].forEach((item) => { - if (!isNone(storage.get('huegasm.' + item))) { - let itemVal = storage.get('huegasm.' + item); + ['threshold', 'playerBottomDisplayed', 'flashingTransitions', 'colorloopMode', 'hueRange', 'brightnessRange'].forEach((item) => { + if (!isNone(chrome.storage.local.get('huegasm.' + item))) { + let itemVal = chrome.storage.local.get('huegasm.' + item); if (isNone(this.actions[item + 'Changed'])) { this.set(item, itemVal); @@ -224,12 +233,6 @@ export default Component.extend(helperMixin, visualizerMixin, { } } }); - - this.set('oldPlayQueueLength', this.get('playQueue.length')); - - SC.initialize({ - client_id: this.get('SC_CLIENT_ID') - }); }, didInsertElement() { @@ -237,423 +240,23 @@ export default Component.extend(helperMixin, visualizerMixin, { let self = this; - // file input code - $('#file-input').on('change', function () { - let files = this.files; - self.send('handleNewFiles', files); - this.value = null; // reset in case upload the second file again - }); - - $(document).on('click', '.alert', (event) => { - $(event.target).addClass('removed'); - }); - // prevent space/text selection when the user repeatedly clicks on the center $('#beat-container').on('mousedown', '#beat-speaker-center-inner', function (event) { event.preventDefault(); }); - $(document).keypress((event) => { - if (event.which === 32 && event.target.type !== 'text') { - this.send('play'); - } - }); - - this.$().on('drop', '#play-list-area', (event) => { - this.send('dropFiles', event.dataTransfer.files); - }); - - // control the volume by scrolling up/down - $('#player-area').on('mousewheel', (event) => { - if (this.get('playQueueNotEmpty')) { - let scrollSize = 5; - - if (event.deltaY < 0) { - scrollSize *= -1; - } - let newVolume = this.get('volume') + scrollSize; - - this.send('volumeChanged', newVolume < 0 ? 0 : newVolume); - event.preventDefault(); - } - }); - - // demo tracks - if (this.get('firstVisit')) { - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/candyland-speechless-feat-rkcb'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/dillistone/dillistone-lili-n-rude'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/vallis-alps-young-feki-remix'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/andrew-luce-when-to-love-you-feat-chelsea-cutler'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/ahh-ooh-carefree-with-me'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/crywolf-slow-burn'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/clozee-red-forest'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/elo-method-subranger-solace'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/90-pounds-of-pete-waited-too-long-feat-devon-baldwin'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/mrsuicidesheep/draper-eyes-open'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/itspapaya/sunny'); - this.send('handleNewSoundCloudURL', 'https://soundcloud.com/stonesthrow/nxworries-anderson-paak-knxwledge-suede'); - - this.get('storage').set('huegasm.firstVisit', false); - - this.sendAction(); - } - if (!this.get('playerBottomDisplayed')) { $('#player-bottom').hide(); } }, actions: { - clearPlaylist() { - this.get('playQueue').clear(); - }, - setVisName(name) { - this.set('currentVisName', name); - }, - hideTooltip() { - $('.bootstrap-tooltip').tooltip('hide'); - }, - gotoSCURL(URL) { - // need to pause the music since soundcloud is going to start playing this song anyways - if (this.get('playing')) { - this.send('play'); - } - - this.send('gotoURL', URL); - }, - gotoURL(URL) { - $('.tooltip').remove(); - window.open(URL, '_blank'); - }, - handleNewSoundCloudURL(URL) { - if (URL) { - SC.resolve(URL).then((resultObj) => { - let processResult = (result) => { - if (result.kind === 'user') { - this.get('notify').alert({ html: this.get('scUserNotSupportedHtml') }); - } else if (result.kind === 'track') { - if (result.streamable === true) { - let picture = null; - - if (result.artwork_url) { - picture = result.artwork_url.replace('large', 't67x67'); - } else if (result.user.avatar_url) { - picture = result.user.avatar_url; - } - - $.get(picture) - .done(() => { - this.get('playQueue').pushObject({ url: result.stream_url + '?client_id=' + this.get('SC_CLIENT_ID'), fileName: result.title + ' - ' + result.user.username, artist: result.user.username, scUrl: result.permalink_url, title: result.title, picture: picture }); - }).fail(() => { // no picture - this.get('playQueue').pushObject({ url: result.stream_url + '?client_id=' + this.get('SC_CLIENT_ID'), fileName: result.title + ' - ' + result.user.username, artist: result.user.username, scUrl: result.permalink_url, title: result.title }); - }); - } else { - failedSongs.push(result.title); - } - } else if (result.kind === 'playlist') { - if (result.streamable === true) { - result.tracks.forEach(processResult); - } else { - failedSongs.push(result.title); - } - } - }, - failedSongs = []; - - if (resultObj instanceof Array) { - resultObj.forEach(processResult); - } else { - processResult(resultObj); - } - - if (failedSongs.length > 0) { - this.get('notify').alert({ html: this.get('notStreamableHtml')(failedSongs) }); - } - - if (this.get('playQueuePointer') === -1) { - if (this.get('firstVisit')) { - this.send('goToSong', 0); - } else { - this.send('next'); - } - } - }, () => { - this.get('notify').alert({ html: this.get('urlNotFoundHtml')(URL) }); - }); - } - - this.set('isShowingAddSoundCloudModal', false); - }, - toggleIsShowingAddSoundCloudModal() { - this.toggleProperty('isShowingAddSoundCloudModal'); - }, slideTogglePlayerBottom() { let elem = this.$('#player-bottom'); elem.velocity(elem.is(':visible') ? 'slideUp' : 'slideDown', { duration: 300 }); this.changePlayerControl('playerBottomDisplayed', !this.get('playerBottomDisplayed')); }, - goToSong(index, playSong, scrollToSong) { - let dancer = this.get('dancer'), playQueue = this.get('playQueue'); - - if (dancer.audio) { - this.clearCurrentAudio(true); - } - - if (!isNone(playQueue[index])) { - let audio = new Audio(); - audio.src = this.get('playQueue')[index].url; - - audio.crossOrigin = "anonymous"; - audio.oncanplay = () => { - this.set('timeTotal', Math.floor(audio.duration)); - this.set('soundCloudFuckUps', 0); - }; - audio.onerror = (event) => { - let playQueuePointer = this.get('playQueuePointer'), - song = this.get('playQueue')[playQueuePointer]; - - if (this.get('soundCloudFuckUps') >= this.get('maxSoundCloudFuckUps')) { - this.get('notify').alert({ html: this.get('tooManySoundCloudFuckUps') }); - this.send('play'); - this.set('soundCloudFuckUps', 0); - } else { - if (song.local) { - this.send('removeAudio', playQueuePointer); - } else { - this.send('next', true); - } - - if (event.target.error.code === 2) { - this.get('notify').alert({ html: this.get('failedToDecodeFileHtml')(song.fileName) }); - } else { - this.get('notify').alert({ html: this.get('failedToPlayFileHtml')(song.fileName) }); - } - - this.set('usingBeatPreferences', false); - this.incrementProperty('soundCloudFuckUps'); - } - }; - audio.ontimeupdate = () => { - this.set('timeElapsed', Math.floor(audio.currentTime)); - }; - audio.onended = () => { - this.send('next'); - }; - - dancer.load(audio, 1); - - this.set('playQueuePointer', index); - - this.loadSongBeatPreferences(); - - if (playSong) { - this.send('play'); - } - - if (scrollToSong) { - // this is just a bad workaround to make sure that the track has been rendered to the playlist - next(this, () => { - $('.track' + index).velocity('scroll', { container: $('#play-list-area'), duration: 200 }); - }); - } - } - }, - removeAudio(index) { - this.get('playQueue').removeAt(index); - - // need to manually remove the tooltip - $('body .tooltip').remove(); - - if (index === this.get('playQueuePointer')) { - this.send('goToSong', index, true, true); - } - }, - playerAreaPlay() { - if (isEmpty($('#player-controls:hover')) && this.get('playQueuePointer') !== -1) { - this.send('play'); - - $('#play-notification').velocity({ opacity: 0.8, scale: 1 }, 0).velocity({ opacity: 0, scale: 3 }, 500); - } - }, - play(replayPause) { - let dancer = this.get('dancer'), - playQueuePointer = this.get('playQueuePointer'), - playing = this.get('playing'), - lightsData = this.get('lightsData'); - - if (playQueuePointer !== -1) { - if (playing) { - dancer.pause(); - - let preMusicLightsDataCache = this.get('preMusicLightsDataCache'), - updateLight = (lightIndex) => { - $.ajax(this.get('apiURL') + '/lights/' + lightIndex + '/state', { - data: JSON.stringify({ - 'on': preMusicLightsDataCache[lightIndex].state.on, - 'hue': preMusicLightsDataCache[lightIndex].state.hue, - 'bri': preMusicLightsDataCache[lightIndex].state.bri - }), - contentType: 'application/json', - type: 'PUT' - }); - }; - - for (let key in lightsData) { - if (lightsData.hasOwnProperty(key)) { - later(this, updateLight, key, 1000); - } - } - - if (!replayPause) { - this.set('timeElapsed', Math.floor(dancer.getTime())); - } - } else { - let timeTotal = this.get('timeTotal'); - - if (this.get('volumeMuted')) { - dancer.setVolume(0); - } else { - dancer.setVolume(this.get('volume') / 100); - } - - // replay song - if (this.get('timeElapsed') === timeTotal && timeTotal !== 0) { - this.send('next', true); - return; - } - - $(window).trigger('resize'); // workaround to redraw the canvas for the vitualizer - - this.set('preMusicLightsDataCache', lightsData); - dancer.play(); - } - - this.set('pauseLightUpdates', !playing); - this.onColorloopModeChange(); - this.toggleProperty('playing'); - } - }, - volumeChanged(value) { - this.changePlayerControl('volume', value); - if (this.get('playing')) { - this.get('dancer').setVolume(value / 100); - } - - if (this.get('volume') > 0 && this.get('volumeMuted')) { - this.changePlayerControl('volumeMuted', false); - } - }, - next(repeatAll) { - let playQueuePointer = this.get('playQueuePointer'), - playQueue = this.get('playQueue'), - nextSong = (playQueuePointer + 1), - repeat = this.get('repeat'), - shuffle = this.get('shuffle'); - - if (repeat === 2) { // repeating one song takes precedence over shuffling - if (playQueuePointer === -1 && playQueue.length > 0) { - nextSong = 0; - } else { - nextSong = playQueuePointer; - } - } else if (shuffle) { // next shuffle song - let shufflePlayed = this.get('shufflePlayed'); - - // played all the song in shuffle mode - if (shufflePlayed.length === playQueue.length) { - shufflePlayed.clear(); - this.send('play', true); - return; - } - - // we're going to assume that the song URL is the id - do { - nextSong = Math.floor(Math.random() * playQueue.length); - } while (shufflePlayed.includes(playQueue[nextSong].url)); - - shufflePlayed.pushObject(playQueue[nextSong].url); - } else if (nextSong > playQueue.length - 1) { - if (repeat === 1 || repeatAll) { - nextSong = nextSong % playQueue.length; - } else { - this.send('play', true); - return; - } - } - - this.send('goToSong', nextSong, true, true); - }, - previous() { - if (this.get('timeElapsed') > 5) { - this.send('seekChanged', 0); - } else { - let nextSong = this.get('playQueuePointer'), - playQueue = this.get('playQueue'); - - if (this.get('shuffle') && !isNone(playQueue[nextSong])) { // go to the previously shuffled song - let shufflePlayed = this.get('shufflePlayed'), - shuffledSongIndx = this.get('shufflePlayed').indexOf(playQueue[nextSong].url), - i = 0; - - if (shufflePlayed.length > 0 && shuffledSongIndx !== -1) { // only if there was one - nextSong = shuffledSongIndx - 1; - - if (nextSong < 0) { - nextSong = shufflePlayed.length - 1; - } - - playQueue.some(function (item) { // try to find the previous song id - if (item.url === shufflePlayed[nextSong]) { - nextSong = i; - return true; - } - i++; - - return false; - }); - } - } else { - nextSong--; - - if (nextSong < 0) { - nextSong = playQueue.length - 1; - } - } - - this.send('goToSong', nextSong, true, true); - } - }, - seekChanged(position) { - let dancer = this.get('dancer'); - - if (dancer.audio) { - dancer.audio.currentTime = Math.floor(this.get('timeTotal') * position / 100); - } - }, - volumeMutedChanged(value) { - let dancer = this.get('dancer'), - volumeMuted = isNone(value) ? !this.get('volumeMuted') : value; - - this.changePlayerControl('volumeMuted', volumeMuted); - - if (this.get('playing')) { - if (volumeMuted) { - dancer.setVolume(0); - } else { - dancer.setVolume(this.get('volume') / 100); - } - } - }, - addLocalAudio: function () { - $('#file-input').click(); - }, - shuffleChanged(value) { - this.changePlayerControl('shuffle', isNone(value) ? !this.get('shuffle') : value); - }, - repeatChanged(value) { - this.changePlayerControl('repeat', isNone(value) ? (this.get('repeat') + 1) % 3 : value); - }, playerBottomDisplayedChanged(value) { this.changePlayerControl('playerBottomDisplayed', value); }, @@ -666,70 +269,9 @@ export default Component.extend(helperMixin, visualizerMixin, { hueRangeChanged(value) { this.changePlayerControl('hueRange', value); }, - playQueuePointerChanged(value) { - this.send('goToSong', value, false, true); - }, clickSpeaker() { this.simulateKick(1); }, - dropFiles(files) { - this.setProperties({ - dragging: false, - draggingOverPlayListArea: false - }); - this.send('handleNewFiles', files); - }, - playerListAreaDragOver() { - this.set('draggingOverPlayListArea', true); - }, - playerListAreaDragLeave() { - this.set('draggingOverPlayListArea', false); - }, - handleNewFiles(files) { - let self = this, - playQueue = this.get('playQueue'), - updatePlayQueue = function () { - let tags = ID3.getAllTags("local"), - picture = null; - - if (tags.picture) { - let base64String = ""; - for (let i = 0; i < tags.picture.data.length; i++) { - base64String += String.fromCharCode(tags.picture.data[i]); - } - - picture = "data:" + tags.picture.format + ";base64," + window.btoa(base64String); - } - - playQueue.pushObject({ - fileName: this.name.replace(/\.[^/.]+$/, ""), - url: URL.createObjectURL(this), - artist: tags.artist, - title: tags.title, - picture: picture, - local: true - }); - - ID3.clearAll(); - - if (self.get('playQueuePointer') === -1) { - self.send('next'); - } - }; - - for (let key in files) { - if (files.hasOwnProperty(key)) { - let file = files[key]; - - if (file.type.startsWith('audio')) { - ID3.loadTags("local", updatePlayQueue.bind(file), { - dataReader: new FileAPIReader(file), - tags: ['title', 'artist', 'album', 'track', 'picture'] - }); - } - } - } - }, toggleDimmer() { this.sendAction('toggleDimmer'); } diff --git a/chrome/app/pods/components/music-tab/mixins/helpers.js b/chrome/app/pods/components/music-tab/mixins/helpers.js deleted file mode 100644 index 2d3d395..0000000 --- a/chrome/app/pods/components/music-tab/mixins/helpers.js +++ /dev/null @@ -1,412 +0,0 @@ -import Ember from 'ember'; - -const { - Mixin, - observer, - computed, - isNone, - run, - $, - inject, - on, - A -} = Ember; - -export default Mixin.create({ - classNames: ['col-sm-10', 'col-sm-offset-1', 'col-xs-12'], - classNameBindings: ['active::hidden'], - elementId: 'music-tab', - - dancer: null, - - notify: inject.service(), - - beatOptions: { - threshold: { - range: {min: 0, max: 0.5}, - step: 0.01, - defaultValue: 0.3, - pips: { - mode: 'values', - values: [0, 0.25, 0.5], - density: 10, - format: { - to: function ( value ) { - if(value === 0) { - value = 'More'; - } else if(value === 0.25) { - value = ''; - } else { - value = 'Less'; - } - - return value; - }, - from: function ( value ) { return value; } - } - } - }, - hueRange: { - range: {min: 0, max: 65535}, - step: 1, - defaultValue: 0.3, - pips: { - mode: 'values', - values: [0, 25500, 46920, 65535], - density: 10, - format: { - to: function ( value ) { - if(value === 0 || value === 65535) { - value = 'Red'; - } else if(value === 25500 ) { - value = 'Green'; - } else { - value = 'Blue'; - } - - return value; - }, - from: function ( value ) { return value; } - } - } - }, - brightnessRange: { - range: {min: 1, max: 254}, - step: 1, - defaultValue: 0, - pips: { - mode: 'values', - values: [1, 50, 100, 150, 200, 254], - density: 10, - format: { - to: function ( value ) { return value; }, - from: function ( value ) { return value; } - } - } - } - }, - - threshold: 0.3, - hueRange: [0, 65535], - brightnessRange: [1, 254], - oldThreshold: null, - - playQueuePointer: -1, - playQueue: A(), - timeElapsed: 0, - timeTotal: 0, - lastLightBopIndex: 0, - - playerBottomDisplayed: true, - dragging: false, - draggingOverPlayListArea: false, - dragLeaveTimeoutHandle: null, - audioStream: null, - dimmerOn: false, - isShowingAddSoundCloudModal: false, - - colorloopMode: false, - flashingTransitions: false, - - // 0 - no repeat, 1 - repeat all, 2 - repeat one - repeat: 0, - shuffle: false, - volumeMuted: false, - volume: 100, - // beat detection related pausing - paused: false, - // audio: playing or paused - playing: false, - songBeatPreferences: {}, - usingBeatPreferences: false, - oldBeatPrefCache: null, - storage: null, - firstVisit: true, - - soundCloudFuckUps: 0, - maxSoundCloudFuckUps: 3, - - // used to insure that we don't replay the same thing multiple times in shuffle mode - shufflePlayed: [], - - // noUiSlider connection specification - filledConnect: [true, false], - hueRangeConnect: [false, true, false], - - SC_CLIENT_ID: 'aeec0034f58ecd85c2bd1deaecc41594', - scUserNotSupportedHtml: '', - tooManySoundCloudFuckUps: '', - notStreamableHtml(fileNames){ - let html = ''; - - return html; - }, - urlNotFoundHtml(url){ - return ''; - }, - failedToPlayFileHtml(fileName){ - return ''; - }, - failedToDecodeFileHtml(fileName){ - return ''; - }, - - scUrl: computed('playQueuePointer', 'playQueue.[]', function(){ - let rtn = null, - currentSong = this.get('playQueue')[this.get('playQueuePointer')]; - - if(currentSong && currentSong.scUrl){ - rtn = currentSong.scUrl; - } - - return rtn; - }), - - playQueueEmpty: computed.empty('playQueue'), - playQueueNotEmpty: computed.notEmpty('playQueue'), - playQueueMultiple: computed('playQueue.[]', function(){ - return this.get('playQueue').length > 1; - }), - - seekPosition: computed('timeElapsed', 'timeTotal', function(){ - let timeTotal = this.get('timeTotal'), - timeElapsed = this.get('timeElapsed'); - - if (timeTotal === 0) { - return 0; - } - - return timeElapsed/timeTotal*100; - }), - - largeArtworkPic: computed('playQueuePointer', 'currentVisName', function(){ - let pic = '', - currentVisName = this.get('currentVisName'), - playQueuePointer = this.get('playQueuePointer'), - playQueue = this.get('playQueue'); - - if(playQueuePointer !== -1 && currentVisName === 'None'){ - let song = playQueue[playQueuePointer]; - if(!isNone(song.picture)){ - pic = song.picture; - - if(song.scUrl){ - pic = pic.replace('67x67', '500x500'); - } - } - } - - return pic; - }), - - repeatIcon: computed('repeat', function() { - if(this.get('repeat') === 2) { - return 'repeat-one'; - } - - return 'repeat'; - }), - - playingIcon: computed('playing', function() { - if(this.get('playing')){ - return 'pause'; - } else if(this.get('timeElapsed') === this.get('timeTotal') && this.get('timeTotal') !== 0){ - return 'replay'; - } else { - return 'play-arrow'; - } - }), - - playerAreaClickIcon: computed('playing', function() { - if(this.get('playing')){ - return 'play-arrow'; - } else { - return 'pause'; - } - }), - - playListAreaClass: computed('dragging', 'draggingOverPlayListArea', 'dimmerOn', function(){ - let classes = 'pointer'; - - if(this.get('dragging')){ - classes += ' drag-here-highlight'; - } - - if(this.get('draggingOverPlayListArea')){ - classes += ' dragging-over'; - } - - if(this.get('dimmerOn')){ - classes += ' dimmerOn'; - } - - return classes; - }), - - dimmerOnClass: computed('dimmerOn', function(){ - return this.get('dimmerOn') ? 'dimmerOn' : null; - }), - - volumeMutedClass: computed('volumeMuted', function(){ - let classes = 'player-control-icon volumeButton'; - - if(this.get('volumeMuted')){ - classes += ' active'; - } - - return classes; - }), - - repeatClass: computed('repeat', function(){ - return this.get('repeat') !== 0 ? 'player-control-icon active' : 'player-control-icon'; - }), - - shuffleClass: computed('shuffle', function(){ - return this.get('shuffle') ? 'player-control-icon active' : 'player-control-icon'; - }), - - volumeIcon: computed('volumeMuted', 'volume', function() { - let volume = this.get('volume'); - - if (this.get('volumeMuted')) { - return "volume-off"; - } else if (volume >= 70) { - return "volume-up"; - } else if (volume > 10) { - return "volume-down"; - } else { - return 'volume-mute'; - } - }), - - beatDetectionAreaArrowIcon: computed('playerBottomDisplayed', function(){ - if(!this.get('playerBottomDisplayed')){ - return 'keyboard-arrow-down'; - } else { - return 'keyboard-arrow-up'; - } - }), - - timeElapsedTxt: computed('timeElapsed', function(){ - return this.formatTime(this.get('timeElapsed')); - }), - - timeTotalTxt: computed('timeTotal', function() { - return this.formatTime(this.get('timeTotal')); - }), - - onPlayQueueChange: observer('playQueue.length', function(){ - let playQueueLength = this.get('playQueue.length'); - - if(playQueueLength > this.get('oldPlayQueueLength')){ - run.once(this, ()=>{ - run.next(this, function() { - $(`.track${playQueueLength-1}`).velocity('scroll', { container: $('#play-list-area'), duration: 200 }); - }); - }); - } - - this.set('oldPlayQueueLength', playQueueLength); - }), - - onColorloopModeChange: observer('colorloopMode', 'playing', function(){ - this.set('colorLoopOn', this.get('playing') && this.get('colorloopMode')); - }), - - onOptionChange: observer('flashingTransitions', 'playQueue.[]', 'playQueuePointer', 'colorloopMode', function(self, option){ - option = option.replace('.[]', ''); - let value = this.get(option); - - // can't really save local music - if(option === 'playQueue'){ - value = value.filter((song)=>{ - return !song.url.startsWith('blob:'); - }); - } - - this.get('storage').set('huegasm.' + option, value); - }), - - onRepeatChange: on('init', observer('repeat', function () { - let tooltipTxt = 'Repeat all', type = 'repeat'; - - if (this.get(type) === 1) { - tooltipTxt = 'Repeat one'; - } else if (this.get(type) === 2) { - tooltipTxt = 'Repeat off'; - } - - this.changeTooltipText(type, tooltipTxt); - })), - - onShuffleChange: on('init', observer('shuffle', function () { - let tooltipTxt = 'Shuffle', type = 'shuffle'; - - if (this.get(type)) { - this.get('shufflePlayed').clear(); - tooltipTxt = 'Unshuffle'; - } - - this.changeTooltipText(type, tooltipTxt); - })), - - onVolumeMutedChange: on('init', observer('volumeMuted', function() { - let tooltipTxt = 'Mute', type = 'volumeMuted', - volumeMuted = this.get(type), dancer = this.get('dancer'), - volume=0; - - if (volumeMuted) { - tooltipTxt = 'Unmute'; - volume = 0; - } else { - volume = this.get('volume')/100; - } - - if(this.get('playing')){ - dancer.setVolume(volume); - } - - this.changeTooltipText(type, tooltipTxt); - })), - - onPrevChange: on('init', observer('timeElapsed', 'playQueueNotEmpty', 'playQueue.[]', function() { - if(this.get('playQueueNotEmpty')){ - let tooltipTxt = 'Previous', type = 'prev'; - - if(this.get('timeElapsed') > 5 || this.get('playQueue').length === 1) { - tooltipTxt = 'Replay'; - } - - this.changeTooltipText(type, tooltipTxt); - } - })), - - onPlayingChange: on('init', observer('playing', function () { - let tooltipTxt = 'Play', type = 'playing'; - - if (this.get(type)) { - tooltipTxt = 'Pause'; - } else if(this.get('timeElapsed') === this.get('timeTotal') && this.get('timeTotal') !== 0){ - tooltipTxt = 'Replay'; - } - - this.changeTooltipText(type, tooltipTxt); - })), - - changeTooltipText(type, text) { - // change the tooltip text if it's already visible - $('#' + type + 'Tooltip + .tooltip .tooltip-inner').html(text); - //change the tooltip text for hover - $('#' + type + 'Tooltip').attr('data-original-title', text); - - if(isNone(this.get(type + 'TooltipTxt'))) { - this.set(type + 'TooltipTxt', text); - } - }, - - formatTime(time){ - return this.pad(Math.floor(time/60), 2) + ':' + this.pad(time%60, 2); - }, - - pad(num, size){ return ('000000000' + num).substr(-size); } -}); diff --git a/chrome/app/pods/components/music-tab/mixins/visualizer.js b/chrome/app/pods/components/music-tab/mixins/visualizer.js deleted file mode 100644 index 17dcfe2..0000000 --- a/chrome/app/pods/components/music-tab/mixins/visualizer.js +++ /dev/null @@ -1,94 +0,0 @@ -import Ember from 'ember'; - -const { - Mixin, - observer, - $ -} = Ember; - -export default Mixin.create({ - currentVisName: 'None', - - visNames: ['None', 'Bars', 'Wave'], - - onCurrentVisNameChange: observer('currentVisName', function () { - let currentVisName = this.get('currentVisName'); - - if(currentVisName === 'None'){ - let canvasEl = $('#visualization')[0], - ctx = canvasEl.getContext('2d'); - - ctx.clearRect(0, 0, canvasEl.width, canvasEl.height); - } - - this.get('storage').set('huegasm.currentVisName', currentVisName); - }), - - didInsertElement(){ - let dancer = this.get('dancer'), - canvas = $('#visualization')[0], - playerArea = $('#player-area'), - ctx = canvas.getContext('2d'), - spacing = 2, - h = playerArea.height(), w; - - canvas.height = h; - - // must be done to preserver resolution so that things don't appear blurry - // note that the height is set to 400px via css so it doesn't need to be recalculated - let syncCanvasHeight = ()=>{ - w = playerArea.width(); - canvas.width = w; - }; - - syncCanvasHeight(); - - $(window).on('resize', syncCanvasHeight); - - dancer.bind('update', () => { - let currentVisName = this.get('currentVisName'), - gradient = ctx.createLinearGradient(0, 0, 0, h), - pageHidden = document.hidden || document.msHidden || document.webkitHidden || document.mozHidden; - - // dont do anything if the page is hidden or no visualization - if(currentVisName === 'None' || pageHidden || !this.get('active')){ - return; - } - - ctx.clearRect(0, 0, w, h); - - if (currentVisName === 'Wave') { - let width = 3, - count = 1024; - - gradient.addColorStop(0.6, 'white'); - gradient.addColorStop(0, '#0036FA'); - - ctx.lineWidth = 1; - ctx.strokeStyle = gradient; - let waveform = dancer.getWaveform(); - - ctx.beginPath(); - ctx.moveTo(0, h / 2); - for (let i = 0, l = waveform.length; i < l && i < count; i++) { - ctx.lineTo(i * ( spacing + width ), ( h / 2 ) + waveform[i] * ( h / 2 )); - } - ctx.stroke(); - ctx.closePath(); - } else if (currentVisName === 'Bars') { - let width = 4, - count = 128; - - gradient.addColorStop(1, '#0f0'); - gradient.addColorStop(0.6, '#ff0'); - gradient.addColorStop(0.2, '#F12B24'); - - ctx.fillStyle = gradient; - let spectrum = dancer.getSpectrum(); - for (let i = 0, l = spectrum.length; i < l && i < count; i++) { - ctx.fillRect(i * ( spacing + width ), h, width, -spectrum[i] * h - 60); - } - } - }); - } -}); diff --git a/chrome/app/pods/components/music-tab/template.hbs b/chrome/app/pods/components/music-tab/template.hbs index 8ed2441..2dac6a5 100644 --- a/chrome/app/pods/components/music-tab/template.hbs +++ b/chrome/app/pods/components/music-tab/template.hbs @@ -1,127 +1,3 @@ -
-
- - -
- -
- - {{paper-icon playerAreaClickIcon id="play-notification"}} - -
- {{range-slider start=seekPosition min=0 max=100 connect=filledConnect id="seek-slider" on-change="seekChanged"}} - - {{#if playQueueNotEmpty}} - {{paper-icon "skip-previous" class="player-control-icon"}}{{/if}}{{paper-icon playingIcon class="player-control-icon"}}{{#if playQueueMultiple}}{{paper-icon "skip-next" action="" class="player-control-icon"}}{{/if}}{{paper-icon icon=volumeIcon class=volumeMutedClass}}{{range-slider start=volume min=0 max=100 connect=filledConnect on-change="volumeChanged" id="volume-bar" class="hidden-xs"}} - -
{{timeElapsedTxt}} / {{timeTotalTxt}}
- - {{#paper-menu as |menu|}} - {{#menu.trigger}} - {{#paper-button iconButton=true}} - {{paper-icon "remove-red-eye" class="player-control-icon"}} - {{/paper-button}} - {{/menu.trigger}} - {{#menu.content width=2 as |content|}} - {{#each visNames as |name|}} - {{#content.menu-item onClick=(action "setVisName" name)}} - {{name}} - - {{#if (eq currentVisName name)}} - {{paper-icon "check" classNames=dimmerOnClass}} - {{/if}} - {{/content.menu-item}} - {{/each}} - {{/menu.content}} - {{/paper-menu}} - - {{#if scUrl}} - - - - - {{/if}} -
-
- -
- - -
- {{#paper-menu as |menu|}} - {{#menu.trigger}} - {{#paper-button iconButton=false}} - {{paper-icon "playlist add" class="player-control-icon"}} Add new music - {{/paper-button}} - {{/menu.trigger}} - {{#menu.content width=3 as |content|}} - {{#content.menu-item onClick="addLocalAudio"}} - {{paper-icon "attachment" class=shuffleClass}} Local file - {{/content.menu-item}} - {{#content.menu-item onClick="toggleIsShowingAddSoundCloudModal"}} - {{paper-icon "cloud" class=shuffleClass}} SoundCloud - {{/content.menu-item}} - {{/menu.content}} - {{/paper-menu}} - - {{paper-icon "shuffle" class=shuffleClass}} - {{paper-icon repeatIcon class=repeatClass}} - -
- -
- {{#if (or playQueueEmpty dragging)}} -
- {{#if dragging}} - Drag your music files here - {{else}} - Add your music files here - {{/if}} -
- {{paper-icon "library-music" class=dimmerOnClass}} - {{/if}} - - {{#each playQueue as |item index|}} -
- {{#if item.picture}} - - {{else}} - - {{/if}} - -
- {{#if item.title}} -
{{item.title}}
-
- {{#if item.artistUrl}} - {{item.artist}} - {{else}} - {{item.artist}} - {{/if}} -
- {{else}} - {{item.fileName}} - {{/if}} -
- - {{paper-icon "close" classNames="close"}} -
- {{/each}} -
-
-
-
{{paper-icon beatDetectionAreaArrowIcon id="beat-detection-area-arrow-icon"}} @@ -130,45 +6,47 @@
- {{#if usingBeatPreferences}} - - {{paper-icon "star" class=dimmerOnClass}} - - {{/if}} -
- + Hue Range - + - {{range-slider start=hueRange orientation="vertical" step=beatOptions.hueRange.step range=beatOptions.hueRange.range connect=hueRangeConnect on-change="hueRangeChanged" pips=beatOptions.hueRange.pips}} + {{range-slider start=hueRange orientation="vertical" step=beatOptions.hueRange.step range=beatOptions.hueRange.range connect=hueRangeConnect + on-change="hueRangeChanged" pips=beatOptions.hueRange.pips}}
- + Brightness Range - + - {{range-slider start=brightnessRange orientation="vertical" step=beatOptions.brightnessRange.step range=beatOptions.brightnessRange.range on-change="brightnessRangeChanged" pips=beatOptions.brightnessRange.pips}} + {{range-slider start=brightnessRange orientation="vertical" step=beatOptions.brightnessRange.step range=beatOptions.brightnessRange.range + on-change="brightnessRangeChanged" pips=beatOptions.brightnessRange.pips}}
- + Sensitivity - + - {{range-slider start=threshold orientation="vertical" step=beatOptions.threshold.step range=beatOptions.threshold.range on-change="thresholdChanged" pips=beatOptions.threshold.pips}} + {{range-slider start=threshold orientation="vertical" step=beatOptions.threshold.step range=beatOptions.threshold.range on-change="thresholdChanged" + pips=beatOptions.threshold.pips}}
- + {{paper-checkbox value=flashingTransitions onChange=(action (mut flashingTransitions)) label="Flashing Transitions"}} - + - - {{paper-checkbox value=colorloopMode onChange=(action (mut colorloopMode)) label="Colorloop"}} - + + {{paper-checkbox value=colorloopMode onChange=(action (mut colorloopMode)) label="Colorloop"}} +
@@ -189,6 +67,4 @@
- - -{{music-tab/add-soundcloud-sound-modal action="handleNewSoundCloudURL" isShowingModal=isShowingAddSoundCloudModal}} \ No newline at end of file + \ No newline at end of file diff --git a/chrome/app/styles/app.scss b/chrome/app/styles/app.scss index c0b4812..61c4712 100644 --- a/chrome/app/styles/app.scss +++ b/chrome/app/styles/app.scss @@ -10,7 +10,6 @@ @import 'common'; @import 'dimmer'; @import 'fancy-speaker'; -@import 'introjs'; @import 'hue-controls'; @import 'light-group'; @import 'music-tab'; @@ -38,25 +37,6 @@ body, button { padding-bottom: 50px; } -.footer { - margin: 0 auto 10px auto; - width: 100%; - max-width: 800px; - text-align: center; - display: flex; - align-items: center; - justify-content: space-around; - -} - -.footer-text { - display: inline-block; - font-size: 18px; - a { - margin-left: 5px; - } -} - .alert { margin-bottom: 0; border: none; diff --git a/chrome/app/styles/introjs.scss b/chrome/app/styles/introjs.scss deleted file mode 100644 index fc7b5a1..0000000 --- a/chrome/app/styles/introjs.scss +++ /dev/null @@ -1,18 +0,0 @@ -#settings.introjs-fixParent { - position: inherit !important; -} - -.introjs-tooltip { - width: 300px; -} - -.introjs-skipbutton { - color: $secondaryThemeColor; -} - -.introjs-bullets ul li a.active { - position: relative; - height: 10px; - width: 10px; - top: -2px; -} diff --git a/chrome/app/styles/music-tab.scss b/chrome/app/styles/music-tab.scss index 21b9fc4..14a1202 100644 --- a/chrome/app/styles/music-tab.scss +++ b/chrome/app/styles/music-tab.scss @@ -23,70 +23,6 @@ color: lighten($playerDefaultIconColor, 30%) !important; } -#player-controls { - transition: all 0.2s ease-in-out; - position: absolute; - bottom: 0; - left: 0; - padding: 15px 10px; - width: 100%; - color: white !important; - z-index: 20; - cursor: default; - background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 1)); - .ember-basic-dropdown-trigger { - position: absolute; - right: 0; - bottom: 13px; - } - .tooltip.top { - margin-top: -17px; - } - .tooltip-arrow { - display: none; - } - md-menu-item>.md-button md-icon { - margin: auto 0 5px 10px; - } - .play-arrow, - .pause, - .replay { - font-size: 30px; - } -} - -#player-time-controls { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - display: inline-block; - margin-left: 1em; -} - -.player-control-icon { - color: $playerDefaultIconColor !important; - transition-duration: 0.1s; - margin-right: 10px; - margin-top: 4px; - font-size: 22px; -} - -.player-control-icon.active { - color: $secondaryThemeColor !important; -} - -.player-control-icon:hover { - color: white !important; -} - -#play-notification { - position: relative; - color: white !important; - background: black; - top: 50%; - left: 50%; - opacity: 0; - border-radius: 100%; -} #player-area { height: $playerHeight; @@ -153,129 +89,11 @@ box-shadow: none; } -#play-list-controls { - min-height: 40px; - margin-top: 5px; - border-bottom: 1px solid #3a3a3a; - position: relative; - button .player-control-icon { - margin: 0 5px 1px 3px; - } - .ember-basic-dropdown-trigger { - position: absolute; - bottom: 0; - right: 0; - color: $whitish; - .paper-button { - margin: 0; - } - } -} - -#play-list-area { - background-color: white; - width: 100%; - height: 350px; - margin: 0 auto; - border-radius: 5px; - transition: 0.1s all ease-in-out; - position: relative; - overflow: auto; - #dragHere { - position: absolute; - top: 27%; - font-size: 20px; - text-align: center; - width: 100%; - } - [md-font-icon="library-music"] { - position: absolute; - top: 40%; - font-size: 100px; - opacity: 0.5; - width: 100%; - text-align: center; - } -} - -.song-artist { - font-weight: bold; -} - -#play-list-area.drag-here-highlight { - background-color: white; - border: 5px dotted #5383ff; -} - -#play-list-area.dragging-over { - background-color: darken(white, 5%); - box-shadow: inset 0 0 20px 0 rgba(0, 0, 0, 1); -} - -#file-input { - width: 1px; - height: 1px; - visibility: hidden; -} - -.playlist-item { - border-bottom: 1px solid rgba(128, 128, 128, 0.3); - border-top: 1px solid rgba(128, 128, 128, 0.3); - height: 62px; - font-family: 'Open Sans', sans-serif; - padding: 0 20px 0 5px; - position: relative; - color: $blackish; - background: darken(white, 5%); - .close { - font-size: 18px; - } - .album-art { - height: 60px; - float: left; - margin-right: 5px; - border: 1px solid rgba(0, 0, 0, 0.5); - } - .song-info { - .song-title { - max-height: 40px; - overflow: hidden; - } - .song-artist { - max-height: 20px; - overflow: hidden; - } - } - .audio-remove-button { - position: absolute; - top: 10px; - right: 0; - padding: 10px; - } -} - -.playlist-item.active { - background: darken(white, 15%) !important; - border-top: 1px solid $secondaryThemeColor; - border-bottom: 1px solid $secondaryThemeColor; -} - -.playlist-item:hover { - background: darken(white, 10%); - .close { - display: block; - } -} - #beat-area { position: relative; padding: 0; } -.star { - cursor: auto !important; -} - #beat-option-button-group { margin: 20px 0 10px 0; } @@ -363,11 +181,7 @@ bottom: 22px; } -#visualization { - position: absolute; - top: 0; - left: 0; -} + #save-beat-preferences-star { position: absolute; @@ -390,17 +204,6 @@ background-size: 80px 80px; } -#artwork { - position: absolute; - width: 100%; - overflow: hidden; - img { - display: block; - margin: 0 auto; - max-height: 400px; - } -} - .keyboard-arrow-down { font-size: 20px; } diff --git a/chrome/bower.json b/chrome/bower.json index 66eb724..8d7b790 100644 --- a/chrome/bower.json +++ b/chrome/bower.json @@ -1,14 +1,10 @@ { "name": "huegasm", "dependencies": { - "JavaScript-ID3-Reader": "https://github.com/aadsm/JavaScript-ID3-Reader.git", "bootstrap-sass": "^3.3.5", "hammer.js": "^2.0.8", - "intro.js": "^2.1.0", - "jquery-mousewheel": "^3.1.13", - "locallyjs": "^0.3.2", "matchMedia": "^0.3.0", "nouislider": "^9.0.0", "velocity": "^1.3.1" } -} +} \ No newline at end of file diff --git a/chrome/ember-cli-build.js b/chrome/ember-cli-build.js index 85f25c8..e4b3409 100644 --- a/chrome/ember-cli-build.js +++ b/chrome/ember-cli-build.js @@ -17,12 +17,6 @@ module.exports = function (defaults) { app.import('vendor/dancer.js'); app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/tooltip.js'); - app.import('bower_components/intro.js/intro.js'); - app.import('bower_components/intro.js/introjs.css'); - app.import('bower_components/intro.js/themes/introjs-nassim.css'); - app.import('bower_components/JavaScript-ID3-Reader/dist/id3-minimized.js'); - app.import('bower_components/jquery-mousewheel/jquery.mousewheel.js'); - app.import('bower_components/locallyjs/dist/locally.min.js'); app.import('bower_components/velocity/velocity.js'); return app.toTree(extraAssets); diff --git a/chrome/public/assets/images/google-play-badge.png b/chrome/public/assets/images/google-play-badge.png deleted file mode 100644 index 585006ba67325d936f202ac2dd7ec385dbdb2cfa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4219 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

6+Ce#m#lwe`wskBX z9v-;y@i_3SN7~v~V!5=sTG-mkN<~FQ0fiba)({H1y1HIID8@}p#J9A_{@b3To=4LB zTf@VE&V`=%S6FO<;FA(-{5wsG8#hP*(7f4w%T)x>peNx-6VM?8KKJ(@x+$L4Smz6f z)I$aCSt~6kuQdiMieDpmVJm4f8$%*yfWg|{%&Y^Skuvw|Lpv{&f{m3 z<gmw}o!`D~e<}KG8x#^!*wnIJ{>cv>J|_g21v5KsDp0^M7zvTwT#yzvM9N$Z{d)9wcTv=RYWo35daCVh%d=Gtm#7A;uqf?bHSmXn@H2nRg zs5s=tI!HI)BpNAE)g#Pi-Y-HVtn?>hL(gl9zJ-Q+NaQw6LjrfLZEbn6?eE_4idwbj zfXtVim}p~adJ{>&6{xHITv>6%EMfQ_f(sc;PQHOz0u%EZ+Qr2M{^e81;V4)TfCicy znX4RONl}X+jJHR)r(CNDCDQ!xuvKpx~397{?{lAk~4MQ;82WbQ$g z7k(m$^c}A>{WPM-pLWeBGCCT9Bo9~4T$HHZ%V|}H@Z#yJAGGp zse~;$KX{N~N`iq|!oubpa?cG;Hf~Z6iXUEy8as|~@RWFv8m(>{B9KJG1aba_FtNSX-%bybw5xL%d zPu!TFpKrN(IQmem4pi~u<6{zTcV3LUv-AA&*EdtMv)U065e46dv!#)knl{29AT%kB z=9Vq_O|+r)SO8j5KeVacF7~5vvz-0?eHKp6k%0jO zDkw=;O!KIalT^z3>5Qrng^VbjfXc!p{lP)12_er4&Rc4Llj};t;u*!d0&Mv{42s$}E zE%ce6pWg+nc{p5yEv?bwUcVI9Ha1F~Ejz-`dye;3PeB@**x&c_3kXny(=GIJbzYjx zFH&ECes5(cGrG36mQwlRXpxNeiV!xkGcz^){rrYPcIG1^5pewk)I}D7ID+OS1JO%t qR+iEz9xrR4r&kwpu>ON^Nvczv{%+vMNyycI0Ho$)jcPUPsQ&@NK?H*U diff --git a/chrome/public/assets/images/sc-white-sm.png b/chrome/public/assets/images/sc-white-sm.png deleted file mode 100644 index 4c009dcabbfc2f8aded985f7490aad696cf1fdb4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 292 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 Y0AkG>lKd;wga7~l07*qoM6N<$f)k2Mw*UYD diff --git a/chrome/public/background.js b/chrome/public/background.js new file mode 100644 index 0000000..7dfbcb5 --- /dev/null +++ b/chrome/public/background.js @@ -0,0 +1,11 @@ +chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { + if (request.action === 'start-listen') { + chrome.tabCapture.capture({ + audio: true, + video: false + }, function (stream) { + console.log('stream', stream); + //I can attach all my filter here... + }); + } +}); \ No newline at end of file diff --git a/chrome/public/manifest.json b/chrome/public/manifest.json index 7eed707..2208354 100644 --- a/chrome/public/manifest.json +++ b/chrome/public/manifest.json @@ -3,14 +3,17 @@ "name": "Huegasm", "description": "Huegasm is a free web application for managing and synchronizing your Philips Hue lights with the beat of your music.", "version": "1.0", - "content_security_policy": "script-src https://connect.soundcloud.com 'self'; object-src 'self'", "icons": { "16": "16x16.png", "48": "48x48.png", "128": "128x128.png" }, "background": { - "page": "index.html" + "background": { + "scripts": [ + "background.js" + ] + }, }, "browser_action": { "default_icon": { @@ -22,7 +25,8 @@ }, "permissions": [ "activeTab", - "background", + "tabCapture", + "storage", "https://ajax.googleapis.com/" ] } \ No newline at end of file