node_modules |
var player = require("./player.js"); | |||||
exports.init = function(app) { | |||||
player.init(app); | |||||
} | |||||
exports.playFile = function(path, cb) { | |||||
player.play(path, cb); | |||||
} | |||||
exports.isPlaying = player.isPlaying; |
var spawn = require("child_process").spawn; | |||||
var fs = require("fs"); | |||||
var net = require("net"); | |||||
var Queue = require("../queue"); | |||||
var child = null; | |||||
var ipcServer = process.cwd()+"/mpv-ipc-socket"; | |||||
function cmd(params, cb) { | |||||
if (child == null || child.sock == null) | |||||
return; | |||||
child.sock.write(JSON.stringify({ | |||||
command: params | |||||
})+"\n"); | |||||
child.msgqueue.dequeue(obj => { | |||||
if (cb) | |||||
cb(obj); | |||||
}); | |||||
} | |||||
function getState(cb) { | |||||
if (child == null) { | |||||
cb({ | |||||
playing: false, | |||||
paused: false, | |||||
muted: false, | |||||
duration: 0, | |||||
time_pos: 0, | |||||
volume: 0, | |||||
volume_max: 0 | |||||
}); | |||||
return; | |||||
} | |||||
var state = { | |||||
playing: true | |||||
}; | |||||
var cbs = 6; | |||||
function next() { | |||||
cbs -= 1; | |||||
if (cbs === 0) | |||||
cb(state); | |||||
} | |||||
cmd(["get_property", "pause"], res => { | |||||
state.paused = res.data; | |||||
next(); | |||||
}); | |||||
cmd(["get_property", "mute"], res => { | |||||
state.muted = res.data; | |||||
next(); | |||||
}); | |||||
cmd(["get_property", "duration"], res => { | |||||
state.duration = res.data; | |||||
next(); | |||||
}); | |||||
cmd(["get_property", "time-pos"], res => { | |||||
state.time_pos = res.data; | |||||
next(); | |||||
}); | |||||
cmd(["get_property", "volume"], res => { | |||||
state.volume = res.data; | |||||
next(); | |||||
}); | |||||
cmd(["get_property", "volume-max"], res => { | |||||
state.volume_max = res.data; | |||||
next(); | |||||
}); | |||||
} | |||||
exports.isPlaying = function() { | |||||
return child != null; | |||||
} | |||||
exports.play = function(path, cb) { | |||||
exports.stop(); | |||||
var lchild = spawn("mpv", [ | |||||
path, | |||||
"--input-ipc-server", ipcServer | |||||
], { stdio: "inherit" }); | |||||
child = lchild; | |||||
lchild.running = true; | |||||
lchild.once("close", () => { | |||||
if (lchild.running) exports.stop(); | |||||
}); | |||||
lchild.on("error", err => console.error(err.toString())); | |||||
lchild.state = {}; | |||||
lchild.msgqueue = Queue(); | |||||
console.log("lchild is "+lchild); | |||||
lchild.initTimeout = setTimeout(() => { | |||||
console.log("lchild still is "+lchild); | |||||
// Create socket | |||||
lchild.sock = net.connect(ipcServer, () => { | |||||
// Add output from mpv to the queue | |||||
lchild.sock.on("data", d => { | |||||
d.toString().split("\n").forEach(str => { | |||||
if (str == "") return; | |||||
var obj = JSON.parse(str); | |||||
if (obj.event) return; | |||||
lchild.msgqueue.push(obj); | |||||
}); | |||||
}); | |||||
lchild.sock.on("error", err => console.trace(err)); | |||||
// Set fullscreen | |||||
cmd(["set_property", "fullscreen", "yes"]); | |||||
cb(); | |||||
}); | |||||
}, 1000); | |||||
} | |||||
exports.stop = function() { | |||||
if (child) { | |||||
child.running = false; | |||||
child.kill("SIGKILL"); | |||||
clearTimeout(child.initTimeout); | |||||
child = null; | |||||
} | |||||
try { | |||||
fs.unlinkSync(ipcServer); | |||||
} catch (err) {} | |||||
} | |||||
exports.init = function(app) { | |||||
function evt(p, cb) { | |||||
app.post("/playback/"+p, (req, res) => cb(req, res)); | |||||
} | |||||
evt("exit", (req, res) => { res.end(); exports.stop() }); | |||||
evt("state", (req, res) => { | |||||
getState((state) => { | |||||
res.json(state); | |||||
}); | |||||
}); | |||||
evt("set/:key/:val", (req, res) => { | |||||
cmd(["set_property", req.params.key, req.params.val]); | |||||
res.end(); | |||||
}); | |||||
} |
module.exports = function() { | |||||
var self = {}; | |||||
var cbs = []; | |||||
var arr = []; | |||||
self.push = function(val) { | |||||
if (cbs.length > 0) { | |||||
cbs.shift()(val); | |||||
} else { | |||||
arr.push(val); | |||||
} | |||||
} | |||||
self.dequeue = function(cb) { | |||||
if (arr.length > 0) { | |||||
cb(arr.shift()); | |||||
} else { | |||||
cbs.push(cb); | |||||
} | |||||
} | |||||
return self; | |||||
} |
{ | |||||
"name": "mmpc3", | |||||
"version": "1.0.0", | |||||
"description": "", | |||||
"main": "server.js", | |||||
"scripts": { | |||||
"test": "echo \"Error: no test specified\" && exit 1", | |||||
"start": "node server.js" | |||||
}, | |||||
"author": "Martin Dørum Nygaard <martid0311@gmail.com> (http://mort.coffee)", | |||||
"license": "ISC", | |||||
"dependencies": { | |||||
"webstuff": "^1.3.0" | |||||
} | |||||
} |
var web = require("webstuff"); | |||||
var play = require("./js/play"); | |||||
var app = web(); | |||||
play.init(app); | |||||
app.express.use((req, res, next) => { | |||||
if (req.url === "/" && play.isPlaying()) | |||||
res.redirect("/playback"); | |||||
else | |||||
next(); | |||||
}); | |||||
app.static("web"); | |||||
app.post("/plaything", (req, res) => { | |||||
play.playFile("/home/martin/Documents/Assassination Classroom - E01.mkv", () => { | |||||
res.redirect("/playback"); | |||||
}); | |||||
}); | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<title>Mort's Media PC</title> | |||||
</head> | |||||
<body> | |||||
<form method="post" action="/plaything"> | |||||
<button>Hello There!</button> | |||||
</form> | |||||
<script src="/webstuff.js"></script> | |||||
</body> | |||||
</html> |
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta charset="utf-8"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<title>Playback</title> | |||||
<link rel="stylesheet" href="style.css"> | |||||
</head> | |||||
<body> | |||||
<div id="group-info"> | |||||
<div id="is-playing">Not Playing</div> | |||||
<div id="progress-text"></div> | |||||
</div> | |||||
<div id="group-bar"> | |||||
<progress id="progress"></progress> | |||||
</div> | |||||
<div id="group-buttons"> | |||||
<button id="mute">Mute</button> | |||||
<button id="skip-back"><</button> | |||||
<button id="pause">||</button> | |||||
<button id="skip-forward">></button> | |||||
<button id="exit">Exit</button> | |||||
</div> | |||||
<div id="group-volume"> | |||||
Volume <span id="volume-text"></span><br> | |||||
<input id="volume" type="range"> | |||||
</div> | |||||
<script src="/webstuff.js"></script> | |||||
<script src="script.js"></script> | |||||
</body> | |||||
</html> |
function timeformat(sec) { | |||||
var d = new Date(null); | |||||
d.setSeconds(Math.floor(sec)) | |||||
return d.toISOString().substr(11, 8); | |||||
} | |||||
var elems = { | |||||
is_playing: $$("#is-playing"), | |||||
progress_text: $$("#progress-text"), | |||||
progress: $$("#progress"), | |||||
pause: $$("#pause"), | |||||
skip_back: $$("#skip-back"), | |||||
skip_forward: $$("#skip-forward"), | |||||
mute: $$("#mute"), | |||||
exit: $$("#exit"), | |||||
volume: $$("#volume"), | |||||
volume_text: $$("#volume-text"), | |||||
}; | |||||
var state = {}; | |||||
/* | |||||
* Update GUI stuff | |||||
*/ | |||||
function update(state) { | |||||
// Playing | |||||
if (state.playing) | |||||
elems.is_playing.innerHTML = "Playing"; | |||||
else | |||||
location.href = "/"; | |||||
// Progress text | |||||
elems.progress_text.innerHTML = | |||||
timeformat(state.time_pos)+"/"+ | |||||
timeformat(state.duration); | |||||
// Progress bar | |||||
elems.progress.value = state.time_pos; | |||||
elems.progress.max = state.duration; | |||||
// Buttons | |||||
elems.pause.className = state.paused ? "active" : ""; | |||||
elems.mute.className = state.muted ? "active" : ""; | |||||
// Volume | |||||
elems.volume.min = 0; | |||||
elems.volume.max = state.volume_max; | |||||
elems.volume.value = state.volume; | |||||
elems.volume.step = 5; | |||||
elems.volume_text.innerHTML = state.volume+"%"; | |||||
} | |||||
function checkState() { | |||||
post("/playback/state", null, function(err, res) { | |||||
if (err) | |||||
return; | |||||
state = JSON.parse(res); | |||||
update(state); | |||||
}); | |||||
} | |||||
checkState(); | |||||
setInterval(checkState, 500); | |||||
/* | |||||
* React to input | |||||
*/ | |||||
function playerset(key, val) { | |||||
post("/playback/set/"+key+"/"+val, null, function() { | |||||
checkState(); | |||||
}); | |||||
} | |||||
// Set time | |||||
elems.progress.addEventListener("click", function(evt) { | |||||
var pos = (evt.clientX - evt.target.offsetLeft) / evt.target.clientWidth; | |||||
pos *= evt.target.max; | |||||
playerset("time-pos", pos); | |||||
}); | |||||
// Toggle pause | |||||
elems.pause.addEventListener("click", function() { | |||||
if (state.paused) | |||||
playerset("pause", "no"); | |||||
else | |||||
playerset("pause", "yes"); | |||||
}); | |||||
// Back 15 seconds | |||||
elems.skip_back.addEventListener("click", function() { | |||||
playerset("time-pos", state.time_pos - 15); | |||||
}); | |||||
// Forwards 15 seconds | |||||
elems.skip_forward.addEventListener("click", function() { | |||||
playerset("time-pos", state.time_pos + 15); | |||||
}); | |||||
// Toggle mute | |||||
elems.mute.addEventListener("click", function(evt) { | |||||
if (state.muted) | |||||
playerset("mute", "no"); | |||||
else | |||||
playerset("mute", "yes"); | |||||
}); | |||||
// Exit | |||||
elems.exit.addEventListener("click", function(evt) { | |||||
post("/playback/exit", null, function() {}); | |||||
}); | |||||
// Set volume | |||||
elems.volume.addEventListener("change", function(evt) { | |||||
playerset("volume", evt.target.value); | |||||
}); |
* { | |||||
box-sizing: border-box; | |||||
} | |||||
body { | |||||
margin: 12px; | |||||
} | |||||
body > div { | |||||
margin-bottom: 12px; | |||||
} | |||||
button { | |||||
display: inline-block; | |||||
padding: 0px; | |||||
border: 1px solid #666; | |||||
width: 50px; | |||||
height: 32px; | |||||
background: #ddd; | |||||
margin-left: 2px; | |||||
margin-right: 2px; | |||||
} | |||||
button:active, | |||||
button.active { | |||||
background: #aaa; | |||||
} | |||||
progress { | |||||
width: 100%; | |||||
height: 24px; | |||||
} | |||||
input[type="range"] { | |||||
border: 1px solid rgba(0, 0, 0, 0); | |||||
box-sizing: border-box; | |||||
margin: 0px; | |||||
width: 100%; | |||||
} | |||||
#is-playing, | |||||
#progress-text { | |||||
display: inline-block; | |||||
} | |||||
#progress-text { | |||||
float: right; | |||||
} | |||||
#group-buttons { | |||||
text-align: center; | |||||
} |