UNPKG

@moartube/moartube-client

Version:

A free, open-source, self-hosted, anonymous, decentralized video/live stream platform. Scalable via Cloudflare, works in the cloud or from home WiFi.

224 lines (183 loc) 8.26 kB
const http = require('http'); const express = require('express'); const expressSession = require('express-session'); const bodyParser = require('body-parser'); const path = require('path'); const fs = require('fs'); const webSocket = require('ws'); const crypto = require('crypto'); const engine = require('express-dot-engine'); const ffmpegPath = String(require('ffmpeg-static')).replace('app.asar', 'app.asar.unpacked'); const { logDebugMessageToConsole, performEncodingDecodingAssessment, cleanVideosDirectory, getPublicDirectoryPath, getDataDirectoryPath, getMoarTubeClientPort, setPublicDirectoryPath, setDataDirectoryPath, setVideosDirectoryPath, setFfmpegPath, setMoarTubeClientPort, setWebsocketServer, getClientSettings, getVideosDirectoryPath, getIsDeveloperMode, setIsDeveloperMode, getViewsDirectoryPath, setViewsDirectoryPath } = require('./utils/helpers'); const { startVideoPublishInterval } = require('./utils/handlers/video-publish-handler'); const { node_isAuthenticated } = require('./utils/node-communications'); const homeRoutes = require('./routes/home'); const accountRoutes = require('./routes/account'); const monetizationRoutes = require('./routes/monetization'); const linksRoutes = require('./routes/links'); const settingsRoutes = require('./routes/settings'); const videosRoutes = require('./routes/videos'); const streamsRoutes = require('./routes/streams'); const reportsVideosRoutes = require('./routes/reports-videos'); const reportsCommentsRoutes = require('./routes/reports-comments'); const commentsRoutes = require('./routes/comments'); const nodeRoutes = require('./routes/node'); startClient(); async function startClient() { process.on('uncaughtException', (error) => { /* ffmpeg utilizes trailer information to detect the end (end of file, EOF) of piped input to stdin. This will trigger an uncaught exception (Error: write EOF) due to live mpeg-ts segments not having a trailer, thus no EOF indication. This is benign, also reportedly does not occur on Unix-based systems, though unconfirmed. */ if (!error.stack.includes('Error: write EOF')) { logDebugMessageToConsole(null, error, new Error().stack); } }); process.on('unhandledRejection', (reason, promise) => { logDebugMessageToConsole(null, reason, new Error().stack); }); logDebugMessageToConsole('starting MoarTube Client', null, null); loadConfig(); setFfmpegPath(ffmpegPath); cleanVideosDirectory(); performEncodingDecodingAssessment(); startVideoPublishInterval(); const app = express(); app.enable('trust proxy'); app.use('/javascript', express.static(path.join(getPublicDirectoryPath(), 'javascript'))); app.use('/css', express.static(path.join(getPublicDirectoryPath(), 'css'))); app.use('/images', express.static(path.join(getPublicDirectoryPath(), 'images'))); app.use('/fonts', express.static(path.join(getPublicDirectoryPath(), 'fonts'))); const sessionMiddleware = expressSession({ name: crypto.randomBytes(64).toString('hex'), secret: crypto.randomBytes(64).toString('hex'), resave: false, saveUninitialized: true }); app.use(sessionMiddleware); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); app.engine('dot', engine.__express); app.set('views', getViewsDirectoryPath()); app.set('view engine', 'dot'); app.use('/', homeRoutes); app.use('/account', accountRoutes); app.use('/monetization', monetizationRoutes); app.use('/links', linksRoutes); app.use('/settings', settingsRoutes); app.use('/videos', videosRoutes); app.use('/streams', streamsRoutes); app.use('/reports/videos', reportsVideosRoutes); app.use('/reports/comments', reportsCommentsRoutes); app.use('/comments', commentsRoutes); app.use('/node', nodeRoutes); const httpServer = http.createServer(app); httpServer.requestTimeout = 0; // needed for long duration requests (streaming, large uploads) httpServer.listen(getMoarTubeClientPort(), function () { logDebugMessageToConsole('MoarTube Client is listening on port ' + getMoarTubeClientPort(), null, null); const websocketServer = new webSocket.Server({ noServer: true, perMessageDeflate: false }); setWebsocketServer(websocketServer); websocketServer.on('connection', function connection(ws) { logDebugMessageToConsole('browser websocket client connected', null, null); ws.on('close', () => { logDebugMessageToConsole('browser websocket client disconnected', null, null); }); }); httpServer.on('upgrade', function upgrade(req, socket, head) { websocketServer.handleUpgrade(req, socket, head, function done(ws) { sessionMiddleware(req, {}, () => { node_isAuthenticated(req.session.jwtToken) .then(nodeResponseData => { if (nodeResponseData.isError) { logDebugMessageToConsole(nodeResponseData.message, null, new Error().stack); } else { if (nodeResponseData.isAuthenticated) { websocketServer.emit('connection', ws, req); } else { ws.close(); } } }) .catch(error => { logDebugMessageToConsole(null, error, new Error().stack); }); }); }); }); }); } function discoverDataDirectoryPath() { const dataDirectory = process.env.APPDATA || (process.platform == 'darwin' ? process.env.HOME + '/Library/Preferences' : process.env.HOME + '/.local/share'); const dataDirectoryPath = path.join(dataDirectory, 'moartube-client'); return dataDirectoryPath; } function loadConfig() { process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; const config = JSON.parse(fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8')); setIsDeveloperMode(config.isDeveloperMode); if (getIsDeveloperMode()) { setDataDirectoryPath(path.join(__dirname, 'data')); } else { setDataDirectoryPath(discoverDataDirectoryPath()); } setPublicDirectoryPath(path.join(__dirname, 'public')); setViewsDirectoryPath(path.join(getPublicDirectoryPath(), 'views')); setVideosDirectoryPath(path.join(getDataDirectoryPath(), 'media', 'videos')); fs.mkdirSync(getDataDirectoryPath(), { recursive: true }); fs.mkdirSync(getVideosDirectoryPath(), { recursive: true }); if (!fs.existsSync(path.join(getDataDirectoryPath(), '_client_settings.json'))) { const clientSettings = { "clientListeningPort": 8080, "processingAgent": { "processingAgentType": "cpu", "processingAgentName": "", "processingAgentModel": "" }, // bitrate units are in kilobytes per second "videoEncoderSettings": { "hls": { "2160p-bitrate": 15000, "1440p-bitrate": 12000, "1080p-bitrate": 10000, "720p-bitrate": 8000, "480p-bitrate": 5000, "360p-bitrate": 4000, "240p-bitrate": 3000, "framerate": 30, "segmentLength": 6, "gop": 180 }, "mp4": { "2160p-bitrate": 15000, "1440p-bitrate": 12000, "1080p-bitrate": 10000, "720p-bitrate": 8000, "480p-bitrate": 5000, "360p-bitrate": 4000, "240p-bitrate": 3000, "framerate": 30, "gop": 60 }, "webm": { "2160p-bitrate": 15000, "1440p-bitrate": 12000, "1080p-bitrate": 10000, "720p-bitrate": 8000, "480p-bitrate": 5000, "360p-bitrate": 4000, "240p-bitrate": 3000, "framerate": 30, "gop": 60 }, "ogv": { "2160p-bitrate": 15000, "1440p-bitrate": 12000, "1080p-bitrate": 10000, "720p-bitrate": 8000, "480p-bitrate": 5000, "360p-bitrate": 4000, "240p-bitrate": 3000, "framerate": 30, "gop": 60 } }, "liveEncoderSettings": { "hls": { "2160p-bitrate": 10000, "1440p-bitrate": 10000, "1080p-bitrate": 8000, "720p-bitrate": 6000, "480p-bitrate": 5000, "360p-bitrate": 4000, "240p-bitrate": 3000, "framerate": 30, "segmentLength": 3, "gop": 90 } } }; fs.writeFileSync(path.join(getDataDirectoryPath(), '_client_settings_default.json'), JSON.stringify(clientSettings)); fs.writeFileSync(path.join(getDataDirectoryPath(), '_client_settings.json'), JSON.stringify(clientSettings)); } const clientSettings = getClientSettings(); setMoarTubeClientPort(clientSettings.clientListeningPort); }