diff --git a/package.json b/package.json
index 0b7f9f6..5aa0dc4 100644
--- a/package.json
+++ b/package.json
@@ -19,10 +19,12 @@
"fs-extra": "^7.0.0",
"socket.io": "^2.1.1",
"sqlite": "^3.0.0",
- "sqlite3": "^4.0.3"
+ "sqlite3": "^4.0.3",
+ "ws": "^6.1.3"
},
"devDependencies": {
"babel-core": "^6.26.3",
- "babel-env": "^2.4.1"
+ "babel-env": "^2.4.1",
+ "morgan": "^1.9.1"
}
}
diff --git a/public/index.css b/public/index.css
index ba293e8..9b6198b 100644
--- a/public/index.css
+++ b/public/index.css
@@ -325,9 +325,19 @@ canvas#visualizer {
padding: 10px;
display: flex;
}
-.sidebar .option.checkbox {
+.sidebar .separator {
+ font-weight: bold;
+ padding: 10px;
+ border-bottom: 2px solid #004561;
+ font-size: 1.2em;
+}
+.sidebar .option.checkbox, .sidebar .option.action {
flex-direction: row;
}
+.sidebar .option.action button {
+ flex-grow: 1;
+ margin: 10px;
+}
.sidebar .option label {
flex-grow: 1;
}
diff --git a/public/index.html b/public/index.html
index a740724..d82d14b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -79,14 +79,17 @@
diff --git a/public/index.js b/public/index.js
index 241602c..e7d969b 100644
--- a/public/index.js
+++ b/public/index.js
@@ -17,6 +17,7 @@
Year | \
Duration | \
'
+
var nowPlaying = 0
var externalStream = false
@@ -45,6 +46,8 @@
streamable: true,
sortby: 'id',
sortdir: 'asc',
+ device: '',
+ remote: false
}
window.mobilecheck = function() {
diff --git a/src/lastfm.js b/src/lastfm.js
index 2d936e5..e637e4b 100644
--- a/src/lastfm.js
+++ b/src/lastfm.js
@@ -8,14 +8,17 @@ const values = require(path.join(process.cwd(), 'values.json'))
let externalTracks = {}
function createHash (data) {
- return crypto.createHash('sha1').update(data.artist + data.name).digest('hex')
+ return crypto
+ .createHash('sha1')
+ .update(data.artist + data.name)
+ .digest('hex')
.substr(0, 8)
}
async function getTrackMetaReal (id) {
if (!id || !externalTracks[id]) return null
let trdata = externalTracks[id]
- if (trdata.file) return trdata
+ if (trdata.file) return Object.assign({}, trdata)
let trsrch = 'ytsearch1:' + trdata.artist + ' - ' + trdata.title
let dldata = await dl.getVideoInfo(trsrch)
@@ -29,31 +32,45 @@ async function getTrackMetaReal (id) {
external: true
}
- return externalTracks[id]
+ return Object.assign({}, externalTracks[id])
}
async function search (track, limit = 30) {
if (!values.lastfm) return []
+
let data
try {
data = await as.GET(`http://ws.audioscrobbler.com/2.0/?method=track.search&track=${track}&api_key=${values.lastfm}&format=json&limit=${limit}`)
data = JSON.parse(data)
+
+ if (!data.results || !data.results.trackmatches || !data.results.trackmatches.track) {
+ throw new Error('No results')
+ }
} catch (e) {
- console.warn(e.stack)
return []
}
- if (!data.results || !data.results.trackmatches || !data.results.trackmatches.track) return
+
let final = []
for (let i in data.results.trackmatches.track) {
let res = data.results.trackmatches.track[i]
- try {
- let tr = { artist: res.artist, title: res.name, external: true, id: createHash(res) }
- externalTracks[tr.id] = tr
- final.push(tr)
- } catch (e) {
- continue
+ let clean = {
+ id: createHash(res),
+ artist: res.artist,
+ title: res.name,
+ external: true
}
+
+ if (externalTracks[clean.id]) {
+ // Copy object
+ clean = Object.assign({}, externalTracks[clean.id])
+ } else {
+ // Save in cache
+ externalTracks[clean.id] = clean
+ }
+
+ final.push(clean)
}
+
return final
}
diff --git a/src/remote-control.js b/src/remote-control.js
new file mode 100644
index 0000000..31494d4
--- /dev/null
+++ b/src/remote-control.js
@@ -0,0 +1,75 @@
+import WebSocket from 'ws'
+
+const wss = new WebSocket.Server({ noServer: true })
+
+function getClientByDevice (dev) {
+ for (let i in wss.clients) {
+ let client = wss.clients[i]
+ if (client.device === dev) return client
+ }
+ return null
+}
+
+wss.on('connection', function (ws) {
+ ws.on('device', function (devid) {
+ ws.device = devid
+ })
+
+ ws.on('connect', function (devid) {
+ let target = getClientByDevice(devid)
+ if (!target) return ws.emit('errno', { code: 100, error: 'Target device is not available.' })
+ target.emit('request', ws.device)
+ })
+
+ ws.on('detach', function () {
+ if (!ws.controlled) return
+ ws.controlled = null
+ let target = getClientByDevice(devid)
+ if (!target) return
+ target.controller = null
+ target.emit('detach')
+ })
+
+ ws.on('accept', function (devid) {
+ ws.controller = devid
+ let target = getClientByDevice(devid)
+ if (!target) return ws.emit('errno', { code: 100, error: 'Target device is not available.' })
+ target.controlled = devid
+ target.emit('accepted', devid)
+ })
+
+ ws.on('play', function (id) {
+ if (!ws.controlled) return
+ let target = getClientByDevice(ws.controlled)
+ if (!target) return ws.emit('errno', { code: 100, error: 'Target device is not available.' })
+ target.emit('play', id)
+ })
+
+ ws.on('seekto', function (num) {
+ if (!ws.controlled) return
+ let target = getClientByDevice(ws.controlled)
+ if (!target) return ws.emit('errno', { code: 100, error: 'Target device is not available.' })
+ target.emit('seekto', num)
+ })
+
+ ws.on('volumeto', function (num) {
+ if (!ws.controlled) return
+ let target = getClientByDevice(ws.controlled)
+ if (!target) return ws.emit('errno', { code: 100, error: 'Target device is not available.' })
+ target.emit('volumeto', num)
+ })
+})
+
+module.exports = function (server) {
+ server.on('upgrade', function (request, socket, head) {
+ const pathname = url.parse(request.url).pathname
+
+ if (pathname === '/remote') {
+ wss.handleUpgrade(request, socket, head, function (ws) {
+ wss.emit('connection', ws, request)
+ })
+ } else {
+ socket.destroy()
+ }
+ })
+}
diff --git a/src/server.js b/src/server.js
index a8d2ef6..2c61bd5 100644
--- a/src/server.js
+++ b/src/server.js
@@ -2,10 +2,12 @@ import path from 'path'
import sqlite from 'sqlite'
import Promise from 'bluebird'
import express from 'express'
+import http from 'http'
import https from 'https'
import ffmpeg from 'fluent-ffmpeg'
import lastfm from './lastfm'
+import remote from './remote-control'
require('express-async-errors')
@@ -19,6 +21,14 @@ const dbPromise = Promise.resolve()
const app = express()
const port = process.env.PORT || 3000
+const dev = process.env.NODE_ENV === 'development'
+const server = http.createServer(app)
+
+if (dev) {
+ const morgan = require('morgan')
+ app.use(morgan('dev'))
+}
+
const router = express.Router()
const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year', 'file']
@@ -123,6 +133,9 @@ router.get('/tracks/search', async (req, res) => {
let lfm = await lastfm.search(original, llimit)
if (lfm && lfm.length) {
tracks = tracks.concat(lfm)
+ count = count + lfm.length
+ if (page == 0) page = 1
+ if (pageCount == 0) pageCount = 1
}
} catch (e) {}
}
@@ -205,6 +218,9 @@ app.use('/', express.static(path.join(process.cwd(), 'public')))
const host = process.env.NODE_ENV === 'development' ? '0.0.0.0' : '127.0.0.1'
-app.listen(port, host, function () {
+// Initialize remote control
+remote(server)
+
+server.listen(port, host, function () {
console.log(`app running on port ${port}`)
})