@@ -0,0 +1 @@ | |||
node_modules |
@@ -0,0 +1,11 @@ | |||
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; |
@@ -0,0 +1,158 @@ | |||
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(); | |||
}); | |||
} |
@@ -0,0 +1,24 @@ | |||
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; | |||
} |
@@ -0,0 +1,15 @@ | |||
{ | |||
"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" | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
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"); | |||
}); | |||
}); | |||
@@ -0,0 +1,14 @@ | |||
<!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> |
@@ -0,0 +1,35 @@ | |||
<!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> |
@@ -0,0 +1,122 @@ | |||
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); | |||
}); |
@@ -0,0 +1,51 @@ | |||
* { | |||
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; | |||
} |