node_modules | node_modules | ||||
conf.json |
{ | |||||
"tmpdir": "tmp" | |||||
} |
var mime = require("mime"); | |||||
/* | |||||
* Must be set to a function which takes an options argument with | |||||
* 'start' and 'end', and returns a readStream. | |||||
* The readStream must have the additional properties | |||||
* 'filesize' and 'filename' set. | |||||
*/ | |||||
exports.readStreamCreator = null; | |||||
exports.httpPath = "/http-stream"; | |||||
exports.init = function(app) { | |||||
app.get("/http-stream", (req, res) => { | |||||
if (exports.readStreamCreator == null) { | |||||
res.writeHead(500); | |||||
throw "readStreamCreator not set!"; | |||||
} | |||||
var rs; | |||||
var parts = []; | |||||
if (req.headers.range) { | |||||
parts = req.headers.range.replace("bytes=", "").split("-"); | |||||
var start = parseInt(parts[0]); | |||||
var end = parseInt(parts[1]); | |||||
var options = {}; | |||||
if (!isNaN(start)) options.start = start; | |||||
if (!isNaN(end)) options.end = end; | |||||
rs = exports.readStreamCreator(options); | |||||
} else { | |||||
rs = exports.readStreamCreator(); | |||||
} | |||||
var start = parseInt(parts[0]) || 0; | |||||
var end = parts[1] ? parseInt(parts[1]) : rs.filesize - 1; | |||||
var chunksize = end - start + 1; | |||||
if (chunksize > rs.filesize || start > end || end >= rs.filesize) { | |||||
res.writeHead(416); | |||||
res.end("Range not satisfiable. Start: "+start+", end: "+end); | |||||
return; | |||||
} | |||||
res.writeHead(req.headers.range ? 206 : 200, { | |||||
"icy-name": rs.filename, | |||||
"content-length": chunksize, | |||||
"content-type": mime.lookup(rs.filename), | |||||
"content-range": "bytes "+start+"-"+end+"/"+rs.filesize, | |||||
"accept-ranges": "bytes" | |||||
}); | |||||
if (req.method === "HEAD") | |||||
return res.end(); | |||||
rs.pipe(res); | |||||
}); | |||||
} | |||||
exports.stop = function() { | |||||
exports.readStreamCreator = null; | |||||
} |
var player = require("./player.js"); | |||||
var player = require("./player"); | |||||
var httpStream = require("./http-stream"); | |||||
var torrentStreamer = require("./torrent-streamer"); | |||||
exports.init = function(app) { | |||||
player.init(app); | |||||
exports.httpPath = player.httpPath; | |||||
var app; | |||||
exports.init = function(_app, conf) { | |||||
app = _app; | |||||
player.init(app, conf); | |||||
httpStream.init(app, conf); | |||||
torrentStreamer.init(app, conf); | |||||
} | } | ||||
exports.playFile = function(path, cb) { | exports.playFile = function(path, cb) { | ||||
player.play(path, cb); | player.play(path, cb); | ||||
} | } | ||||
exports.playTorrent = function(magnet, cb) { | |||||
torrentStreamer.stream(magnet, err => { | |||||
if (err) | |||||
return cb(err); | |||||
player.play(app.getAddress()+httpStream.httpPath, cb); | |||||
}); | |||||
} | |||||
exports.isPlaying = player.isPlaying; | exports.isPlaying = player.isPlaying; | ||||
player.onstop = function() { | |||||
torrentStreamer.stop(); | |||||
httpStream.stop(); | |||||
} |
var net = require("net"); | var net = require("net"); | ||||
var Queue = require("../queue"); | var Queue = require("../queue"); | ||||
exports.httpPath = "/playback"; | |||||
var child = null; | var child = null; | ||||
var ipcServer = process.cwd()+"/mpv-ipc-socket"; | var ipcServer = process.cwd()+"/mpv-ipc-socket"; | ||||
var lchild = spawn("mpv", [ | var lchild = spawn("mpv", [ | ||||
path, | path, | ||||
"--input-ipc-server", ipcServer | |||||
"--no-cache-pause", | |||||
"--input-unix-socket", ipcServer | |||||
], { stdio: "inherit" }); | ], { stdio: "inherit" }); | ||||
child = lchild; | child = lchild; | ||||
lchild.running = true; | lchild.running = true; | ||||
lchild.once("close", () => { | lchild.once("close", () => { | ||||
console.log("child closed"); | |||||
if (lchild.running) exports.stop(); | if (lchild.running) exports.stop(); | ||||
}); | }); | ||||
lchild.on("error", err => console.error(err.toString())); | lchild.on("error", err => console.error(err.toString())); | ||||
lchild.state = {}; | lchild.state = {}; | ||||
lchild.msgqueue = Queue(); | lchild.msgqueue = Queue(); | ||||
console.log("lchild is "+lchild); | |||||
lchild.initTimeout = setTimeout(() => { | lchild.initTimeout = setTimeout(() => { | ||||
console.log("lchild still is "+lchild); | |||||
// Create socket | // Create socket | ||||
lchild.sock = net.connect(ipcServer, () => { | lchild.sock = net.connect(ipcServer, () => { | ||||
lchild.sock.on("error", err => console.trace(err)); | lchild.sock.on("error", err => console.trace(err)); | ||||
// Set fullscreen | |||||
cmd(["set_property", "fullscreen", "yes"]); | cmd(["set_property", "fullscreen", "yes"]); | ||||
cb(); | cb(); | ||||
child.kill("SIGKILL"); | child.kill("SIGKILL"); | ||||
clearTimeout(child.initTimeout); | clearTimeout(child.initTimeout); | ||||
child = null; | child = null; | ||||
exports.onstop(); | |||||
} | } | ||||
try { | try { | ||||
fs.unlinkSync(ipcServer); | fs.unlinkSync(ipcServer); | ||||
exports.init = function(app) { | exports.init = function(app) { | ||||
function evt(p, cb) { | function evt(p, cb) { | ||||
app.post("/playback/"+p, (req, res) => cb(req, res)); | |||||
app.post(exports.httpPath+"/"+p, (req, res) => cb(req, res)); | |||||
} | } | ||||
evt("exit", (req, res) => { res.end(); exports.stop() }); | evt("exit", (req, res) => { res.end(); exports.stop() }); |
var httpStream = require("./http-stream"); | |||||
var torrentStream = require("torrent-stream"); | |||||
var mediarx = /\.(mp4|mkv|mov|avi|ogv)$/; | |||||
var tmpdir = process.cwd()+"/tmp"; | |||||
var engine; | |||||
var conf; | |||||
exports.init = function(app, _conf) { | |||||
conf = _conf; | |||||
} | |||||
exports.stream = function(magnet, cb) { | |||||
if (engine) | |||||
return engine.destroy(() => | |||||
{ engine = null; exports.stream(magnet, cb) }); | |||||
engine = torrentStream(magnet, { | |||||
tmp: conf.tmpdir | |||||
}); | |||||
engine.on("ready", () => { | |||||
var file = null; | |||||
engine.files.forEach(f => { | |||||
if (mediarx.test(f.name) && | |||||
(file == null || f.length > file.length)) { | |||||
file = f; | |||||
} | |||||
}); | |||||
if (file == null) | |||||
return cb("No media file in the torrent"); | |||||
file.select(); | |||||
httpStream.readStreamCreator = function(options) { | |||||
console.log("creating stream with", options); | |||||
var rs = file.createReadStream(options); | |||||
rs.filesize = file.length; | |||||
rs.filename = file.name; | |||||
rs.on("close", () => console.log("stream closing")); | |||||
return rs; | |||||
} | |||||
cb(); | |||||
}); | |||||
} | |||||
exports.stop = function() { | |||||
if (engine != null) | |||||
engine.destroy(); | |||||
} |
"author": "Martin Dørum Nygaard <martid0311@gmail.com> (http://mort.coffee)", | "author": "Martin Dørum Nygaard <martid0311@gmail.com> (http://mort.coffee)", | ||||
"license": "ISC", | "license": "ISC", | ||||
"dependencies": { | "dependencies": { | ||||
"webstuff": "^1.3.0" | |||||
"mime": "^1.3.4", | |||||
"torrent-stream": "^1.0.3", | |||||
"webstuff": "^1.4.0" | |||||
} | } | ||||
} | } |
var fs = require("fs"); | |||||
var web = require("webstuff"); | var web = require("webstuff"); | ||||
var play = require("./js/play"); | var play = require("./js/play"); | ||||
var magnet = "magnet:?xt=urn:btih:13241fe16a2797b2a41b7822bde970274d6b687c&dn=Mad+Max%3A+Fury+Road+%282015%29+1080p+BrRip+x264+-+YIFY&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Fzer0day.ch%3A1337&tr=udp%3A%2F%2Fopen.demonii.com%3A1337&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Fexodus.desync.com%3A6969"; | |||||
var conf = JSON.parse(fs.readFileSync("conf.json")); | |||||
var app = web(); | var app = web(); | ||||
play.init(app); | |||||
play.init(app, conf); | |||||
app.express.use((req, res, next) => { | app.express.use((req, res, next) => { | ||||
if (req.url === "/" && play.isPlaying()) | if (req.url === "/" && play.isPlaying()) | ||||
res.redirect("/playback"); | |||||
res.redirect(play.httpPath); | |||||
else | else | ||||
next(); | next(); | ||||
}); | }); | ||||
app.static("web"); | app.static("web"); | ||||
app.post("/plaything", (req, res) => { | |||||
play.playFile("/home/martin/Documents/Assassination Classroom - E01.mkv", () => { | |||||
res.redirect("/playback"); | |||||
app.post("/play/link", (req, res) => { | |||||
req.parseBody((err, fields) => { | |||||
if (!fields.url) | |||||
return res.redirect("/"); | |||||
play.playFile(fields.url, () => { | |||||
res.redirect(play.httpPath); | |||||
}); | |||||
}); | }); | ||||
}); | }); | ||||
app.post("/play/magnet", (req, res) => { | |||||
req.parseBody((err, fields) => { | |||||
if (!fields.magnet) | |||||
return res.redirect("/"); | |||||
play.playTorrent(fields.magnet, () => { | |||||
res.redirect(play.httpPath); | |||||
}); | |||||
}); | |||||
}); |
<head> | <head> | ||||
<meta charset="utf-8"> | <meta charset="utf-8"> | ||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
<link rel="stylesheet" href="style.css"> | |||||
<title>Mort's Media PC</title> | <title>Mort's Media PC</title> | ||||
</head> | </head> | ||||
<body> | <body> | ||||
<form method="post" action="/plaything"> | |||||
<button>Hello There!</button> | |||||
</form> | |||||
<div id="parts"> | |||||
<form class="part" method="post" action="/play/link"> | |||||
<div class="name">URL:</div> | |||||
<input type="url" name="url" autocomplete="off"> | |||||
<button>Play</button> | |||||
</form> | |||||
<form class="part" method="post" action="/play/magnet"> | |||||
<div class="name">Magnet Link:</div> | |||||
<input type="url" name="magnet" autocomplete="off"> | |||||
<button>Play</button> | |||||
</form> | |||||
</div> | |||||
<script src="/webstuff.js"></script> | <script src="/webstuff.js"></script> | ||||
</body> | </body> | ||||
</html> | </html> |
elems.volume.addEventListener("change", function(evt) { | elems.volume.addEventListener("change", function(evt) { | ||||
playerset("volume", evt.target.value); | playerset("volume", evt.target.value); | ||||
}); | }); | ||||
elems.volume.addEventListener("keydown", function(evt) { | |||||
if (evt.keyCode === 37 || evt.keyCode === 40) | |||||
playerset("volume", parseInt(evt.target.value) - parseInt(evt.target.step)); | |||||
else if (evt.keyCode === 38 || evt.keyCode === 39) | |||||
playerset("volume", parseInt(evt.target.value) + parseInt(evt.target.step)); | |||||
}); | |||||
window.addEventListener("keydown", function(evt) { | |||||
console.log(evt.keyCode); | |||||
}); |
* { | |||||
box-sizing: border-box; | |||||
} | |||||
#parts { | |||||
max-width: 600px; | |||||
margin: auto; | |||||
} | |||||
#parts .part { | |||||
border-bottom: 1px solid #eee; | |||||
padding: 20px 10px; | |||||
padding-top: 17px; | |||||
} | |||||
#parts .part:last-child { | |||||
border-bottom: none; | |||||
} | |||||
#parts .part .name { | |||||
margin-bottom: 6px; | |||||
} | |||||
#parts .part input { | |||||
width: calc(100% - 80px); | |||||
} | |||||
#parts .part button { | |||||
width: 75px; | |||||
float: right; | |||||
} | |||||
#parts .part input, | |||||
#parts .part button { | |||||
height: 25px; | |||||
} |