Browse Source

lots of work

master
mortie 7 years ago
parent
commit
e2b477c31a
11 changed files with 240 additions and 17 deletions
  1. 1
    0
      .gitignore
  2. 3
    0
      conf.json.example
  3. 61
    0
      js/play/http-stream.js
  4. 26
    3
      js/play/index.js
  5. 8
    5
      js/play/player.js
  6. 56
    0
      js/play/torrent-streamer.js
  7. 3
    1
      package.json
  8. 25
    5
      server.js
  9. 15
    3
      web/index.html
  10. 10
    0
      web/playback/script.js
  11. 32
    0
      web/style.css

+ 1
- 0
.gitignore View File

node_modules node_modules
conf.json

+ 3
- 0
conf.json.example View File

{
"tmpdir": "tmp"
}

+ 61
- 0
js/play/http-stream.js View File

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

+ 26
- 3
js/play/index.js View File

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

+ 8
- 5
js/play/player.js View File

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

+ 56
- 0
js/play/torrent-streamer.js View File

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

+ 3
- 1
package.json View File

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

+ 25
- 5
server.js View File

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

+ 15
- 3
web/index.html View File

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

+ 10
- 0
web/playback/script.js View File

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

+ 32
- 0
web/style.css View File

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

Loading…
Cancel
Save