Fix some stuff, start work on remote

This commit is contained in:
Evert Prants 2019-01-25 23:11:58 +02:00
parent d3c7da844b
commit e49ca45dbb
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
7 changed files with 154 additions and 15 deletions

View File

@ -19,10 +19,12 @@
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"socket.io": "^2.1.1", "socket.io": "^2.1.1",
"sqlite": "^3.0.0", "sqlite": "^3.0.0",
"sqlite3": "^4.0.3" "sqlite3": "^4.0.3",
"ws": "^6.1.3"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-env": "^2.4.1" "babel-env": "^2.4.1",
"morgan": "^1.9.1"
} }
} }

View File

@ -325,9 +325,19 @@ canvas#visualizer {
padding: 10px; padding: 10px;
display: flex; 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; flex-direction: row;
} }
.sidebar .option.action button {
flex-grow: 1;
margin: 10px;
}
.sidebar .option label { .sidebar .option label {
flex-grow: 1; flex-grow: 1;
} }

View File

@ -79,14 +79,17 @@
<div class="sidebar bar sb-abs"> <div class="sidebar bar sb-abs">
<h2>Options</h2> <h2>Options</h2>
<div class="sidebar content" id="options"> <div class="sidebar content" id="options">
<div class="separator">Playback</div>
<div class="option checkbox"> <div class="option checkbox">
<label for="st-autoplay">Automatically play next track</label> <label for="st-autoplay">Automatically play next track</label>
<input type="checkbox" id="st-autoplay" name="autoplay"> <input type="checkbox" id="st-autoplay" name="autoplay">
</div> </div>
<div class="separator">Search</div>
<div class="option checkbox"> <div class="option checkbox">
<label for="st-streamable">Include streamable tracks in searches (experimental)</label> <label for="st-streamable">Include streamable tracks in searches (experimental)</label>
<input type="checkbox" id="st-streamable" name="streamable"> <input type="checkbox" id="st-streamable" name="streamable">
</div> </div>
<div class="separator">Sorting</div>
<div class="option checkbox"> <div class="option checkbox">
<label for="st-trackids" title="Shows track number in album instead when unchecked">Show track IDs</label> <label for="st-trackids" title="Shows track number in album instead when unchecked">Show track IDs</label>
<input type="checkbox" id="st-trackids" name="trackids"> <input type="checkbox" id="st-trackids" name="trackids">
@ -109,6 +112,19 @@
<option value="desc">Descending order</option> <option value="desc">Descending order</option>
</select> </select>
</div> </div>
<div class="separator">Remote Control</div>
<div class="option checkbox">
<label for="st-remote">Allow remote control</label>
<input type="checkbox" id="st-remote" name="remote">
</div>
<div class="option">
<label for="st-device">Device name</label>
<input type="text" id="st-device" name="device">
</div>
<div class="option action">
<button id="dc">Take Remote Control</button>
<button id="dc-stop">Release Remote</button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -17,6 +17,7 @@
<th data-sort-by="year" class="small">Year</th> \ <th data-sort-by="year" class="small">Year</th> \
<th class="small">Duration</th> \ <th class="small">Duration</th> \
</tr>' </tr>'
var nowPlaying = 0 var nowPlaying = 0
var externalStream = false var externalStream = false
@ -45,6 +46,8 @@
streamable: true, streamable: true,
sortby: 'id', sortby: 'id',
sortdir: 'asc', sortdir: 'asc',
device: '',
remote: false
} }
window.mobilecheck = function() { window.mobilecheck = function() {

View File

@ -8,14 +8,17 @@ const values = require(path.join(process.cwd(), 'values.json'))
let externalTracks = {} let externalTracks = {}
function createHash (data) { 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) .substr(0, 8)
} }
async function getTrackMetaReal (id) { async function getTrackMetaReal (id) {
if (!id || !externalTracks[id]) return null if (!id || !externalTracks[id]) return null
let trdata = externalTracks[id] let trdata = externalTracks[id]
if (trdata.file) return trdata if (trdata.file) return Object.assign({}, trdata)
let trsrch = 'ytsearch1:' + trdata.artist + ' - ' + trdata.title let trsrch = 'ytsearch1:' + trdata.artist + ' - ' + trdata.title
let dldata = await dl.getVideoInfo(trsrch) let dldata = await dl.getVideoInfo(trsrch)
@ -29,31 +32,45 @@ async function getTrackMetaReal (id) {
external: true external: true
} }
return externalTracks[id] return Object.assign({}, externalTracks[id])
} }
async function search (track, limit = 30) { async function search (track, limit = 30) {
if (!values.lastfm) return [] if (!values.lastfm) return []
let data let data
try { 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 = 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) data = JSON.parse(data)
if (!data.results || !data.results.trackmatches || !data.results.trackmatches.track) {
throw new Error('No results')
}
} catch (e) { } catch (e) {
console.warn(e.stack)
return [] return []
} }
if (!data.results || !data.results.trackmatches || !data.results.trackmatches.track) return
let final = [] let final = []
for (let i in data.results.trackmatches.track) { for (let i in data.results.trackmatches.track) {
let res = data.results.trackmatches.track[i] let res = data.results.trackmatches.track[i]
try { let clean = {
let tr = { artist: res.artist, title: res.name, external: true, id: createHash(res) } id: createHash(res),
externalTracks[tr.id] = tr artist: res.artist,
final.push(tr) title: res.name,
} catch (e) { external: true
continue
} }
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 return final
} }

75
src/remote-control.js Normal file
View File

@ -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()
}
})
}

View File

@ -2,10 +2,12 @@ import path from 'path'
import sqlite from 'sqlite' import sqlite from 'sqlite'
import Promise from 'bluebird' import Promise from 'bluebird'
import express from 'express' import express from 'express'
import http from 'http'
import https from 'https' import https from 'https'
import ffmpeg from 'fluent-ffmpeg' import ffmpeg from 'fluent-ffmpeg'
import lastfm from './lastfm' import lastfm from './lastfm'
import remote from './remote-control'
require('express-async-errors') require('express-async-errors')
@ -19,6 +21,14 @@ const dbPromise = Promise.resolve()
const app = express() const app = express()
const port = process.env.PORT || 3000 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 router = express.Router()
const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year', 'file'] 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) let lfm = await lastfm.search(original, llimit)
if (lfm && lfm.length) { if (lfm && lfm.length) {
tracks = tracks.concat(lfm) tracks = tracks.concat(lfm)
count = count + lfm.length
if (page == 0) page = 1
if (pageCount == 0) pageCount = 1
} }
} catch (e) {} } 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' 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}`) console.log(`app running on port ${port}`)
}) })