initial commit

Este commit está contenido en:
ale
2020-12-02 21:13:47 +00:00
commit 9fcdf54113
Se han modificado 10 ficheros con 809 adiciones y 0 borrados

13
README.md Archivo normal
Ver fichero

@@ -0,0 +1,13 @@
# p2p-media
### p2p multimedia software based on [p2p-media-loader](https://github.com/Novage/p2p-media-loader)
## Run
```
docker-compose up -d
```
## License
### MIT

23
docker-compose.yml Archivo normal
Ver fichero

@@ -0,0 +1,23 @@
version: '2'
services:
p2p-media:
build: ./p2p-media
container_name: p2p-media
hostname: p2p-media
restart: always
entrypoint:
- /bin/bash
- /home/node/p2p-media/entrypoint.sh
volumes:
- ./p2p-media:/home/node/p2p-media
- ./p2p-media/config.json:/home/node/wt-tracker/config.json:ro
expose:
- 8080
- 9000
networks:
p2p-net:
networks:
p2p-net:

8
p2p-media/Dockerfile Archivo normal
Ver fichero

@@ -0,0 +1,8 @@
FROM node:10-slim
RUN apt update && apt -y upgrade && apt -y install git python build-essential && apt clean
USER node
WORKDIR /home/node
RUN git clone https://github.com/Novage/wt-tracker
WORKDIR /home/node/wt-tracker
RUN npm i
RUN npm run build

21
p2p-media/config.json Archivo normal
Ver fichero

@@ -0,0 +1,21 @@
{
"servers": [
{
"server": {
"port": 9000,
"host": "0.0.0.0"
},
"websockets": {
"path": "/*",
"maxPayloadLength": 65536,
"idleTimeout": 240,
"compression": 1,
"maxConnections": 0
}
}
],
"tracker": {
"maxOffers": 20,
"announceInterval": 120
}
}

5
p2p-media/entrypoint.sh Archivo normal
Ver fichero

@@ -0,0 +1,5 @@
#!/bin/bash
npm start config.json &
cd /home/node/p2p-media
npm i
node index.js

26
p2p-media/index.js Archivo normal
Ver fichero

@@ -0,0 +1,26 @@
const http = require('http'),
express = require('express'),
app = express(),
geoip = require('geoip-lite'),
morgan = require('morgan'),
compression = require('compression'),
rfs = require('rotating-file-stream'),
accessLogStream = rfs.createStream('access.log', {
interval: '1d',
path: __dirname + '/logs',
compress: 'gzip'
}),
server = http.createServer(app).listen(8080, () => {
console.log(`Listening on ${server.address().address}:${server.address().port}`)
})
app.disable('x-powered-by')
.use(compression({ level: 9 }))
.use(morgan('combined', { stream: accessLogStream }))
.use(express.json())
.post('/ip', (req, res) => {
if (req.body.ip.match(/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/)) {
res.json(geoip.lookup(req.body.ip))
}
})
.use(express.static(__dirname + '/public'))

28
p2p-media/package.json Archivo normal
Ver fichero

@@ -0,0 +1,28 @@
{
"name": "p2p-media",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"install": "browserify -r p2p-graph | terser -m -c > public/js/p2p-graph.js && cp node_modules/clappr/dist/clappr.min.js node_modules/hls.js/dist/hls.min.js node_modules/level-selector/dist/level-selector.min.js node_modules/p2p-media-loader-hlsjs/build/p2p-media-loader-hlsjs.min.js node_modules/p2p-media-loader-core/build/p2p-media-loader-core.min.js public/js/ && cp node_modules/ipfs/dist/index.min.js public/js/ipfs.min.js && cp node_modules/hlsjs-ipfs-loader/dist/index.min.js public/js/hlsjs-ipfs-loader.min.js"
},
"author": "ale",
"license": "MIT",
"dependencies": {
"browserify": "*",
"clappr": "*",
"compression": "*",
"express": "*",
"geoip-lite": "*",
"hls.js": "*",
"hlsjs-ipfs-loader": "*",
"ipfs": "*",
"level-selector": "github:clappr/clappr-level-selector-plugin",
"morgan": "*",
"p2p-graph": "*",
"p2p-media-loader-core": "*",
"p2p-media-loader-hlsjs": "*",
"rotating-file-stream": "*",
"terser": "*"
}
}

195
p2p-media/public/css/main.css Archivo normal
Ver fichero

@@ -0,0 +1,195 @@
body {
font: normal 400 16px 'Roboto', sans-serif;
color: #333333;
margin-top: 1em;
margin-bottom: 3em;
}
.main-header .title {
text-align: center;
}
.container {
padding: 0 15px;
margin: 0 auto;
}
@media (min-width: 576px) {
.container {
max-width: 540px;
}
}
@media (min-width: 768px) {
.container {
max-width: 720px;
}
}
@media (min-width: 992px) {
.container {
max-width: 960px;
}
}
@media (min-width: 1200px) {
.container {
max-width: 1140px;
}
}
#main-view *,
::after,
::before {
box-sizing: border-box;
}
#main-view .wrapper {
display: flex;
flex-wrap: wrap;
}
#main-view .wrapper .column-1,
#main-view .wrapper .column-2 {
width: 100%;
}
@media (min-width: 768px) {
#main-view .wrapper .column-1 {
flex: 0 0 66.666667%;
max-width: 66.666667%;
padding-right: 30px;
}
#main-view .wrapper .column-2 {
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
}
#video {
width: 100%;
}
.embed-responsive {
position: relative;
display: block;
width: 100%;
padding: 0;
overflow: hidden;
}
#main-view .form-group {
display: flex;
flex-direction: column;
margin-bottom: 1em;
}
#main-view .form-group label {
margin-bottom: 0.5em;
}
#main-view .form-control {
display: block;
width: 100%;
height: calc(2.25em + 2px);
padding: .375em .75em;
font-size: 1em;
line-height: 1.5em;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25em;
}
#main-view .form-button {
text-align: center;
white-space: nowrap;
border: 1px solid transparent;
padding: .375em .75em;
font-size: 1em;
line-height: 1.5em;
border-radius: .25em;
color: #fff;
background-color: #972e2d;
border-color: #972e2d;
margin-bottom: 5px;
}
#main-view .form-button:hover {
background-color: #c60000;
border-color: #c60000;
}
#main-view .form-button:focus {
outline: none;
}
#main-view .embed-responsive::before {
display: block;
content: "";
}
#main-view .embed-responsive .embed-responsive-item {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
border: 0;
}
#main-view .embed-responsive-16by9::before {
padding-top: 56.25%;
}
#main-view #level {
margin-top: 2px;
width: auto;
float: right;
}
#graph {
max-width: 100%;
overflow: hidden;
margin: 3em auto;
border: 1px solid #eee;
}
#chart_container {
position: relative;
margin: 3em auto;
padding-left: 20px;
max-width: 100%;
}
#y_axis {
position: absolute;
top: 0;
width: 40px;
left: -20px;
}
#y_axis>svg {
overflow: visible;
}
#legend {
position: absolute;
top: 20px;
left: 40px;
z-index: 1;
}
#legend-totals {
position: absolute;
bottom: 20px;
left: 40px;
z-index: 1;
}
#main-view .hide {
display: none;
}

62
p2p-media/public/index.html Archivo normal
Ver fichero

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>P2P</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/rickshaw/1.6.3/rickshaw.min.css">
<link type="text/css" rel="stylesheet" href="css/main.css">
<script src="js/p2p-media-loader-core.min.js"></script>
<script src="js/p2p-media-loader-hlsjs.min.js"></script>
<script src="js/ipfs.min.js"></script>
<script src="js/hlsjs-ipfs-loader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script async src="https://cdnjs.cloudflare.com/ajax/libs/rickshaw/1.6.3/rickshaw.min.js"></script>
<script src="js/p2p-graph.js"></script>
<script src="js/main.js"></script>
</head>
<body>
<div class="container">
<header class="main-header">
<h2 class="title">P2P - HatThieves</h2>
</header>
<div id="main-view">
<div id="error-webrtc-data-channels" class="hide">
WebRTC Data Channels API is not supported by your browser. P2P disabled.<br>
Read more at <a href="http://iswebrtcreadyyet.com/legacy.html" target="_blank">Is WebRTC ready yet?</a>.
<hr>
</div>
<div id="error-hls-js" class="hide">
Your browser doesn't support hls.js engine. P2P disabled.<br>
Read more at <a href="https://en.wikipedia.org/wiki/Media_Source_Extensions" target="_blank">Media
Source Extensions</a>.
<hr>
</div>
<div id="error-shakaplayer" class="hide">
Your browser doesn't support Shaka Player engine. P2P disabled.<br>
Read more at <a href="https://en.wikipedia.org/wiki/Media_Source_Extensions" target="_blank">Media
Source Extensions</a>.
<hr>
</div>
<div class="wrapper">
<div class="column-1">
<div id="video_container"></div>
<div id="chart_container">
<div id="legend"></div>
<div id="legend-totals"></div>
<div id="y_axis"></div>
<div id="chart"></div>
</div>
</div>
<div class="column-2">
<div id="graph"></div>
</div>
</div>
</div>
</div>
</body>
</html>

428
p2p-media/public/js/main.js Archivo normal
Ver fichero

@@ -0,0 +1,428 @@
function waitForGlobalObject(objectName, objectNextName) {
return new Promise((resolve) => {
function check() {
if ((window[objectName] !== undefined)
&& ((objectNextName === undefined) || window[objectName][objectNextName] !== undefined)) {
resolve();
} else {
setTimeout(check, 200);
}
}
check();
});
}
function waitForModule(moduleName) {
return new Promise((resolve) => {
function check() {
try {
resolve(require(moduleName));
} catch (e) {
setTimeout(check, 200);
}
}
check();
});
}
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.onload = () => {
resolve();
};
script.onerror = () => {
console.log("Failed to load script", src);
reject();
};
script.src = src;
document.head.appendChild(script);
});
}
class App {
async init() {
await waitForGlobalObject("p2pml", "core", "Ipfs", "HlsjsIpfsLoader", "Hls");
this.isP2PSupported = p2pml.core.HybridLoader.isSupported();
if (!this.isP2PSupported) {
document.querySelector("#error-webrtc-data-channels").classList.remove("hide");
}
var params = (new URL(document.location)).searchParams;
this.videoUrl = params.get('url') ? params.get('url') : 'https://hls.hatthieves.es/hls/streaming.m3u8';
this.videoContainer = document.getElementById("video_container");
this.loadSpeedTimespan = 10; // seconds
const P2PGraphClass = await waitForModule("p2p-graph");
this.graph = new P2PGraphClass("#graph");
this.graph.add({ id: "me", name: "Tú", me: true });
await waitForGlobalObject("Rickshaw");
this.initChart();
this.restartApp();
}
async restartApp() {
this.downloadStats = [];
this.downloadTotals = { http: 0, p2p: 0 };
this.uploadStats = [];
this.uploadTotal = 0;
while (this.videoContainer.hasChildNodes()) {
this.videoContainer.removeChild(this.videoContainer.lastChild);
}
const config = {
loader: {
trackerAnnounce: 'wss://p2p.hatthieves.es/ws'
},
rtcConfig: {
iceServers: [
{ urls: "stuns:hatthieves.es:3479" },
{ urls: "stun:hatthieves.es:3478" }
]
}
};
await loadScript("js/hls.min.js");
if (!Hls.isSupported()) {
document.querySelector("#error-hls-js").classList.remove("hide");
}
this.engine = this.isP2PSupported ? new p2pml.hlsjs.Engine(config) : undefined;
this.initClapprPlayer();
if (this.isP2PSupported) {
this.engine.on(p2pml.core.Events.PieceBytesDownloaded, this.onBytesDownloaded.bind(this));
this.engine.on(p2pml.core.Events.PieceBytesUploaded, this.onBytesUploaded.bind(this));
}
this.refreshChart();
this.refreshGraph();
}
async initClapprPlayer() {
const scriptsPromise = (async () => {
await loadScript("js/clappr.min.js");
await loadScript("js/level-selector.min.js");
})();
var outer = document.createElement("div");
outer.className = "embed-responsive embed-responsive-16by9";
var video = document.createElement("div");
video.id = "video";
video.className = "embed-responsive-item";
outer.appendChild(video);
this.videoContainer.appendChild(outer);
const params = (new URL(document.location)).searchParams
const setup = {
parentId: "#video",
plugins: [],
source: params.get('key') && params.get('key').length > 0 ? params.get('key') : this.videoUrl,
width: "100%",
height: "100%",
muted: false,
autoPlay: true,
poster: 'https://www.hatthieves.es/wp-content/uploads/2020/04/cropped-blackground3-2.jpg',
playbackNotSupportedMessage: 'Play is not supported',
watermark: 'https://www.hatthieves.es/wp-content/uploads/2019/08/cropped-ht.png', position: 'top-left',
watermarkLink: 'https://www.hatthieves.es',
mediacontrol: { seekbar: "#ffffff", buttons: "#ffffff" },
playback: { playInline: true, preload: true, maxBufferLength: 30 }
};
if (params.get('key') && params.get('key').length > 0) {
const repoPath = 'ipfs-' + Math.random(),
node = await Ipfs.create({
repo: repoPath
}),
ipfsHash = params.get('hash') ? params.get('hash') : '',
key = params.get('key') ? params.get('key') : ''
setup.playback.hlsjsConfig = {
debug: false,
loader: HlsjsIpfsLoader,
ipfs: node,
ipfsHash: ipfsHash
};
document.getElementById('chart_container').style.display = 'none'
document.getElementById('graph').parentNode.style.display = 'none'
document.getElementById('video_container').parentNode.style.flex = '0 0 100%'
document.getElementById('video_container').parentNode.style['max-width'] = '100%'
} else {
setup.playback.hlsjsConfig = {
// liveSyncDurationCount: 7,
loader: this.isP2PSupported ? this.engine.createLoaderClass() : Hls.DefaultConfig.loader
};
}
await scriptsPromise;
setup.plugins.push(LevelSelector);
var player = new Clappr.Player(setup);
if (this.isP2PSupported) {
p2pml.hlsjs.initClapprPlayer(player);
}
}
initChart() {
var chartConf = {
element: document.querySelector("#chart"),
renderer: 'multi',
interpolation: "basis",
stack: false,
min: 'auto',
strokeWidth: 1,
series: [
{ name: "Upload P2P", color: "#88eab9", data: [], renderer: 'area' },
{ name: " - P2P", color: "#88b9ea", data: [], renderer: 'area' },
{ name: " - HTTP", color: "#eae288", data: [], renderer: 'area' },
{ name: "Download", color: "#f64", data: [], renderer: 'line' }
]
};
this.chart = new Rickshaw.Graph(chartConf);
new Rickshaw.Graph.Axis.X({
graph: this.chart,
tickFormat: () => ''
});
new Rickshaw.Graph.Axis.Y({
graph: this.chart,
orientation: 'left',
element: document.getElementById('y_axis')
});
this.legend = new Rickshaw.Graph.Legend({
graph: this.chart,
element: document.getElementById('legend')
});
this.legendTotals = new Rickshaw.Graph.Legend({
graph: this.chart,
element: document.getElementById("legend-totals")
});
this.chart.render();
setInterval(this.updateChartData.bind(this), 1000);
var chartResize = () => {
chartConf.width = this.chart.element.clientWidth;
this.chart.configure(chartConf);
this.chart.render();
};
chartResize();
window.addEventListener("resize", chartResize);
}
refreshChart() {
if (!this.chart) {
return;
}
var data0 = this.chart.series[0].data;
var data1 = this.chart.series[1].data;
var data2 = this.chart.series[2].data;
var data3 = this.chart.series[3].data;
var lastX = data0.length > 0 ? data0[data0.length - 1].x : -1;
var seriesDataMapper = (currentValue, index) => ({ x: index + lastX + 1, y: 0 });
data0.length = 0;
data1.length = 0;
data2.length = 0;
data3.length = 0;
var stubData = Array.apply(null, Array(200)).map(seriesDataMapper);
data0.push.apply(data0, stubData.slice(0));
data1.push.apply(data1, stubData.slice(0));
data2.push.apply(data2, stubData.slice(0));
data3.push.apply(data3, stubData.slice(0));
this.chart.update();
}
updateChartData() {
var downloadSpeed = this.getDownloadSpeed();
var http = Number((downloadSpeed.http * 8 / 1000000).toFixed(2));
var p2p = Number((downloadSpeed.p2p * 8 / 1000000).toFixed(2));
var total = Number((http + p2p).toFixed(2));
var upload = Number(this.getUploadSpeed() * 8 / 1000000).toFixed(2);
var data0 = this.chart.series[0].data;
var data1 = this.chart.series[1].data;
var data2 = this.chart.series[2].data;
var data3 = this.chart.series[3].data;
var x = data0.length > 0 ? data0[data0.length - 1].x + 1 : 0;
data0.shift();
data1.shift();
data2.shift();
data3.shift();
data0.push({ x: x, y: -upload });
data1.push({ x: x, y: total });
data2.push({ x: x, y: http });
data3.push({ x: x, y: total });
this.chart.update();
this.formatChartLegendLine(0, total);
this.formatChartLegendLine(1, http);
this.formatChartLegendLine(2, p2p);
this.formatChartLegendLine(3, upload);
this.updateLegendTotals();
}
formatChartLegendLine(index, speed) {
if (this.legend) {
var line = this.legend.lines[index];
line.element.childNodes[1].textContent = line.series.name + ' - ' + speed + ' Mbit/s';
}
}
updateLegendTotals() {
if (!this.legendTotals) {
return;
}
var httpMb = this.downloadTotals.http / 1048576;
var p2pMb = this.downloadTotals.p2p / 1048576;
var totalMb = httpMb + p2pMb;
var uploadMb = this.uploadTotal / 1048576;
if (totalMb != 0) {
this.legendTotals.lines[0].element.childNodes[1].textContent
= "Download - "
+ Number(totalMb).toFixed(1) + " MiB";
this.legendTotals.lines[1].element.childNodes[1].textContent
= " - HTTP - "
+ Number(httpMb).toFixed(1) + " MiB - "
+ Number((httpMb * 100) / totalMb).toFixed(0) + "%";
this.legendTotals.lines[2].element.childNodes[1].textContent
= " - P2P - "
+ Number(p2pMb).toFixed(1) + " MiB - "
+ Number((p2pMb * 100) / totalMb).toFixed(0) + "%";
this.legendTotals.lines[3].element.childNodes[1].textContent
= "Upload P2P - "
+ Number(uploadMb).toFixed(1) + " MiB";
}
}
getDownloadSpeed() {
var startingPoint = performance.now() - (this.loadSpeedTimespan * 1000);
var httpSize = 0;
var p2pSize = 0;
var i = this.downloadStats.length;
while (i--) {
var stat = this.downloadStats[i];
if (stat.timestamp < startingPoint) {
break;
}
if (stat.method === "p2p") {
p2pSize += stat.size;
} else if (stat.method === "http") {
httpSize += stat.size;
}
}
this.downloadStats.splice(0, i + 1);
return { p2p: p2pSize / this.loadSpeedTimespan, http: httpSize / this.loadSpeedTimespan };
}
getUploadSpeed() {
var startingPoint = performance.now() - (this.loadSpeedTimespan * 1000);
var size = 0;
var i = this.uploadStats.length;
while (i--) {
var stat = this.uploadStats[i];
if (stat.timestamp < startingPoint) {
break;
}
size += stat.size;
}
this.uploadStats.splice(0, i + 1);
return size / this.loadSpeedTimespan;
}
onBytesDownloaded(method, size) {
this.downloadStats.push({ method: method, size: size, timestamp: performance.now() });
this.downloadTotals[method] += size;
}
onBytesUploaded(method, size) {
this.uploadStats.push({ size: size, timestamp: performance.now() });
this.uploadTotal += size;
}
refreshGraph() {
if (!this.graph) {
return;
}
var nodes = this.graph.list();
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].id !== "me") {
this.graph.disconnect("me", nodes[i].id);
this.graph.remove(nodes[i].id);
}
}
if (this.isP2PSupported) {
this.engine.on(p2pml.core.Events.PeerConnect, this.onPeerConnect.bind(this));
this.engine.on(p2pml.core.Events.PeerClose, this.onPeerClose.bind(this));
}
}
onPeerConnect(peer) {
if (!this.graph.hasPeer(peer.id)) {
var oReq = new XMLHttpRequest(),
graph = this.graph
oReq.onreadystatechange = function () {
if (oReq.readyState != 4) { return; }
var location = this.response
graph.add({ id: peer.id, name: location.city + ' - ' + location.region + ' (' + location.country + ')' || 'Unknown' });
graph.connect("me", peer.id);
}
oReq.open('POST', 'ip', true);
oReq.responseType = 'json';
oReq.setRequestHeader('Content-Type', 'application/json');
oReq.send(JSON.stringify({ ip: peer.remoteAddress }));
}
}
onPeerClose(id) {
if (this.graph.hasPeer(id)) {
this.graph.disconnect("me", id);
this.graph.remove(id);
}
}
}
window.onload = function () {
window.app = new App();
window.app.init();
}