diff --git a/.htaccess b/.htaccess
index 7352b65c..ccaa4ef3 100755
--- a/.htaccess
+++ b/.htaccess
@@ -1,5 +1,4 @@
-
RewriteEngine On
RewriteCond %{REQUEST_URI} !/production/ [NC]
-RewriteRule (.*) /production/$1 [NC,L]
\ No newline at end of file
+RewriteRule (.*) /production/$1 [NC,L]
diff --git a/src/js/actions/index.js b/src/js/actions/index.js
index fec22d6a..c632dc3a 100755
--- a/src/js/actions/index.js
+++ b/src/js/actions/index.js
@@ -1,4 +1,3 @@
import albumActions from './albumActions'
-import mopidy from './mopidy'
diff --git a/src/js/actions/mopidy.js b/src/js/actions/mopidy.js
deleted file mode 100755
index a4f49050..00000000
--- a/src/js/actions/mopidy.js
+++ /dev/null
@@ -1,68 +0,0 @@
-
-/**
- * Actions and Action Creators
- **/
-
-export function updateStatus( online ){
- return {
- type: 'STATUS',
- online: online
- }
-}
-
-export function updateState( state ){
- return {
- type: 'STATE',
- state: state
- }
-}
-
-export function updateConsume( consume ){
- return {
- type: 'CONSUME',
- consume: consume
- }
-}
-
-export function updateRandom( random ){
- return {
- type: 'RANDOM',
- random: random
- }
-}
-
-export function updateRepeat( repeat ){
- return {
- type: 'REPEAT',
- repeat: repeat
- }
-}
-
-export function updateTlTracks( tracks ){
- return {
- type: 'TRACKLIST',
- tracks: tracks
- }
-}
-
-export function updateCurrentTlTrack( tltrack ){
- return {
- type: 'TRACKINFOCUS',
- trackInFocus: tltrack
- }
-}
-
-export function updateVolume( volume ){
- return {
- type: 'VOLUME',
- volume: volume
- }
-}
-
-export function changeTrack( tlid ){
- return {
- type: 'CHANGE_TRACK',
- tlid: tlid
- }
-}
-
diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js
index f5553dcf..fa909f90 100755
--- a/src/js/bootstrap.js
+++ b/src/js/bootstrap.js
@@ -1,10 +1,16 @@
console.info('Bootstrapping...');
-import { createStore } from 'redux'
+import { createStore, applyMiddleware } from 'redux'
+import thunk from 'redux-thunk'
import reducer from './reducers/index'
+import mopidyMiddleware from './services/mopidy/middleware'
// create our global store
-let store = createStore( reducer, {} );
+let store = createStore(
+ reducer,
+ {},
+ applyMiddleware( thunk, mopidyMiddleware )
+);
export default store;
diff --git a/src/js/components/App.js b/src/js/components/App.js
index a57152d2..30817054 100755
--- a/src/js/components/App.js
+++ b/src/js/components/App.js
@@ -6,8 +6,7 @@ import { Link } from 'react-router'
import { connect } from 'react-redux'
import * as actions from '../actions/index'
-import Services from '../services/Services'
-import MopidyService from '../services/MopidyService'
+import * as mopidyActions from '../services/mopidy/actions'
/**
@@ -19,6 +18,10 @@ class App extends React.Component{
super(props);
}
+ componentDidMount(){
+ this.props.mopidyActions.connect();
+ }
+
render(){
return (
@@ -32,7 +35,6 @@ class App extends React.Component{
Authorize
{this.props.children}
-
);
}
@@ -50,7 +52,8 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = (dispatch) => {
return {
- actions: bindActionCreators(actions, dispatch)
+ actions: bindActionCreators(actions, dispatch),
+ mopidyActions: bindActionCreators(mopidyActions, dispatch)
}
}
diff --git a/src/js/components/Player.js b/src/js/components/Player.js
new file mode 100755
index 00000000..5d99a1b4
--- /dev/null
+++ b/src/js/components/Player.js
@@ -0,0 +1,76 @@
+
+import React, { PropTypes } from 'react'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+
+import FontAwesome from 'react-fontawesome'
+import * as actions from '../services/mopidy/actions'
+
+class Player extends React.Component{
+
+ constructor(props) {
+ super(props);
+ }
+
+ renderControls(){
+
+ var playButton = this.props.actions.instruct('playback.play')}>
+ if( this.props.mopidy.state == 'playing' ){
+ playButton = this.props.actions.instruct('playback.pause')}>
+ }
+
+ var consumeButton = this.props.actions.instruct('tracklist.setConsume', [true])}>Consume
+ if( this.props.mopidy.consume ){
+ consumeButton = this.props.actions.instruct('tracklist.setConsume', [false])}>Un-Consume
+ }
+
+ var randomButton = this.props.actions.instruct('tracklist.setRandom', [true])}>Random
+ if( this.props.mopidy.random ){
+ randomButton = this.props.actions.instruct('tracklist.setRandom', [false])}>Un-Random
+ }
+
+ var repeatButton = this.props.actions.instruct('tracklist.setRepeat', [true])}>Repeat
+ if( this.props.mopidy.repeat ){
+ repeatButton = this.props.actions.instruct('tracklist.setRepeat', [false])}>Un-Repeat
+ }
+
+ return (
+
+ );
+ }
+
+ render(){
+ return (
+
+ { this.renderControls() }
+
+ );
+ }
+}
+
+
+/**
+ * Export our component
+ *
+ * We also integrate our global store, using connect()
+ **/
+
+const mapStateToProps = (state, ownProps) => {
+ return state;
+}
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ actions: bindActionCreators(actions, dispatch)
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Player)
\ No newline at end of file
diff --git a/src/js/components/Queue.js b/src/js/components/Queue.js
index 30c6e21e..bd45dc31 100755
--- a/src/js/components/Queue.js
+++ b/src/js/components/Queue.js
@@ -3,10 +3,11 @@ import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
-import Services from '../services/Services'
+import FontAwesome from 'react-fontawesome'
import TrackList from '../components/TrackList'
import Track from '../components/Track'
-import * as actions from '../actions/mopidy'
+import Player from '../components/Player'
+import * as actions from '../services/mopidy/actions'
class Queue extends React.Component{
@@ -39,6 +40,7 @@ class Queue extends React.Component{
{ this.renderTrackInFocus() }
Other tracks
{ this.renderTrackList() }
+
);
}
diff --git a/src/js/components/TrackList.js b/src/js/components/TrackList.js
index 1c5767bf..e500de16 100755
--- a/src/js/components/TrackList.js
+++ b/src/js/components/TrackList.js
@@ -2,10 +2,9 @@
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
-import * as actions from '../actions/mopidy'
+import * as actions from '../services/mopidy/actions'
import Track from './Track'
-import MopidyService from '../services/MopidyService'
class TrackList extends React.Component{
diff --git a/src/js/reducers/index.js b/src/js/reducers/index.js
index 89e850a9..5e0f3653 100755
--- a/src/js/reducers/index.js
+++ b/src/js/reducers/index.js
@@ -7,7 +7,7 @@
// import all of our application reducers
import album from './album'
-import mopidy from './mopidy'
+import mopidy from '../services/mopidy/reducer'
import { combineReducers } from 'redux'
diff --git a/src/js/reducers/mopidy.js b/src/js/reducers/mopidy.js
deleted file mode 100755
index e74a3d82..00000000
--- a/src/js/reducers/mopidy.js
+++ /dev/null
@@ -1,57 +0,0 @@
-
-export default function reducer(mopidy = {}, action){
- switch (action.type) {
-
- case 'STATUS':
- return Object.assign({}, mopidy, {
- online: action.online
- });
-
- case 'STATE':
- return Object.assign({}, mopidy, {
- state: action.state
- });
-
- case 'CONSUME':
- return Object.assign({}, mopidy, {
- consume: action.consume
- });
-
- case 'RANDOM':
- return Object.assign({}, mopidy, {
- random: action.random
- });
-
- case 'REPEAT':
- return Object.assign({}, mopidy, {
- repeat: action.repeat
- });
-
- case 'TRACKLIST':
- return Object.assign({}, mopidy, {
- tracks: action.tracks
- });
-
- case 'TRACKINFOCUS':
- console.log( action );
- return Object.assign({}, mopidy, {
- trackInFocus: action.trackInFocus
- });
-
- case 'VOLUME':
- return Object.assign({}, mopidy, {
- volume: action.volume
- });
-
- case 'CHANGE_TRACK':
- return Object.assign({}, mopidy, {
- tlid: action.tlid
- });
-
- default:
- return mopidy
- }
-}
-
-
-
diff --git a/src/js/services/MopidyService.js b/src/js/services/MopidyService.js
index 6d0446dc..83663429 100755
--- a/src/js/services/MopidyService.js
+++ b/src/js/services/MopidyService.js
@@ -5,7 +5,7 @@ import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import FontAwesome from 'react-fontawesome'
-import * as actions from '../actions/mopidy'
+import * as actions from '../services/mopidy/actions'
/**
@@ -16,14 +16,21 @@ import * as actions from '../actions/mopidy'
class MopidyService extends React.Component{
constructor( props ){
- super(props)
-
+ super(props);
+/*
this.connection = new Mopidy({
- webSocketUrl: "ws://music.james:6680/mopidy/ws",
+ webSocketUrl: "ws://tv.barnsley.nz:6680/mopidy/ws",
+ //webSocketUrl: "ws://music.james:6680/mopidy/ws",
callingConvention: 'by-position-or-by-name'
});
this.connection.on( (type, data) => this.handleMessage( type, data ) );
+ */
+ }
+
+ componentDidMount(){
+
+ this.props.actions.connect();
}
handleMessage( type, data ){
diff --git a/src/js/services/mopidy/actions.js b/src/js/services/mopidy/actions.js
new file mode 100755
index 00000000..fd3972c2
--- /dev/null
+++ b/src/js/services/mopidy/actions.js
@@ -0,0 +1,25 @@
+
+/**
+ * Actions and Action Creators
+ **/
+
+export function connect(){
+ return {
+ type: 'CONNECT'
+ }
+}
+
+export function disconnect(){
+ return {
+ type: 'DISCONNECT'
+ }
+}
+
+export function instruct( call, value ){
+ return {
+ type: 'INSTRUCT',
+ call: call,
+ value: value
+ }
+}
+
diff --git a/src/js/services/mopidy/middleware.js b/src/js/services/mopidy/middleware.js
new file mode 100755
index 00000000..ba0ba0ab
--- /dev/null
+++ b/src/js/services/mopidy/middleware.js
@@ -0,0 +1,153 @@
+
+import Mopidy from 'mopidy'
+import actions from './actions'
+
+const MopidyMiddleware = (function(){
+
+ var socket = null;
+
+ const onOpen = (ws, store, token) => evt => {
+ console.log('opened')
+ //Send a handshake, or authenticate with remote end
+
+ //Tell the store we're connected
+ //store.dispatch(actions.connected());
+ }
+
+ const onClose = (ws, store) => evt => {
+ //Tell the store we've disconnected
+ //store.dispatch(actions.disconnected());
+ }
+
+ const handleMessage = (ws, store, type, data) => {
+
+ switch( type ){
+
+ case 'state:online':
+ //store.dispatch( actions.updateStatus( true ) );
+ instruct( ws, store, 'playback.getState' );
+ instruct( ws, store, 'playback.getVolume' );
+ instruct( ws, store, 'tracklist.getConsume' );
+ instruct( ws, store, 'tracklist.getRandom' );
+ instruct( ws, store, 'tracklist.getRepeat' );
+ instruct( ws, store, 'tracklist.getTlTracks' );
+ instruct( ws, store, 'playback.getCurrentTlTrack' );
+ break;
+
+ case 'state:offline':
+ store.dispatch( actions.updateStatus( false ) );
+ break;
+
+ case 'event:tracklistChanged':
+ instruct( ws, store, 'tracklist.getTlTracks' );
+ break;
+
+ //case 'event:trackPlaybackEnded':
+ case 'event:playbackStateChanged':
+ case 'event:trackPlaybackStarted':
+ instruct( ws, store, 'playback.getState' );
+ instruct( ws, store, 'playback.getCurrentTlTrack' );
+ break;
+
+ case 'event:volumeChanged':
+ store.dispatch( actions.updateVolume( data.volume ) );
+ break;
+
+ case 'event:optionsChanged':
+ instruct( ws, store, 'tracklist.getConsume' );
+ instruct( ws, store, 'tracklist.getRandom' );
+ instruct( ws, store, 'tracklist.getRepeat' );
+ break;
+
+ default:
+ //console.log( 'MopidyService: Unhandled event', type, message );
+ }
+ }
+
+
+ function doStuff( ws, store ){
+ console.log('doing stuff', store);
+ ws.playback.getState()
+ .then(
+ response => {
+ store.dispatch({ type: 'STATE', state: response })
+ },
+ error => {
+ console.error( error );
+ }
+ );
+ }
+
+
+ /**
+ * Get something from Mopidy
+ *
+ * Sends request to Mopidy server, and updates our local storage on return
+ * @param object ws Websocket
+ * @param string model Mopidy model (playback, tracklist, etc)
+ * @param string property the property to get (TlTracks, Consume, etc)
+ **/
+ const instruct = ( ws, store, call, value = false ) => {
+ console.log('Mopidy: '+call, value);
+ var callParts = call.split('.');
+ var model = callParts[0];
+ var method = callParts[1];
+ var property = method;
+ property = property.replace('get','');
+ property = property.replace('set','');
+
+ if( value ){
+ ws[model][method]( value )
+ .then(
+ response => store.dispatch({ type: property, model: model, data: response }),
+ error => console.error( error )
+ );
+ }else{
+ ws[model][method]()
+ .then(
+ response => store.dispatch({ type: property, model: model, data: response }),
+ error => console.error( error )
+ );
+ }
+ }
+
+ return store => next => action => {
+
+ switch(action.type) {
+
+ //The user wants us to connect
+ case 'CONNECT':
+
+ if(socket != null) socket.close();
+ store.dispatch({ type: 'CONNECTING' });
+
+ socket = new Mopidy({
+ webSocketUrl: "ws://tv.barnsley.nz:6680/mopidy/ws",
+ callingConvention: 'by-position-or-by-name'
+ });
+
+ socket.on( (type, data) => handleMessage( socket, store, type, data ) );
+
+ break;
+
+ //The user wants us to disconnect
+ case 'DISCONNECT':
+ if(socket != null) socket.close();
+ socket = null;
+ store.dispatch({ type: 'DISCONNECTED' });
+ break;
+
+ //Send the 'SEND_MESSAGE' action down the websocket to the server
+ case 'INSTRUCT':
+ instruct( socket, store, action.call, action.value )
+ break;
+
+ //This action is irrelevant to us, pass it on to the next middleware
+ default:
+ return next(action);
+ }
+ }
+
+})();
+
+export default MopidyMiddleware
\ No newline at end of file
diff --git a/src/js/services/mopidy/reducer.js b/src/js/services/mopidy/reducer.js
new file mode 100755
index 00000000..8a2cec12
--- /dev/null
+++ b/src/js/services/mopidy/reducer.js
@@ -0,0 +1,60 @@
+
+export default function reducer(mopidy = {}, action){
+ switch (action.type) {
+
+ case 'CONNECTING':
+ return Object.assign({}, mopidy, { connected: false, connecting: true });
+
+ case 'CONNECTED':
+ return Object.assign({}, mopidy, { connected: true, connecting: false });
+
+ case 'DISCONNECTED':
+ return Object.assign({}, mopidy, { connected: false, connecting: false });
+
+ case 'State':
+ return Object.assign({}, mopidy, {
+ state: action.data
+ });
+
+ case 'Consume':
+ return Object.assign({}, mopidy, {
+ consume: action.data
+ });
+
+ case 'Random':
+ return Object.assign({}, mopidy, {
+ random: action.data
+ });
+
+ case 'Repeat':
+ return Object.assign({}, mopidy, {
+ repeat: action.data
+ });
+
+ case 'TlTracks':
+ return Object.assign({}, mopidy, {
+ tracks: action.data
+ });
+
+ case 'CurrentTlTrack':
+ return Object.assign({}, mopidy, {
+ trackInFocus: action.data
+ });
+
+ case 'Volume':
+ return Object.assign({}, mopidy, {
+ volume: action.volume
+ });
+
+ case 'CHANGE_TRACK':
+ return Object.assign({}, mopidy, {
+ tlid: action.tlid
+ });
+
+ default:
+ return mopidy
+ }
+}
+
+
+