Fix some stuff, start work on remote
This commit is contained in:
parent
d3c7da844b
commit
e49ca45dbb
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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() {
|
||||||
|
@ -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
75
src/remote-control.js
Normal 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -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}`)
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user