| 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; | |||||
| } |