Browse Source

modified version to stream to web page

master
mortie 7 years ago
parent
commit
daf30e4f46
10 changed files with 19 additions and 485 deletions
  1. 1
    52
      README.md
  2. 1
    3
      conf.json.example
  3. 0
    9
      js/notify.js
  4. 3
    51
      js/play/index.js
  5. 0
    54
      js/play/subtitle-finder.js
  6. 8
    28
      server.js
  7. 0
    6
      web/index.html
  8. 4
    33
      web/playback/index.html
  9. 0
    165
      web/playback/script.js
  10. 2
    84
      web/playback/style.css

+ 1
- 52
README.md View File

@@ -1,20 +1,6 @@
# MMPC2

The second version of Mort's Media PC.

## Goal

MMPC2 is meant to be running constantly on a HTPC, letting you stream media to
a TV and control the playback remotely, all through a web interface. It allows
you to stream three kinds of movies:

* URLs - YouTube, Vimeo, Dailymotion, plain HTTP streams, anything supported by
mpv and youtube-dl.
* Magnet Links - Stream any torrent.
* Files - Upload files and have them play.

For magnet links and files, subtitles will automatically be downloaded if you
want and subtitles exist for it in OpenSubtitles.
Modified version of mmpc2.

## Installation

@@ -41,34 +27,6 @@ that.
With rolling release distros, the version of node in the package manager will
generally be new enough - e.g `sudo pacman -S npm node` in arch is enough.

### Getting a recent version of mpv

MMPC2 requires a relatively new version of mpv, newer than what's currently in
Debian and Ubuntu. For Ubuntu, you could just add this ppa to your system:
https://launchpad.net/~mc3man/+archive/Ubuntu/mpv-tests

sudo add-apt-repository ppa:mc3man/mpv-tests
sudo apt-get update
sudo apt-get install mpv

For Debian, you will probably have to either use the testing repositories to
install mpv, get some binary from somewhere, or compile it from source. I
compiled from source when installing it on the Debian stable (jessie), and the
instructions are a bit too long to include here, but you can get the source
from here: https://github.com/mpv-player/mpv/releases/latest and get the source
there. Make sure to compile with luajit support for youtube-dl, and libpulse
for pulseaudio (if you use that).

### Getting a recent version of youtbe-dl

Debian may ship a version of youtube-dl that's so old it doesn't really work
anymore - I had that problem with my Debian box. To fix that, uninstall
youtube-dl if you installed with apt-get (`apt-get remove youtube-dl`), and
install it through pip (installing pip if necessary)

sudo apt-get install python-pip
sudo pip install youtube-dl

## Configuration

`conf.json` contains a couple of configuration options (assuming you copied
@@ -77,13 +35,4 @@ install it through pip (installing pip if necessary)
{
"tmpdir": String. The directory to store temporary files in.
Default: "tmp"

"subtitles": String (or false). The language code for the subtitles,
or false for no subtitles. Default: "en" (english).

"additional_links": Array of objects which look like this:
{ "name": "some name", "url": "some URL" }
Lets you add more parts to the index page. I use it to link to an
instance of guacamole, a web based VNC client.
( https://guacamole.incubator.apache.org/ )
}

+ 1
- 3
conf.json.example View File

@@ -1,5 +1,3 @@
{
"tmpdir": "tmp",
"subtitles": "en",
"additional_links": []
"tmpdir": "tmp"
}

+ 0
- 9
js/notify.js View File

@@ -1,9 +0,0 @@
var spawn = require("child_process").spawn;

module.exports = function(name, msg) {
var args = ["--urgency", "low"];
args.push(name);
if (msg) args.push(msg);

spawn("notify-send", args);
}

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

@@ -4,13 +4,11 @@ var https = require("https");
var pathlib = require("path");
var urllib = require("url");
var fsutil = require("../fsutil");
var notify = require("../notify");
var player = require("./player");
var httpStream = require("./http-stream");
var torrentStreamer = require("./torrent-streamer");
var subtitleFinder = require("./subtitle-finder");

exports.httpPath = player.httpPath;
exports.httpPath = "/playback";

exports.cleanupFiles = [];

@@ -23,58 +21,16 @@ exports.init = function(_app, _conf) {
player.init(app, conf);
httpStream.init(app, conf);
torrentStreamer.init(app, conf);
subtitleFinder.init(app, conf);
}

/*
* Filename is optional; in case you want to provide a filename for subtitles
* but want a different path
*/
exports.playFile = function(path, cb, filename) {
filename = filename || pathlib.basename(path);

notify("Playing file", filename);

// Find file's length
fs.stat(path, (err, stat) => {
if (err) {
console.trace(err);
return cb();
}

// Find subtitles
subtitleFinder.find(stat.size, filename, subFile => {

// Play!
player.play(path, subFile, cb);
});
});
}

exports.playUrl = function(url, cb) {

notify("Playing url...", url);

// Just play, we won't bother finding subtitles
player.play(url, null, cb);
}

exports.playTorrent = function(magnet, cb) {

notify("Playing torrent...");

// Stream torrent
torrentStreamer.stream(magnet, (err, filesize, filename) => {
if (err)
return cb(err);

// Find subtitles
subtitleFinder.find(filesize, filename, subFile => {

// Play!
notify("Starting playback.", filename);
player.play(app.getAddress()+httpStream.httpPath, subFile, cb);
});
cb();
});
}

@@ -88,8 +44,6 @@ exports.playTorrentPage = function(url, cb) {
return match[1];
}

notify("Finding magnet on torrent page...", url);

var urlobj = urllib.parse(url);
var o = urlobj.protocol === "https:" ? https : http;
o.request(urlobj, res => {
@@ -98,15 +52,13 @@ exports.playTorrentPage = function(url, cb) {
res
.on("data", d => str += d )
.on("error", err => {
notify("Error downloading page!", err.toString());
console.trace(err);
cb();
})
.on("end", () => {
var magnet = findMagnet(str);
if (!magnet) {
notify("No magnet link on page!");
cb();
cb("No magnet link on page!");
return;
}


+ 0
- 54
js/play/subtitle-finder.js View File

@@ -1,54 +0,0 @@
var OpenSubtitles = require("opensubtitles-api");
var fs = require("fs");
var http = require("http");
var urllib = require("url");

var subs = new OpenSubtitles({ useragent: "mmpc-media-streamer" });

var conf;

exports.init = function(app, _conf) {
conf = _conf;
}

exports.find = function(filesize, filename, cb) {
if (!conf.subtitles)
return cb();

var subFile = conf.tmpdir+"/subs.srt";
try {
fs.unlinkSync(subFile);
} catch (err) {}

subs.search({
sublanguageid: conf.subtitles,
filesize: filesize,
filename: filename
}).then(subtitles => {
var sub = subtitles[conf.subtitles];
if (!sub || !sub.url)
return cb();

try {
var ws = fs.createWriteStream(subFile);
} catch (err) {
console.trace(err);
cb();
}

http.request(urllib.parse(sub.url), res => {
res.pipe(ws);

res
.on("error", err => {
notify("Error finding subtitles", err.toString());
console.trace(err);
cb();
})
.on("end", () => {
ws.close();
setTimeout(() => cb(subFile), 500);
});
}).end();
});
}

+ 8
- 28
server.js View File

@@ -1,7 +1,6 @@
var fs = require("fs");
var pathlib = require("path");
var web = require("webstuff");
var notify = require("./js/notify");
var play = require("./js/play");
var fsutil = require("./js/fsutil");

@@ -24,8 +23,13 @@ app.post("/play/url", (req, res) => {
if (!fields.url)
return res.redirect("/");

function cb() {
res.redirect(play.httpPath);
function cb(err) {
if (err) {
console.log(err);
res.redirect("/");
} else {
res.redirect(play.httpPath);
}
}

if (fields.url.indexOf("magnet:") === 0) {
@@ -33,32 +37,8 @@ app.post("/play/url", (req, res) => {
} else if (fields.url.indexOf("/torrent") !== -1) {
play.playTorrentPage(fields.url, cb);
} else {
play.playUrl(fields.url, cb);
}
});
});

app.post("/play/file", (req, res) => {
notify("Receiving file...");
req.parseBody((err, fields, files) => {
var file = files.file;

if (file == null || !file.name || file.size === 0)
return res.redirect("/");

var fname = conf.tmpdir+"/uploaded-file"+pathlib.extname(file.name);
fsutil.move(file.path, fname, err => {
if (err) {
console.trace(err);
return res.redirect("/");
}

play.cleanupFiles.push(fname);

play.playFile(fname, () => {
res.redirect(play.httpPath);
}, file.name);
});
}
});
});


+ 0
- 6
web/index.html View File

@@ -13,12 +13,6 @@
<input type="url" name="url" autocomplete="off">
<button>Play</button>
</form>

<form class="part" method="post" enctype="multipart/form-data" action="/play/file">
<div class="name">File:</div>
<input type="file" name="file" autocomplete="off">
<button>Play</button>
</form>
</div>

<script src="/webstuff.js"></script>

+ 4
- 33
web/playback/index.html View File

@@ -7,38 +7,9 @@
<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">&lt;</button>
<button id="pause">||</button>
<button id="skip-forward">&gt;</button>
<button id="exit">Exit</button>
</div>

<div id="group-volume">
Volume <span id="volume-text">0</span>%<br>
<input id="volume" type="range">
</div>

<div id="group-sub-delay">
Subtitle Delay <span id="sub-delay">0</span>s<br>
<button id="sub-delay-less2">-1</button>
<button id="sub-delay-less">-0.1</button>
<button id="sub-delay-reset">0</button>
<button id="sub-delay-more">+0.1</button>
<button id="sub-delay-more2">+1</button>
</div>

<script src="/webstuff.js"></script>
<script src="script.js"></script>
<video controls autoplay>
<source src="/http-stream">
Your browser does not support the video tag.
</video>
</body>
</html>

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

@@ -1,165 +0,0 @@
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"),

sub_delay: $$("#sub-delay"),
sub_delay_less: $$("#sub-delay-less"),
sub_delay_less2: $$("#sub-delay-less2"),
sub_delay_more: $$("#sub-delay-more"),
sub_delay_more2: $$("#sub-delay-more2"),
sub_delay_reset: $$("#sub-delay-reset")
};

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;

// Sub Delay
elems.sub_delay.innerHTML = state.sub_delay;
}

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");
playerset("time-pos", state.time_pos - 1);
}
});

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

// Less subtitle delay
elems.sub_delay_less.addEventListener("click", function() {
playerset("sub-delay", state.sub_delay - 0.1);
});
elems.sub_delay_less2.addEventListener("click", function() {
playerset("sub-delay", state.sub_delay - 1);
});

// More subtitle delay
elems.sub_delay_more.addEventListener("click", function() {
playerset("sub-delay", state.sub_delay + 0.1);
});
elems.sub_delay_more2.addEventListener("click", function() {
playerset("sub-delay", state.sub_delay + 1);
});

// Set subtitle delay to 0
elems.sub_delay_reset.addEventListener("click", function() {
playerset("sub-delay", 0);
});

window.addEventListener("keydown", function(evt) {
console.log(evt.keyCode);
});

+ 2
- 84
web/playback/style.css View File

@@ -1,89 +1,7 @@
* {
box-sizing: border-box;
line-height: 30px;
font-family: Sans-Serif
}

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

/*
* Playback
*/

#is-playing,
#progress-text {
display: inline-block;
}
#progress-text {
float: right;
}
#group-buttons {
text-align: center;
}

progress {
width: 100%;
height: 24px;
}

/*
* Volume
*/

input[type="range"] {
border: 1px solid rgba(0, 0, 0, 0);
box-sizing: border-box;
margin: 0px;
width: 100%;
}

#group-volume {
text-align: center;
border-top: 1px solid #ccc;
}

#group-volume {
padding-top: 12px;
margin-top: 24px;
}

/*
* Sub Delay
*/

#group-sub-delay {
text-align: center;
}

#group-sub-delay {
border-top: 1px solid #ccc;
}

#group-sub-delay {
padding-top: 12px;
}
#group-sub-delay button {
margin-top: 6px;
video {
width: 100%;
}

Loading…
Cancel
Save