From 65a5ecc3b627fd249eee35c4a2462cddbdad820b Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 10 May 2022 20:04:49 +0200 Subject: [PATCH 01/43] added `streaming-server` package --- packages/streaming-server/package.json | 17 +++++++++++++++++ packages/streaming-server/src/index.js | 1 + 2 files changed, 18 insertions(+) create mode 100644 packages/streaming-server/package.json create mode 100644 packages/streaming-server/src/index.js diff --git a/packages/streaming-server/package.json b/packages/streaming-server/package.json new file mode 100644 index 00000000..ebbf3b95 --- /dev/null +++ b/packages/streaming-server/package.json @@ -0,0 +1,17 @@ +{ + "name": "@comty/streaming-server", + "author": "RageStudio", + "version": "0.1.0", + "main": "dist/index.js", + "scripts": { + "dev": "nodemon --ignore dist/ --exec corenode-node ./src/index.js" + }, + "dependencies": { + "linebridge": "^0.11.13", + "node-media-server": "^2.3.9" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "nodemon": "^2.0.15" + } +} diff --git a/packages/streaming-server/src/index.js b/packages/streaming-server/src/index.js new file mode 100644 index 00000000..31233052 --- /dev/null +++ b/packages/streaming-server/src/index.js @@ -0,0 +1 @@ +import { Server } from "linebridge/dist/server" \ No newline at end of file From df151c5a0af183f8fb52165fbd50477d137b3e09 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 10 May 2022 20:45:15 +0200 Subject: [PATCH 02/43] add lib --- .../src/lib/getStreamingKeyFromStreamPath/index.js | 3 +++ packages/streaming-server/src/lib/index.js | 1 + 2 files changed, 4 insertions(+) create mode 100644 packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js create mode 100644 packages/streaming-server/src/lib/index.js diff --git a/packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js b/packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js new file mode 100644 index 00000000..dd42ae54 --- /dev/null +++ b/packages/streaming-server/src/lib/getStreamingKeyFromStreamPath/index.js @@ -0,0 +1,3 @@ +export default function getStreamingKeyFromStreamPath(StreamPath) { + return StreamPath.split("/").pop() +} \ No newline at end of file diff --git a/packages/streaming-server/src/lib/index.js b/packages/streaming-server/src/lib/index.js new file mode 100644 index 00000000..a74fde00 --- /dev/null +++ b/packages/streaming-server/src/lib/index.js @@ -0,0 +1 @@ +export { default as getStreamingKeyFromStreamPath } from "./getStreamingKeyFromStreamPath" \ No newline at end of file From f00a15548b753f0b13c386cd555bf4b56352402d Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 10 May 2022 20:45:24 +0200 Subject: [PATCH 03/43] add managers --- .../src/managers/DbManager/index.js | 40 +++++++++++++++++++ .../src/managers/SessionsManager/index.js | 17 ++++++++ .../streaming-server/src/managers/index.js | 2 + 3 files changed, 59 insertions(+) create mode 100644 packages/streaming-server/src/managers/DbManager/index.js create mode 100644 packages/streaming-server/src/managers/SessionsManager/index.js create mode 100644 packages/streaming-server/src/managers/index.js diff --git a/packages/streaming-server/src/managers/DbManager/index.js b/packages/streaming-server/src/managers/DbManager/index.js new file mode 100644 index 00000000..3531eb2c --- /dev/null +++ b/packages/streaming-server/src/managers/DbManager/index.js @@ -0,0 +1,40 @@ +import mongoose from "mongoose" + +function parseConnectionString(obj) { + const { db_user, db_driver, db_name, db_pwd, db_hostname, db_port } = obj + return `${db_driver ?? "mongodb"}://${db_user ? `${db_user}` : ""}${db_pwd ? `:${db_pwd}` : ""}${db_user ? "@" : ""}${db_hostname ?? "localhost"}:${db_port ?? ""}/${db_name ?? ""}` +} + +export default class DBManager { + constructor() { + this.env = process.env + } + + connect = () => { + return new Promise((resolve, reject) => { + try { + console.log("🌐 Trying to connect to DB...") + const dbUri = parseConnectionString(this.env) + + //console.log(dbUri) + + mongoose.connect(dbUri, { + useNewUrlParser: true, + useUnifiedTopology: true + }) + .then((res) => { return resolve(true) }) + .catch((err) => { return reject(err) }) + } catch (err) { + return reject(err) + } + }).then(done => { + console.log(`✅ Connected to DB`) + }).catch((error) => { + console.log(`❌ Failed to connect to DB, retrying...\n`) + console.log(error) + setTimeout(() => { + this.connect() + }, 1000) + }) + } +} \ No newline at end of file diff --git a/packages/streaming-server/src/managers/SessionsManager/index.js b/packages/streaming-server/src/managers/SessionsManager/index.js new file mode 100644 index 00000000..554c1e48 --- /dev/null +++ b/packages/streaming-server/src/managers/SessionsManager/index.js @@ -0,0 +1,17 @@ +export default class SessionsManager { + constructor() { + this.sessions = {} + } + + newSession = (id, session) => { + this.sessions[id] = session + } + + getSession = (id) => { + return this.sessions[id] + } + + removeSession = (id) => { + delete this.sessions[id] + } +} \ No newline at end of file diff --git a/packages/streaming-server/src/managers/index.js b/packages/streaming-server/src/managers/index.js new file mode 100644 index 00000000..11ce56ad --- /dev/null +++ b/packages/streaming-server/src/managers/index.js @@ -0,0 +1,2 @@ +export { default as DbManager } from "./DbManager" +export { default as SessionsManager } from "./SessionsManager" From fa2e40c75db03517f12a3f460f5a5f614d384987 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 10 May 2022 20:45:40 +0200 Subject: [PATCH 04/43] added `StreamingKeys` model --- packages/streaming-server/src/models/index.js | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/streaming-server/src/models/index.js diff --git a/packages/streaming-server/src/models/index.js b/packages/streaming-server/src/models/index.js new file mode 100644 index 00000000..fd844dec --- /dev/null +++ b/packages/streaming-server/src/models/index.js @@ -0,0 +1,20 @@ +import mongoose, { Schema } from "mongoose" + +function getSchemas() { + const obj = Object() + + const _schemas = require("../schemas") + Object.keys(_schemas).forEach((key) => { + obj[key] = Schema(_schemas[key]) + }) + + return obj +} + +const schemas = getSchemas() + +// server +export const Config = mongoose.model("Config", schemas.Config, "config") + +// streaming +export const StreamingKeys = mongoose.model("StreamingKeys", schemas.StreamingKey, "StreamingKeys") \ No newline at end of file From d154c36062c24b79d305c8ac9c94550978338ac9 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 10 May 2022 20:45:45 +0200 Subject: [PATCH 05/43] added `StreamingKeys` schema --- .../streaming-server/src/schemas/StreamingKey/index.js | 10 ++++++++++ packages/streaming-server/src/schemas/index.js | 1 + 2 files changed, 11 insertions(+) create mode 100644 packages/streaming-server/src/schemas/StreamingKey/index.js create mode 100644 packages/streaming-server/src/schemas/index.js diff --git a/packages/streaming-server/src/schemas/StreamingKey/index.js b/packages/streaming-server/src/schemas/StreamingKey/index.js new file mode 100644 index 00000000..86a50b1a --- /dev/null +++ b/packages/streaming-server/src/schemas/StreamingKey/index.js @@ -0,0 +1,10 @@ +export default { + userId: { + type: String, + required: true, + }, + key: { + type: String, + required: true, + } +} \ No newline at end of file diff --git a/packages/streaming-server/src/schemas/index.js b/packages/streaming-server/src/schemas/index.js new file mode 100644 index 00000000..63fcdb63 --- /dev/null +++ b/packages/streaming-server/src/schemas/index.js @@ -0,0 +1 @@ +export { default as StreamingKey } from "./StreamingKey" \ No newline at end of file From 06f38164b35e6862c89659b75792ff3398d9eed2 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Tue, 10 May 2022 20:45:55 +0200 Subject: [PATCH 06/43] added `mongoose` dep --- packages/streaming-server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/streaming-server/package.json b/packages/streaming-server/package.json index ebbf3b95..66f76642 100644 --- a/packages/streaming-server/package.json +++ b/packages/streaming-server/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "linebridge": "^0.11.13", + "mongoose": "^6.3.3", "node-media-server": "^2.3.9" }, "devDependencies": { From 9b44010bc3bbfaa8c7ec0dd90d70ce1e751bd789 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:38:09 +0200 Subject: [PATCH 07/43] added basic streaming control panel --- .../app/src/pages/streaming_control/index.jsx | 115 ++++++++++++++++++ .../src/pages/streaming_control/index.less | 36 ++++++ 2 files changed, 151 insertions(+) create mode 100644 packages/app/src/pages/streaming_control/index.jsx create mode 100644 packages/app/src/pages/streaming_control/index.less diff --git a/packages/app/src/pages/streaming_control/index.jsx b/packages/app/src/pages/streaming_control/index.jsx new file mode 100644 index 00000000..c6d1c4ea --- /dev/null +++ b/packages/app/src/pages/streaming_control/index.jsx @@ -0,0 +1,115 @@ +import React from "react" +import * as antd from "antd" +import { Icons } from "components/Icons" + +import "./index.less" + +const StreamingKeyView = (props) => { + const [streamingKeyVisibility, setStreamingKeyVisibility] = React.useState(false) + + const toogleVisibility = (to) => { + setStreamingKeyVisibility(to ?? !streamingKeyVisibility) + } + + return
+ {streamingKeyVisibility ? + <> + toogleVisibility()} /> +

+ {props.streamingKey ?? "No streaming key available"} +

+ : + <> + toogleVisibility()} /> + Show key + + } +
+} + +export default (props) => { + const [isConnected, setIsConnected] = React.useState(false) + const [targetServer, setTargetServer] = React.useState("No available server") + + const [streamingKey, setStreamingKey] = React.useState(null) + const [serverTier, setServerTier] = React.useState(null) + + const checkStreamingKey = async () => { + const result = await app.request.get.streamingKey().catch((error) => { + console.error(error) + antd.message.error(error.message) + + return null + }) + + if (result) { + setStreamingKey(result.key) + } + } + + const checkTagetServer = async () => { + const result = await app.request.get.targetStreamingServer() + + if (result) { + const targetServer = `${result.protocol}://${result.address}:${result.port}/${result.space}` + setTargetServer(targetServer) + } + } + + React.useEffect(() => { + checkStreamingKey() + checkTagetServer() + // TODO: Use UserTier controller to check streaming service tier + // by now, we just use a fixed value + setServerTier("basic") + }, []) + + return
+
+

Connection Status

+
+ : } + > + {isConnected ? "Connected" : "Disconnected"} + +
+
+ +
+

Server info

+
+
+ + Server Address +
+
+

+ {targetServer} +

+
+
+
+
+ + Streaming Key +
+
+ +
+
+
+
+ + Usage Tier +
+
+ + {serverTier} + +
+
+
+
+} \ No newline at end of file diff --git a/packages/app/src/pages/streaming_control/index.less b/packages/app/src/pages/streaming_control/index.less new file mode 100644 index 00000000..c128686c --- /dev/null +++ b/packages/app/src/pages/streaming_control/index.less @@ -0,0 +1,36 @@ +.streamingControlPanel { + display: inline-flex; + flex-direction: column; + + .info { + display: flex; + flex-direction: column; + + margin-bottom: 10px; + + .label { + + } + + .value { + margin-left: 10px; + font-family: "DM Mono", monospace; + + h4 { + // select all text + user-select: all; + margin: 0; + } + } + } + + > div { + margin-bottom: 20px; + } +} + +.streamingKeyString { + display: inline-flex; + flex-direction: row; + align-items: center; +} \ No newline at end of file From f149060dffc7c720bdedb960bbe815c3e1d93176 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:38:24 +0200 Subject: [PATCH 08/43] added `streamingKey` to models --- packages/server/src/models/index.js | 1 + packages/server/src/schemas/index.js | 3 ++- packages/server/src/schemas/streamingKey/index.js | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 packages/server/src/schemas/streamingKey/index.js diff --git a/packages/server/src/models/index.js b/packages/server/src/models/index.js index 4d41330a..a416d2b3 100644 --- a/packages/server/src/models/index.js +++ b/packages/server/src/models/index.js @@ -28,5 +28,6 @@ export const Post = mongoose.model("Post", schemas.Post, "posts") export const Comment = mongoose.model("Comment", schemas.Comment, "comments") // streamings +export const StreamingKey = mongoose.model("StreamingKey", schemas.streamingKey, "streamingKeys") // marketplace \ No newline at end of file diff --git a/packages/server/src/schemas/index.js b/packages/server/src/schemas/index.js index 308fc09f..035ab475 100644 --- a/packages/server/src/schemas/index.js +++ b/packages/server/src/schemas/index.js @@ -5,4 +5,5 @@ export { default as Config } from "./config" export { default as Post } from "./post" export { default as Comment } from "./comment" export { default as UserFollow } from "./userFollow" -export { default as Badge } from "./badge" \ No newline at end of file +export { default as Badge } from "./badge" +export { default as streamingKey } from "./streamingKey" \ No newline at end of file diff --git a/packages/server/src/schemas/streamingKey/index.js b/packages/server/src/schemas/streamingKey/index.js new file mode 100644 index 00000000..f63af956 --- /dev/null +++ b/packages/server/src/schemas/streamingKey/index.js @@ -0,0 +1,10 @@ +export default { + user_id: { + type: String, + required: true, + }, + key: { + type: String, + required: true, + } +} \ No newline at end of file From 39ecbf3ee3d6410e512944f61233f4684644247b Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:38:44 +0200 Subject: [PATCH 09/43] added `StreamingController` with basic methods --- .../controllers/StreamingController/index.js | 78 +++++++++++++++++++ packages/server/src/controllers/index.js | 2 + 2 files changed, 80 insertions(+) create mode 100644 packages/server/src/controllers/StreamingController/index.js diff --git a/packages/server/src/controllers/StreamingController/index.js b/packages/server/src/controllers/StreamingController/index.js new file mode 100644 index 00000000..82e8f27d --- /dev/null +++ b/packages/server/src/controllers/StreamingController/index.js @@ -0,0 +1,78 @@ +import { Controller } from "linebridge/dist/server" +import { User, StreamingKey } from "../../models" +import { nanoid } from "nanoid" + +import axios from "axios" + +const streamingServerAddress = "media.ragestudio.net" + +export default class StreamingController extends Controller { + static useMiddlewares = ["withAuthentication"] + + methods = { + genereteKey: async (user_id) => { + // this will generate a new key for the user + // if the user already has a key, it will be regenerated + + const streamingKey = new StreamingKey({ + user_id, + key: nanoid() + }) + + await streamingKey.save() + + return streamingKey + } + } + + get = { + "/streams": async (req, res) => { + // TODO: meanwhile linebridge remote linkers are in development we gonna use this methods to fetch + const result = await axios.get(`http://${streamingServerAddress}/streams`).catch((error) => { + res.status(500).json({ + error: `Failed to fetch streams from [${streamingServerAddress}]: ${error.message}` + }) + return false + }) + + if (result) { + console.log(result) + } + }, + "/target_streaming_server": async (req, res) => { + // TODO: resolve an available server + // for now we just return the only one should be online + return res.json({ + protocol: "rtmp", + port: "1935", + space: "live", + address: streamingServerAddress, + }) + }, + "/streaming_key": async (req, res) => { + let streamingKey = await StreamingKey.findOne({ + userId: req.user._id.toString() + }) + + if (!streamingKey) { + const newKey = await this.methods.genereteKey(req.user._id.toString()).catch(err => { + res.status(500).json({ + error: `Cannot generate a new key: ${err.message}`, + }) + + return false + }) + + if (!newKey) { + return false + } + + return res.json(newKey) + } else { + return res.json(streamingKey) + } + }, + } + + +} \ No newline at end of file diff --git a/packages/server/src/controllers/index.js b/packages/server/src/controllers/index.js index a19b7f97..adf6affe 100644 --- a/packages/server/src/controllers/index.js +++ b/packages/server/src/controllers/index.js @@ -5,6 +5,7 @@ import { default as UserController } from "./UserController" import { default as FilesController } from "./FilesController" import { default as PublicController } from "./PublicController" import { default as PostsController } from "./PostsController" +import { default as StreamingController } from "./StreamingController" export default [ PostsController, @@ -14,4 +15,5 @@ export default [ SessionController, UserController, FilesController, + StreamingController, ] \ No newline at end of file From d295ddda53bd958c005a649ee708db6c2abddee2 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:39:05 +0200 Subject: [PATCH 10/43] fix `streamingKey` schema --- packages/streaming-server/src/models/index.js | 6 ++---- packages/streaming-server/src/schemas/StreamingKey/index.js | 2 +- packages/streaming-server/src/schemas/index.js | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/streaming-server/src/models/index.js b/packages/streaming-server/src/models/index.js index fd844dec..8945412e 100644 --- a/packages/streaming-server/src/models/index.js +++ b/packages/streaming-server/src/models/index.js @@ -4,6 +4,7 @@ function getSchemas() { const obj = Object() const _schemas = require("../schemas") + Object.keys(_schemas).forEach((key) => { obj[key] = Schema(_schemas[key]) }) @@ -13,8 +14,5 @@ function getSchemas() { const schemas = getSchemas() -// server -export const Config = mongoose.model("Config", schemas.Config, "config") - // streaming -export const StreamingKeys = mongoose.model("StreamingKeys", schemas.StreamingKey, "StreamingKeys") \ No newline at end of file +export const StreamingKey = mongoose.model("StreamingKey", schemas.streamingKey, "streamingKeys") \ No newline at end of file diff --git a/packages/streaming-server/src/schemas/StreamingKey/index.js b/packages/streaming-server/src/schemas/StreamingKey/index.js index 86a50b1a..f63af956 100644 --- a/packages/streaming-server/src/schemas/StreamingKey/index.js +++ b/packages/streaming-server/src/schemas/StreamingKey/index.js @@ -1,5 +1,5 @@ export default { - userId: { + user_id: { type: String, required: true, }, diff --git a/packages/streaming-server/src/schemas/index.js b/packages/streaming-server/src/schemas/index.js index 63fcdb63..1f405d09 100644 --- a/packages/streaming-server/src/schemas/index.js +++ b/packages/streaming-server/src/schemas/index.js @@ -1 +1 @@ -export { default as StreamingKey } from "./StreamingKey" \ No newline at end of file +export { default as streamingKey } from "./streamingKey" \ No newline at end of file From 10eb64692fb6c8f1cbd14127a27882bd2aeb9201 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:39:30 +0200 Subject: [PATCH 11/43] reject client session when session is removed or ended --- packages/streaming-server/src/managers/SessionsManager/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/streaming-server/src/managers/SessionsManager/index.js b/packages/streaming-server/src/managers/SessionsManager/index.js index 554c1e48..abe430f2 100644 --- a/packages/streaming-server/src/managers/SessionsManager/index.js +++ b/packages/streaming-server/src/managers/SessionsManager/index.js @@ -12,6 +12,8 @@ export default class SessionsManager { } removeSession = (id) => { + this.sessions[id].reject() + delete this.sessions[id] } } \ No newline at end of file From bbd0c1d5bb278c504f2882a535f88bf42a1b1d91 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:39:41 +0200 Subject: [PATCH 12/43] added `@ffmpeg-installer/ffmpeg` dep --- packages/streaming-server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/streaming-server/package.json b/packages/streaming-server/package.json index 66f76642..4da32e76 100644 --- a/packages/streaming-server/package.json +++ b/packages/streaming-server/package.json @@ -7,6 +7,7 @@ "dev": "nodemon --ignore dist/ --exec corenode-node ./src/index.js" }, "dependencies": { + "@ffmpeg-installer/ffmpeg": "^1.1.0", "linebridge": "^0.11.13", "mongoose": "^6.3.3", "node-media-server": "^2.3.9" From 1a0a53445e4d073bc84bc1bd423caecff16b1a26 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:40:00 +0200 Subject: [PATCH 13/43] added basic streaming server classes --- packages/streaming-server/src/index.js | 108 ++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/packages/streaming-server/src/index.js b/packages/streaming-server/src/index.js index 31233052..f1ac72b7 100644 --- a/packages/streaming-server/src/index.js +++ b/packages/streaming-server/src/index.js @@ -1 +1,107 @@ -import { Server } from "linebridge/dist/server" \ No newline at end of file +const ffmpeg = require("@ffmpeg-installer/ffmpeg") + +import { Server } from "linebridge/dist/server" +import MediaServer from "node-media-server" +import { SessionsManager, DbManager } from "./managers" +import { getStreamingKeyFromStreamPath } from "./lib" + +import { StreamingKey } from "./models" + +const HTTPServerConfig = { + port: 3002, +} + +const MediaServerConfig = { + rtmp: { + port: 1935, + chunk_size: 60000, + gop_cache: true, + ping: 30, + ping_timeout: 60 + }, + // trans: { + // ffmpeg: ffmpeg.path, + // tasks: [ + // { + // app: "live", + // hls: true, + // hlsFlags: "[hls_time=2:hls_list_size=3:hls_flags=delete_segments]", + // dash: true, + // dashFlags: "[f=dash:window_size=3:extra_window_size=5]" + // } + // ] + // } +} + +class StreamingServer { + IHTTPServer = new Server(HTTPServerConfig) + + IMediaServer = new MediaServer(MediaServerConfig) + + Db = new DbManager() + + Sessions = new SessionsManager() + + PublicStreamings = [] + + constructor() { + this.registerMediaServerEvents() + + // fire initization + this.initialize() + } + + registerMediaServerEvents = () => { + Object.keys(this.mediaServerEvents).forEach(eventName => { + this.IMediaServer.on(eventName, this.mediaServerEvents[eventName]) + }) + } + + mediaServerEvents = { + preConnect: async (id, args) => { + // this event is fired after client is connected + // but session is not created yet & not ready to publish + + // get session + const session = this.IMediaServer.getSession(id) + + // create a userspaced session for the client with containing session + this.Sessions.newSession(id, session) + }, + postConnect: async (id, args) => { + // this event is fired after client is connected and session is created + // and is already published + }, + doneConnect: async (id, args) => { + // this event is fired when client has ended the connection + + // stop the session + this.Sessions.removeSession(id) + }, + prePublish: async (id, StreamPath, args) => { + // this event is fired before client is published + // here must be some validation (as key validation) + const streamingKey = getStreamingKeyFromStreamPath(StreamPath) + + const streamingUserspace = await StreamingKey.findOne({ + key: streamingKey + }) + + console.log(streamingUserspace) + + if (!streamingUserspace) { + this.Sessions.removeSession(id) + return false + } + + PublicStreamings.push(id) + } + } + + initialize = async () => { + await this.Db.connect() + this.IMediaServer.run() + } +} + +new StreamingServer() \ No newline at end of file From 0ae6cbc0d9d6919e8cb6d6ac798a6f88195d8000 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:40:16 +0200 Subject: [PATCH 14/43] added streaming routes --- packages/app/constants/routes.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/app/constants/routes.json b/packages/app/constants/routes.json index 8232575e..00f8233e 100644 --- a/packages/app/constants/routes.json +++ b/packages/app/constants/routes.json @@ -18,5 +18,15 @@ "id": "marketplace", "title": "Marketplace", "icon": "Package" + }, + { + "id": "streams", + "title": "Streams", + "icon": "Tv" + }, + { + "id": "streaming_control", + "title": "Streaming Control", + "icon": "Video" } ] \ No newline at end of file From bb2cd23c4bebdf2892d0b0028a311a4d27d51ae3 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:53:15 +0200 Subject: [PATCH 15/43] fetch data from media server api --- .../src/controllers/StreamingController/index.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/server/src/controllers/StreamingController/index.js b/packages/server/src/controllers/StreamingController/index.js index 82e8f27d..cd7cfdc7 100644 --- a/packages/server/src/controllers/StreamingController/index.js +++ b/packages/server/src/controllers/StreamingController/index.js @@ -4,7 +4,10 @@ import { nanoid } from "nanoid" import axios from "axios" -const streamingServerAddress = "media.ragestudio.net" +const streamingServerAddress = process.env.mediaServerAddress ?? "media.ragestudio.net" +const streamingServerAPIPort = process.env.mediaServerAPIPort ?? 3002 +const streamingServerAPIProtocol = process.env.mediaServerAPIProtocol ?? "http" +const streamingServerAPIUri = `${streamingServerAPIProtocol}://${streamingServerAddress}:${streamingServerAPIPort}` export default class StreamingController extends Controller { static useMiddlewares = ["withAuthentication"] @@ -28,15 +31,15 @@ export default class StreamingController extends Controller { get = { "/streams": async (req, res) => { // TODO: meanwhile linebridge remote linkers are in development we gonna use this methods to fetch - const result = await axios.get(`http://${streamingServerAddress}/streams`).catch((error) => { + const { data } = await axios.get(`${streamingServerAPIUri}/streams`).catch((error) => { res.status(500).json({ error: `Failed to fetch streams from [${streamingServerAddress}]: ${error.message}` }) return false }) - - if (result) { - console.log(result) + + if (data) { + return res.json(data) } }, "/target_streaming_server": async (req, res) => { @@ -73,6 +76,4 @@ export default class StreamingController extends Controller { } }, } - - } \ No newline at end of file From ff34d2900b4e8a59e7f80cd784161b55d4810a0c Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:53:30 +0200 Subject: [PATCH 16/43] added `publishStream` & `unpublishStream` methods --- .../src/managers/SessionsManager/index.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/streaming-server/src/managers/SessionsManager/index.js b/packages/streaming-server/src/managers/SessionsManager/index.js index abe430f2..60e4360a 100644 --- a/packages/streaming-server/src/managers/SessionsManager/index.js +++ b/packages/streaming-server/src/managers/SessionsManager/index.js @@ -1,6 +1,7 @@ export default class SessionsManager { constructor() { this.sessions = {} + this.publicStreams = [] } newSession = (id, session) => { @@ -13,7 +14,19 @@ export default class SessionsManager { removeSession = (id) => { this.sessions[id].reject() - + delete this.sessions[id] } + + publishStream = (payload) => { + if (typeof payload !== "object") { + throw new Error("Payload must be an object") + } + + this.publicStreams.push(payload) + } + + unpublishStream = (id) => { + this.publicStreams = this.publicStreams.filter(stream => stream.id !== id) + } } \ No newline at end of file From 27972f3f5fc70180c6b6d6c0996600964fa20b40 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 10:53:43 +0200 Subject: [PATCH 17/43] initialize http server --- packages/streaming-server/src/index.js | 43 +++++++++++++++++++------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/packages/streaming-server/src/index.js b/packages/streaming-server/src/index.js index f1ac72b7..7bf93d20 100644 --- a/packages/streaming-server/src/index.js +++ b/packages/streaming-server/src/index.js @@ -37,26 +37,43 @@ class StreamingServer { IHTTPServer = new Server(HTTPServerConfig) IMediaServer = new MediaServer(MediaServerConfig) - - Db = new DbManager() - - Sessions = new SessionsManager() - PublicStreamings = [] + Db = new DbManager() + + Sessions = new SessionsManager() constructor() { this.registerMediaServerEvents() + this.registerHTTPServerEndpoints() // fire initization this.initialize() } registerMediaServerEvents = () => { - Object.keys(this.mediaServerEvents).forEach(eventName => { + Object.keys(this.mediaServerEvents).forEach((eventName) => { this.IMediaServer.on(eventName, this.mediaServerEvents[eventName]) }) } + registerHTTPServerEndpoints = () => { + Object.keys(this.httpServerEndpoints).forEach((route) => { + this.IHTTPServer.registerHTTPEndpoint({ + route: route, + ...this.httpServerEndpoints[route] + }) + }) + } + + httpServerEndpoints = { + "/streams": { + method: "get", + fn: async (req, res) => { + return res.json(this.Sessions.publicStreams) + } + } + } + mediaServerEvents = { preConnect: async (id, args) => { // this event is fired after client is connected @@ -64,7 +81,7 @@ class StreamingServer { // get session const session = this.IMediaServer.getSession(id) - + // create a userspaced session for the client with containing session this.Sessions.newSession(id, session) }, @@ -74,9 +91,11 @@ class StreamingServer { }, doneConnect: async (id, args) => { // this event is fired when client has ended the connection - + // stop the session this.Sessions.removeSession(id) + + this.Sessions.unpublishStream(id) }, prePublish: async (id, StreamPath, args) => { // this event is fired before client is published @@ -87,20 +106,22 @@ class StreamingServer { key: streamingKey }) - console.log(streamingUserspace) - if (!streamingUserspace) { this.Sessions.removeSession(id) return false } - PublicStreamings.push(id) + this.Sessions.publishStream({ + id, + user_id: streamingUserspace.user_id, + }) } } initialize = async () => { await this.Db.connect() this.IMediaServer.run() + this.IHTTPServer.initialize() } } From a86918d3b947a4f23060f6baa681e4420e3139bb Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 11:05:52 +0200 Subject: [PATCH 18/43] fix addresses --- .../src/controllers/StreamingController/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/server/src/controllers/StreamingController/index.js b/packages/server/src/controllers/StreamingController/index.js index cd7cfdc7..ce887438 100644 --- a/packages/server/src/controllers/StreamingController/index.js +++ b/packages/server/src/controllers/StreamingController/index.js @@ -4,10 +4,11 @@ import { nanoid } from "nanoid" import axios from "axios" -const streamingServerAddress = process.env.mediaServerAddress ?? "media.ragestudio.net" -const streamingServerAPIPort = process.env.mediaServerAPIPort ?? 3002 -const streamingServerAPIProtocol = process.env.mediaServerAPIProtocol ?? "http" -const streamingServerAPIUri = `${streamingServerAPIProtocol}://${streamingServerAddress}:${streamingServerAPIPort}` +const streamingMediaServer = process.env.streamingMediaServer ?? "media.ragestudio.net" +const streamingServerAPIAddress = process.env.streamingServerAPIAddress ?? "media.ragestudio.net" +const streamingServerAPIPort = process.env.streamingServerAPIPort ?? 3002 +const streamingServerAPIProtocol = process.env.streamingServerAPIProtocol ?? "http" +const streamingServerAPIUri = `${streamingServerAPIProtocol}://${streamingServerAPIAddress}:${streamingServerAPIPort}` export default class StreamingController extends Controller { static useMiddlewares = ["withAuthentication"] @@ -33,7 +34,7 @@ export default class StreamingController extends Controller { // TODO: meanwhile linebridge remote linkers are in development we gonna use this methods to fetch const { data } = await axios.get(`${streamingServerAPIUri}/streams`).catch((error) => { res.status(500).json({ - error: `Failed to fetch streams from [${streamingServerAddress}]: ${error.message}` + error: `Failed to fetch streams from [${streamingServerAPIAddress}]: ${error.message}` }) return false }) @@ -49,7 +50,7 @@ export default class StreamingController extends Controller { protocol: "rtmp", port: "1935", space: "live", - address: streamingServerAddress, + address: streamingMediaServer, }) }, "/streaming_key": async (req, res) => { From 182040d37a563f5609d86f0c15ecd1b84b05202f Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 14:58:09 +0200 Subject: [PATCH 19/43] stream pipe with `streamingUserspace` username resolver --- packages/streaming-server/src/index.js | 186 ++++++++++++++++++++++--- 1 file changed, 163 insertions(+), 23 deletions(-) diff --git a/packages/streaming-server/src/index.js b/packages/streaming-server/src/index.js index 7bf93d20..598fc007 100644 --- a/packages/streaming-server/src/index.js +++ b/packages/streaming-server/src/index.js @@ -5,6 +5,9 @@ import MediaServer from "node-media-server" import { SessionsManager, DbManager } from "./managers" import { getStreamingKeyFromStreamPath } from "./lib" +import axios from "axios" +import stream from "stream" + import { StreamingKey } from "./models" const HTTPServerConfig = { @@ -19,6 +22,10 @@ const MediaServerConfig = { ping: 30, ping_timeout: 60 }, + http: { + port: 1000, + allow_origin: '*' + }, // trans: { // ffmpeg: ffmpeg.path, // tasks: [ @@ -26,8 +33,6 @@ const MediaServerConfig = { // app: "live", // hls: true, // hlsFlags: "[hls_time=2:hls_list_size=3:hls_flags=delete_segments]", - // dash: true, - // dashFlags: "[f=dash:window_size=3:extra_window_size=5]" // } // ] // } @@ -66,40 +71,162 @@ class StreamingServer { } httpServerEndpoints = { + "/events/on-publish": { + method: "post", + fn: async (req, res) => { + req.body = Buffer.from(req.body).toString() + + // decode url-encoded body + req.body = req.body.split("&").reduce((acc, cur) => { + const [key, value] = cur.split("=") + acc[key] = value + + return acc + }, {}) + + const streamingKey = req.body.name + + const streamingUserspace = await StreamingKey.findOne({ + key: streamingKey + }) + + if (!streamingUserspace) { + return res.status(403).send("Invalid stream key") + } + + this.Sessions.publishStream({ + user_id: streamingUserspace.user_id, + stream_key: streamingKey + }) + + return res.send("OK") + } + }, + "/events/on-publish-done": { + method: "post", + fn: async (req, res) => { + req.body = Buffer.from(req.body).toString() + + // decode url-encoded body + req.body = req.body.split("&").reduce((acc, cur) => { + const [key, value] = cur.split("=") + acc[key] = value + + return acc + }, {}) + + const streamingKey = req.body.name + + const streamingUserspace = await StreamingKey.findOne({ + key: streamingKey + }) + + if (!streamingUserspace) { + return res.status(403).send("Invalid stream key") + } + + this.Sessions.unpublishStream(streamingKey) + + return res.send("OK") + } + }, "/streams": { method: "get", fn: async (req, res) => { - return res.json(this.Sessions.publicStreams) + if (req.query?.user_id) { + const streams = await this.Sessions.getStreamsByUserId(req.query.user_id) + + return res.json(streams) + } + + return res.json(this.Sessions.getPublicStreams()) + } + }, + "/stream/:mode/:username": { + method: "get", + fn: async (req, res) => { + const { username, mode = "flv" } = req.params + + const streamSession = this.Sessions.publicStreams.find(stream => { + if (stream.username === username) { + return stream + } + }) + + if (!streamSession) { + return res.status(404).json({ + error: "Stream not found" + }) + } + + const streamKey = streamSession.stream_key + + switch (mode) { + case "flv": { + const streamingFLVUri = `http://localhost:${MediaServerConfig.http.port}/live/${streamKey}.flv` + + // create a stream pipe response using media server api with axios + const request = await axios.get(streamingFLVUri, { + responseType: "stream" + }) + + // create a buffer stream from the request + const bufferStream = request.data.pipe(new stream.PassThrough()) + + // set header for stream response + res.setHeader("Content-Type", "video/x-flv") + + // pipe the buffer stream to the response + bufferStream.on("data", (chunk) => { + res.write(chunk) + }) + + break; + } + + case "hls": { + const streamingHLSUri = `http://localhost:${MediaServerConfig.http.port}/live/${streamKey}.m3u8` + + // create a stream pipe response using media server api with axios + const request = await axios.get(streamingHLSUri, { + responseType: "stream" + }) + + // create a buffer stream from the request + const bufferStream = request.data.pipe(new stream.PassThrough()) + + // set header for stream response + res.setHeader("Content-Type", "application/x-mpegURL") + + // pipe the buffer stream to the response + bufferStream.on("data", (chunk) => { + res.write(chunk) + }) + + break; + } + + default: { + return res.status(400).json({ + error: "Stream mode not supported" + }) + } + } } } } mediaServerEvents = { - preConnect: async (id, args) => { - // this event is fired after client is connected - // but session is not created yet & not ready to publish + prePublish: async (id, StreamPath, args) => { + // this event is fired before client is published + // here must be some validation (as key validation) // get session const session = this.IMediaServer.getSession(id) // create a userspaced session for the client with containing session this.Sessions.newSession(id, session) - }, - postConnect: async (id, args) => { - // this event is fired after client is connected and session is created - // and is already published - }, - doneConnect: async (id, args) => { - // this event is fired when client has ended the connection - // stop the session - this.Sessions.removeSession(id) - - this.Sessions.unpublishStream(id) - }, - prePublish: async (id, StreamPath, args) => { - // this event is fired before client is published - // here must be some validation (as key validation) const streamingKey = getStreamingKeyFromStreamPath(StreamPath) const streamingUserspace = await StreamingKey.findOne({ @@ -114,14 +241,27 @@ class StreamingServer { this.Sessions.publishStream({ id, user_id: streamingUserspace.user_id, + username: streamingUserspace.username, + stream_key: streamingKey }) + }, + donePublish: async (id, StreamPath, args) => { + // this event is fired when client has ended the connection + + // stop the session + this.Sessions.removeSession(id) + + const streamingKey = getStreamingKeyFromStreamPath(StreamPath) + + this.Sessions.unpublishStream(streamingKey) } } initialize = async () => { await this.Db.connect() - this.IMediaServer.run() - this.IHTTPServer.initialize() + + await this.IHTTPServer.initialize() + await this.IMediaServer.run() } } From 86d2bce01f6dd6167e4297a54752bed9bad57f4f Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 14:58:33 +0200 Subject: [PATCH 20/43] update `streamingKey` schema --- packages/server/src/schemas/streamingKey/index.js | 4 ++++ packages/streaming-server/src/schemas/StreamingKey/index.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/server/src/schemas/streamingKey/index.js b/packages/server/src/schemas/streamingKey/index.js index f63af956..ebb47e30 100644 --- a/packages/server/src/schemas/streamingKey/index.js +++ b/packages/server/src/schemas/streamingKey/index.js @@ -1,4 +1,8 @@ export default { + username: { + type: String, + required: true, + }, user_id: { type: String, required: true, diff --git a/packages/streaming-server/src/schemas/StreamingKey/index.js b/packages/streaming-server/src/schemas/StreamingKey/index.js index f63af956..ebb47e30 100644 --- a/packages/streaming-server/src/schemas/StreamingKey/index.js +++ b/packages/streaming-server/src/schemas/StreamingKey/index.js @@ -1,4 +1,8 @@ export default { + username: { + type: String, + required: true, + }, user_id: { type: String, required: true, From 2f717a179ec53c37b959000f72c06ba6077fa68e Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 14:58:54 +0200 Subject: [PATCH 21/43] generate keys with username --- .../controllers/StreamingController/index.js | 38 ++++++++++++++++++- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/server/src/controllers/StreamingController/index.js b/packages/server/src/controllers/StreamingController/index.js index ce887438..7612a402 100644 --- a/packages/server/src/controllers/StreamingController/index.js +++ b/packages/server/src/controllers/StreamingController/index.js @@ -18,8 +18,12 @@ export default class StreamingController extends Controller { // this will generate a new key for the user // if the user already has a key, it will be regenerated + // get username from user_id + const userData = await User.findOne({ user_id: user_id }) + const streamingKey = new StreamingKey({ user_id, + username: userData.username, key: nanoid() }) @@ -55,7 +59,7 @@ export default class StreamingController extends Controller { }, "/streaming_key": async (req, res) => { let streamingKey = await StreamingKey.findOne({ - userId: req.user._id.toString() + user_id: req.user._id.toString() }) if (!streamingKey) { @@ -75,6 +79,36 @@ export default class StreamingController extends Controller { } else { return res.json(streamingKey) } - }, + } + } + + post = { + "/regenerate_streaming_key": async (req, res) => { + // check if the user already has a key + let streamingKey = await StreamingKey.findOne({ + user_id: req.user._id.toString() + }) + + // if exists, delete it + + if (streamingKey) { + await streamingKey.remove() + } + + // generate a new key + const newKey = await this.methods.genereteKey(req.user._id.toString()).catch(err => { + res.status(500).json({ + error: `Cannot generate a new key: ${err.message}`, + }) + + return false + }) + + if (!newKey) { + return false + } + + return res.json(newKey) + } } } \ No newline at end of file From 5cb5f85b45ad24410c8362bb36bf58592eae02d3 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 14:59:14 +0200 Subject: [PATCH 22/43] get public streams methods --- .../src/managers/SessionsManager/index.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/streaming-server/src/managers/SessionsManager/index.js b/packages/streaming-server/src/managers/SessionsManager/index.js index 60e4360a..82526867 100644 --- a/packages/streaming-server/src/managers/SessionsManager/index.js +++ b/packages/streaming-server/src/managers/SessionsManager/index.js @@ -1,3 +1,5 @@ +import lodash from "lodash" + export default class SessionsManager { constructor() { this.sessions = {} @@ -26,7 +28,18 @@ export default class SessionsManager { this.publicStreams.push(payload) } - unpublishStream = (id) => { - this.publicStreams = this.publicStreams.filter(stream => stream.id !== id) + unpublishStream = (stream_key) => { + this.publicStreams = this.publicStreams.filter(stream => stream.stream_key !== stream_key) + } + + getPublicStreams = () => { + // return this.publicStreams but without stream_key property + return lodash.map(this.publicStreams, stream => { + return lodash.omit(stream, "stream_key") + }) + } + + getStreamsByUserId = (user_id) => { + return lodash.filter(this.publicStreams, stream => stream.user_id === user_id) } } \ No newline at end of file From 618f951aa23ea9099ec1f69d01c28dcc56b8c228 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 15:00:52 +0200 Subject: [PATCH 23/43] fetch list of streams --- packages/app/src/pages/streams/index.jsx | 54 ++++++++++++++++++----- packages/app/src/pages/streams/index.less | 49 ++++++++++++++++++++ 2 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 packages/app/src/pages/streams/index.less diff --git a/packages/app/src/pages/streams/index.jsx b/packages/app/src/pages/streams/index.jsx index 59110796..1aafb96b 100644 --- a/packages/app/src/pages/streams/index.jsx +++ b/packages/app/src/pages/streams/index.jsx @@ -1,8 +1,9 @@ -import React from 'react' -import axios from "axios" +import React from "react" import * as antd from "antd" import { SelectableList, ActionsBar } from "components" +import "./index.less" + export default class Streams extends React.Component { state = { list: [], @@ -15,38 +16,69 @@ export default class Streams extends React.Component { } updateStreamsList = async () => { - const streams = await this.api.get.streams().catch(error => { + let streams = await this.api.get.streams().catch(error => { console.error(error) antd.message.error(error) - + return false }) + if (streams && Array.isArray(streams)) { + // resolve user_id with user basic data + streams = streams.map(async (stream) => { + const userData = await this.api.get.user(undefined, { user_id: stream.user_id }).catch((error) => { + console.error(error) + antd.message.error(error) + + return false + }) + + if (userData) { + stream.userData = userData + } + + return stream + }) + + streams = await Promise.all(streams) + } + this.setState({ list: streams }) } - onClickItem = (item) => { window.app.setLocation(`/streams/viewer?key=${item}`) } renderListItem = (stream) => { - stream.StreamPath = stream.StreamPath.replace(/^\/live\//, "") - - return
this.onClickItem(stream.StreamPath)}> -

@{stream.StreamPath} #{stream.id}

+ return
this.onClickItem(stream.username)} + className="streaming-item" + > +
+ {stream.userData.username} +
+
+
+

@{stream.userData.username}

+ + #{stream.id} + +
+
} render() { - return
+ return
Refresh
- Date: Thu, 12 May 2022 16:24:46 +0200 Subject: [PATCH 24/43] extend `get/streams` with internal api details --- packages/streaming-server/src/index.js | 59 ++++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/streaming-server/src/index.js b/packages/streaming-server/src/index.js index 598fc007..48030268 100644 --- a/packages/streaming-server/src/index.js +++ b/packages/streaming-server/src/index.js @@ -1,4 +1,5 @@ const ffmpeg = require("@ffmpeg-installer/ffmpeg") +import lodash from "lodash" import { Server } from "linebridge/dist/server" import MediaServer from "node-media-server" @@ -26,18 +27,20 @@ const MediaServerConfig = { port: 1000, allow_origin: '*' }, - // trans: { - // ffmpeg: ffmpeg.path, - // tasks: [ - // { - // app: "live", - // hls: true, - // hlsFlags: "[hls_time=2:hls_list_size=3:hls_flags=delete_segments]", - // } - // ] - // } + trans: { + ffmpeg: ffmpeg.path, + tasks: [ + { + app: "live", + hls: true, + hlsFlags: "[hls_time=2:hls_list_size=3:hls_flags=delete_segments]", + } + ] + } } +const internalMediaServerURI = `http://127.0.0.1:${MediaServerConfig.http.port}` + class StreamingServer { IHTTPServer = new Server(HTTPServerConfig) @@ -139,7 +142,37 @@ class StreamingServer { return res.json(streams) } - return res.json(this.Sessions.getPublicStreams()) + let streams = this.Sessions.getPublicStreams() + + // retrieve streams details from internal media server api + let streamsListDetails = await axios.get(`${internalMediaServerURI}/api/streams`) + + streamsListDetails = streamsListDetails.data.live ?? {} + + // return only publisher details + streamsListDetails = Object.keys(streamsListDetails).map((streamKey) => { + return { + // filter unwanted properties + ...lodash.omit(streamsListDetails[streamKey].publisher, ["stream", "ip"]) + } + }) + + // reduce as an object + streamsListDetails = streamsListDetails.reduce((acc, cur) => { + acc[cur.clientId] = cur + + return acc + }, {}) + + // merge with public streams + streams = streams.map((stream) => { + return { + ...stream, + ...streamsListDetails[stream.id] + } + }) + + return res.json(streams) } }, "/stream/:mode/:username": { @@ -163,7 +196,7 @@ class StreamingServer { switch (mode) { case "flv": { - const streamingFLVUri = `http://localhost:${MediaServerConfig.http.port}/live/${streamKey}.flv` + const streamingFLVUri = `${internalMediaServerURI}/live/${streamKey}.flv` // create a stream pipe response using media server api with axios const request = await axios.get(streamingFLVUri, { @@ -185,7 +218,7 @@ class StreamingServer { } case "hls": { - const streamingHLSUri = `http://localhost:${MediaServerConfig.http.port}/live/${streamKey}.m3u8` + const streamingHLSUri = `${internalMediaServerURI}/live/${streamKey}.m3u8` // create a stream pipe response using media server api with axios const request = await axios.get(streamingHLSUri, { From c4de104b0a43515bec98fd696ba0e3ecde1f3d45 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 16:24:53 +0200 Subject: [PATCH 25/43] added lodash lib --- packages/streaming-server/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/streaming-server/package.json b/packages/streaming-server/package.json index 4da32e76..8ceadf34 100644 --- a/packages/streaming-server/package.json +++ b/packages/streaming-server/package.json @@ -9,6 +9,7 @@ "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", "linebridge": "^0.11.13", + "lodash": "^4.17.21", "mongoose": "^6.3.3", "node-media-server": "^2.3.9" }, From d3169737b1a124409811acdf9c735a22d1ea5fba Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 16:51:34 +0200 Subject: [PATCH 26/43] added `layout_page` modes --- packages/app/src/theme/index.less | 152 ++++++++++++++++-------------- 1 file changed, 79 insertions(+), 73 deletions(-) diff --git a/packages/app/src/theme/index.less b/packages/app/src/theme/index.less index 7a0b40b9..1a45d350 100644 --- a/packages/app/src/theme/index.less +++ b/packages/app/src/theme/index.less @@ -5,29 +5,29 @@ ::-webkit-scrollbar { display: none; - width : 0; - height : 0; + width: 0; + height: 0; z-index: 0; } ::-webkit-scrollbar-thumb { position: absolute; - z-index : 200; + z-index: 200; - height : 6px; - margin : 5px 10px 5px 5px; + height: 6px; + margin: 5px 10px 5px 5px; transition: all 200ms ease-in-out; - border : 4px solid rgba(0, 0, 0, 0); + border: 4px solid rgba(0, 0, 0, 0); background-color: rgba(0, 0, 0, 0.15); - background-clip : padding-box; + background-clip: padding-box; -webkit-border-radius: 7px; } ::-webkit-scrollbar-button { - width : 0; - height : 0; + width: 0; + height: 0; display: none; } @@ -36,33 +36,33 @@ } html { - overflow : hidden; - height : 100%; + overflow: hidden; + height: 100%; -webkit-overflow-scrolling: touch; background-color: var(--background-color-primary) !important; svg { - margin-right : 10px; + margin-right: 10px; vertical-align: -0.125em; } } body { - overflow : hidden; + overflow: hidden; -webkit-overflow-scrolling: touch; - -webkit-app-region : no-drag; + -webkit-app-region: no-drag; height: 100%; - user-select : none; + user-select: none; --webkit-user-select: none; scroll-behavior: smooth; - text-rendering : optimizeLegibility !important; + text-rendering: optimizeLegibility !important; background-color: var(--background-color-primary) !important; - font-family : var(--fontFamily); + font-family: var(--fontFamily); } #root { @@ -71,7 +71,7 @@ body { position: fixed; overflow: hidden; - width : 100%; + width: 100%; height: 100%; background-color: var(--background-color-primary) !important; @@ -79,11 +79,11 @@ body { #nprogress { position: absolute; - top : 0; - width : 100vw; + top: 0; + width: 100vw; .bar { - height : 2px; + height: 2px; background: #48acf0; } } @@ -91,26 +91,26 @@ body { .ant-layout, .content_layout, .app_layout { - background : var(--background-color-primary) !important; + background: var(--background-color-primary) !important; background-color: var(--background-color-primary) !important; - position : relative; + position: relative; -webkit-overflow-scrolling: touch; - width : 100%; - height : 100%; + width: 100%; + height: 100%; max-height: 100vh; - overflow : hidden; + overflow: hidden; transition: all 150ms ease-in-out; ::-webkit-scrollbar { - display : block; + display: block; position: absolute; - width : 14px; - height : 18px; - z-index : 200; + width: 14px; + height: 18px; + z-index: 200; transition: all 200ms ease-in-out; } @@ -119,15 +119,15 @@ body { ::-webkit-scrollbar { display: none !important; - width : 0; - height : 0; + width: 0; + height: 0; z-index: 0; } } } .layout_page { - position : relative; + position: relative; -webkit-overflow-scrolling: touch; height: 100%; @@ -136,12 +136,18 @@ body { overflow-x: hidden; overflow-y: overlay; + + transition: all 150ms ease-in-out; + + &.noMargin { + margin: 0; + } } @media (max-width: 768px) { .layout_page { padding: 10px; - margin : 0; + margin: 0; } h1, @@ -152,7 +158,7 @@ body { h6, span, p { - user-select : none; + user-select: none; -webkit-user-select: none; } @@ -163,17 +169,17 @@ body { .fade-transverse-active { transition: all 250ms; - height : fit-content; - width : 100%; + height: fit-content; + width: 100%; } .fade-transverse-enter { - opacity : 0; + opacity: 0; transform: translateX(-30px); } .fade-transverse-leave { - opacity : 0; + opacity: 0; transform: translateX(30px); } @@ -183,18 +189,18 @@ body { } .fade-scale-enter { - opacity : 0; + opacity: 0; transform: scale(1.2); } .fade-scale-leave { - opacity : 0; + opacity: 0; transform: scale(0.8); } .fade-opacity-active { transition: all 250ms; - opacity : 1; + opacity: 1; } .fade-opacity-leave { @@ -206,50 +212,50 @@ body { } .app_initialization { - width : 100vw; - height : 100vh; + width: 100vw; + height: 100vh; padding: 50px; - display : flex; - flex-direction : column; + display: flex; + flex-direction: column; justify-content: center; - align-items : center; + align-items: center; >div { - width : 100%; + width: 100%; height: fit-content; - display : flex; - flex-direction : column; + display: flex; + flex-direction: column; justify-content: center; - align-items : center; + align-items: center; margin-bottom: 50px; } } .app_crash_wrapper { - width : 100vw; - height : 100vh; - display : flex; - flex-direction : column; + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; justify-content: center; - align-items : center; + align-items: center; } // Fixments .ant-btn { - display : flex; - align-items : center; + display: flex; + align-items: center; justify-content: center; - user-select : none; + user-select: none; --webkit-user-select: none; } .ant-result-extra { - display : flex; - align-items : center; + display: flex; + align-items: center; justify-content: center; } @@ -268,7 +274,7 @@ body { } *:not(input):not(textarea) { - -webkit-user-select : none; + -webkit-user-select: none; /* disable selection/Copy of UIWebView */ -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */ @@ -278,35 +284,35 @@ body { overflow: hidden; //background-color: rgba(240, 242, 245, 0.8); - backdrop-filter : blur(10px); + backdrop-filter: blur(10px); --webkit-backdrop-filter: blur(10px); - width : 100%; - height : 100%; + width: 100%; + height: 100%; z-index: 1000; - display : flex; + display: flex; flex-direction: column; - align-items : center; + align-items: center; justify-content: center; } .splash_logo { - width : 100%; + width: 100%; height: 100%; - display : flex; + display: flex; flex-direction: column; - align-items : center; + align-items: center; justify-content: center; img { - width : fit-content; - max-width : 50%; + width: fit-content; + max-width: 50%; max-height: 50%; - filter : drop-shadow(14px 10px 10px rgba(128, 128, 128, 0.5)); + filter: drop-shadow(14px 10px 10px rgba(128, 128, 128, 0.5)); } } From 735b4dbbb438d384a67a9af2ea80053258495895 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 16:51:43 +0200 Subject: [PATCH 27/43] added `rxjs` dep --- packages/app/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/package.json b/packages/app/package.json index 2c689ccb..9413bab7 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -74,6 +74,7 @@ "react-router-config": "^5.1.1", "react-router-dom": "6.2.1", "react-virtualized": "^9.22.3", + "rxjs": "^7.5.5", "store": "^2.0.12", "styled-components": "^5.3.3", "vite-ssr": "0.15.0" From ac6e8c9129ea6b1a4d47a1f79734707b81b1ff75 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 16:51:59 +0200 Subject: [PATCH 28/43] added compact mode --- packages/app/src/layout/index.jsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/app/src/layout/index.jsx b/packages/app/src/layout/index.jsx index a3bd56ef..a83f7bac 100644 --- a/packages/app/src/layout/index.jsx +++ b/packages/app/src/layout/index.jsx @@ -12,7 +12,7 @@ const LayoutRenders = { mobile: (props) => { return - +
{props.children}
@@ -28,7 +28,7 @@ const LayoutRenders = {
- +
{props.children}
@@ -43,6 +43,7 @@ export default class Layout extends React.Component { state = { layoutType: "default", isOnTransition: false, + compactMode: false, } setLayout = (layout) => { @@ -62,6 +63,11 @@ export default class Layout extends React.Component { window.app.eventBus.on("transitionDone", () => { this.setState({ isOnTransition: false }) }) + window.app.eventBus.on("toogleCompactMode", (to) => { + this.setState({ + compactMode: to ?? !this.state.compactMode, + }) + }) if (window.app.settings.get("forceMobileMode") || window.app.isAppCapacitor() || Math.min(window.screen.width, window.screen.height) < 768 || navigator.userAgent.indexOf("Mobi") > -1) { window.isMobile = true @@ -85,6 +91,9 @@ export default class Layout extends React.Component { const layoutComponentProps = { ...this.props, ...this.state, + layoutPageModesClassnames: [{ + ["noMargin"]: this.state.compactMode, + }] } if (LayoutRenders[this.state.layoutType]) { From c33003b3c62ee7aadcdff50ad72df3ea5157e8c6 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 16:52:08 +0200 Subject: [PATCH 29/43] added `withEvent` method --- .../app/src/extensions/settings.extension.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/app/src/extensions/settings.extension.js b/packages/app/src/extensions/settings.extension.js index 97595e5c..ec845ea6 100644 --- a/packages/app/src/extensions/settings.extension.js +++ b/packages/app/src/extensions/settings.extension.js @@ -1,6 +1,7 @@ import { Extension } from "evite" import store from "store" import defaultSettings from "schemas/defaultSettings.json" +import { Observable } from "rxjs" export default class SettingsExtension extends Extension { constructor(app, main) { @@ -52,6 +53,23 @@ export default class SettingsExtension extends Extension { return this.settings[key] } + withEvent = (listenEvent, defaultValue) => { + let value = defaultValue ?? this.settings[key] ?? false + + const observable = new Observable((subscriber) => { + subscriber.next(value) + + window.app.eventBus.on(listenEvent, (to) => { + value = to + subscriber.next(value) + }) + }) + + return observable.subscribe((value) => { + return value + }) + } + window = { "settings": this } From ff50b5d4f383ea6fe0bb98925a7015437877f5d2 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 17:00:31 +0200 Subject: [PATCH 30/43] make border 0 on hidden mode --- packages/app/src/layout/header/index.less | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/app/src/layout/header/index.less b/packages/app/src/layout/header/index.less index aa967eb6..733f105e 100644 --- a/packages/app/src/layout/header/index.less +++ b/packages/app/src/layout/header/index.less @@ -1,20 +1,20 @@ @import "theme/index.less"; .app_header { - user-select : none; + user-select: none; --webkit-user-select: none; - display : flex; + display: flex; flex-direction: row; - align-items : center; - z-index : 100; + align-items: center; + z-index: 100; - height : @app_header_height !important; + height: @app_header_height !important; padding: 10px; transition: all ease-in-out 150ms; - background : var(--background-color-primary) !important; + background: var(--background-color-primary) !important; background-color: var(--background-color-primary) !important; border-bottom: 1px var(--border-color) solid; @@ -25,7 +25,8 @@ &.hidden { opacity: 0; - height : 0 !important; + height: 0 !important; padding: 0 !important; + border: 0 !important; } } \ No newline at end of file From 21bbb5c9df9fc57b16e33dd47304c7d48a8d5574 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 17:21:30 +0200 Subject: [PATCH 31/43] update darkMode setting --- packages/app/constants/settings/app.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/app/constants/settings/app.jsx b/packages/app/constants/settings/app.jsx index 9924605d..8945a54f 100644 --- a/packages/app/constants/settings/app.jsx +++ b/packages/app/constants/settings/app.jsx @@ -146,14 +146,17 @@ export default [ "experimental": true }, { + "experimental": true, "id": "darkMode", "storaged": true, "group": "aspect", "type": "Switch", "icon": "Moon", "title": "Dark mode", - "emitEvent": "darkMode", - "experimental": true + "emitEvent": "theme.applyVariant", + "emissionValueUpdate": (value) => { + return value ? "dark" : "light" + }, }, { "id": "primaryColor", From c5a020a8999d01b45344ad0660f6a97af61d475e Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 17:22:04 +0200 Subject: [PATCH 32/43] apply variant only storage variant from eventBus --- packages/app/src/extensions/theme.extension.jsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/packages/app/src/extensions/theme.extension.jsx b/packages/app/src/extensions/theme.extension.jsx index adab64ad..913dcce9 100644 --- a/packages/app/src/extensions/theme.extension.jsx +++ b/packages/app/src/extensions/theme.extension.jsx @@ -9,7 +9,6 @@ export default class ThemeExtension extends Extension { this.themeManifestStorageKey = "theme" this.modificationStorageKey = "themeModifications" - this.variantStorageKey = "themeVariation" this.theme = null @@ -19,12 +18,9 @@ export default class ThemeExtension extends Extension { initializers = [ async () => { - this.mainContext.eventBus.on("darkMode", (value) => { - if (value) { - this.applyVariant("dark") - } else { - this.applyVariant("light") - } + this.mainContext.eventBus.on("theme.applyVariant", (value) => { + this.applyVariant(value) + this.setVariant(value) }) this.mainContext.eventBus.on("modifyTheme", (value) => { this.update(value) @@ -95,11 +91,11 @@ export default class ThemeExtension extends Extension { } getStoragedVariant = () => { - return store.get(this.variantStorageKey) + return app.settings.get("themeVariant") } setVariant = (variationKey) => { - return store.set(this.variantStorageKey, variationKey) + return app.settings.set("themeVariant", variationKey) } setModifications = (modifications) => { @@ -142,7 +138,6 @@ export default class ThemeExtension extends Extension { if (values) { this.currentVariant = variant this.update(values) - this.setVariant(variant) } } From 9175befcfcc0ff0d27b66fad82631a605958fef8 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:02:39 +0200 Subject: [PATCH 33/43] format --- packages/app/src/theme/variations/dark.less | 37 +++++++++++++-------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/packages/app/src/theme/variations/dark.less b/packages/app/src/theme/variations/dark.less index fc07b936..bcdd3baf 100644 --- a/packages/app/src/theme/variations/dark.less +++ b/packages/app/src/theme/variations/dark.less @@ -2,11 +2,17 @@ div { color: var(--text-color); } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { color: var(--header-text-color); } -a, p { +a, +p { color: var(--text-color); } @@ -22,35 +28,40 @@ svg:not(.ant-tag *) { color: var(--svg-color); } -input, .ant-input-affix-wrapper, .ant-input { - color: var(--text-color)!important; +input, +.ant-input-affix-wrapper, +.ant-input { + color: var(--text-color) !important; background-color: var(--background-color-accent); } -input:disabled{ +input:disabled { background-color: var(--background_disabled); } // MODAL .ant-modal-content { - background-color: var(--background-color-accent)!important; + background-color: var(--background-color-accent) !important; } // TABLE tr { - background-color: var(--background-color-accent)!important; + background-color: var(--background-color-accent) !important; } -.ant-table, .ant-table-content, .ant-table-thead, .ant-table-cell { +.ant-table, +.ant-table-content, +.ant-table-thead, +.ant-table-cell { background-color: var(--background-color-accent); } -.ant-table-thead > tr > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { +.ant-table-thead>tr>th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { background-color: var(--background-color-contrast); } // MENU -.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { +.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected { background-color: var(--background-color-primary); } @@ -59,12 +70,12 @@ tr { background-color: var(--button-background-color); } -.ant-btn span{ - color: var(--button-text-color)!important; +.ant-btn span { + color: var(--button-text-color) !important; } .ant-btn svg { - color: var(--button-text-color)!important; + color: var(--button-text-color) !important; } // DRAWER From b02bf6877cff36ad4c25100c936204194fd36ba9 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:02:59 +0200 Subject: [PATCH 34/43] fix `ant-result` colors --- packages/app/src/theme/index.less | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/packages/app/src/theme/index.less b/packages/app/src/theme/index.less index 1a45d350..b5551b7f 100644 --- a/packages/app/src/theme/index.less +++ b/packages/app/src/theme/index.less @@ -324,4 +324,28 @@ body { to { opacity: 0; } +} + +.ant-result { + .ant-result-content { + display: inline-flex; + align-items: center; + justify-content: center; + + text-align: center; + + padding: 10px; + background-color: var(--background-color-accent); + color: var(--background-color-primary); + + h1, + h2, + h3, + h4, + h5, + p, + span { + margin: 0; + } + } } \ No newline at end of file From 046dd43831fbdaafbf08d5d6cc7e2504ae375d55 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:03:40 +0200 Subject: [PATCH 35/43] fix methods & added missings --- .../src/managers/SessionsManager/index.js | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/streaming-server/src/managers/SessionsManager/index.js b/packages/streaming-server/src/managers/SessionsManager/index.js index 82526867..def40749 100644 --- a/packages/streaming-server/src/managers/SessionsManager/index.js +++ b/packages/streaming-server/src/managers/SessionsManager/index.js @@ -29,17 +29,29 @@ export default class SessionsManager { } unpublishStream = (stream_key) => { - this.publicStreams = this.publicStreams.filter(stream => stream.stream_key !== stream_key) + this.publicStreams = this.publicStreams.filter((stream) => stream.stream_key !== stream_key) } getPublicStreams = () => { // return this.publicStreams but without stream_key property - return lodash.map(this.publicStreams, stream => { + return lodash.map(this.publicStreams, (stream) => { return lodash.omit(stream, "stream_key") }) } getStreamsByUserId = (user_id) => { - return lodash.filter(this.publicStreams, stream => stream.user_id === user_id) + const streams = lodash.filter(this.publicStreams, (stream) => stream.user_id === user_id) + + return lodash.map(streams, (stream) => { + return lodash.omit(stream, "stream_key") + }) + } + + getStreamsByUsername = (username) => { + const streams = lodash.filter(this.publicStreams, (stream) => stream.username === username) + + return lodash.map(streams, (stream) => { + return lodash.omit(stream, "stream_key") + }) } } \ No newline at end of file From f6bbeb5c665af6d809b65a681349b0cc198df1bd Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:25:08 +0200 Subject: [PATCH 36/43] update remotes config --- packages/app/config/index.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/app/config/index.js b/packages/app/config/index.js index 32bfae3d..4448b1ed 100644 --- a/packages/app/config/index.js +++ b/packages/app/config/index.js @@ -12,11 +12,10 @@ export default { alt: "/logo_alt.svg", full: "/logo_full.svg", }, - api: { - address: defaultRemotesOrigins.http_api,//process.env.NODE_ENV !== "production" ? `http://${window.location.hostname}:3000` : defaultRemotesOrigins.http_api, - }, - ws: { - address: defaultRemotesOrigins.ws_api, //process.env.NODE_ENV !== "production" ? `ws://${window.location.hostname}:3001` : defaultRemotesOrigins.ws_api, + remotes: { + mainApi: defaultRemotesOrigins.main_api, //process.env.NODE_ENV !== "production" ? `http://${window.location.hostname}:3000` : defaultRemotesOrigins.http_api + streamingApi: defaultRemotesOrigins.streaming_api, //process.env.NODE_ENV !== "production" ? `ws://${window.location.hostname}:3001` : defaultRemotesOrigins.ws_api + websocketApi: defaultRemotesOrigins.ws_api, }, app: { title: packagejson.name, From a099cff27a1e4f32a402588b2fb4449bfc02e19a Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:25:22 +0200 Subject: [PATCH 37/43] set default language to `en` --- packages/app/config/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/config/index.js b/packages/app/config/index.js index 4448b1ed..84877eac 100644 --- a/packages/app/config/index.js +++ b/packages/app/config/index.js @@ -40,6 +40,6 @@ export default { name: "Español" } ], - defaultLocale: "es", + defaultLocale: "en", } } \ No newline at end of file From fd3147863682b7b9feefd08aa66f71a2780bf2c5 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:26:09 +0200 Subject: [PATCH 38/43] get remote addresses from `remotes` config --- packages/app/src/extensions/api.extension.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/extensions/api.extension.js b/packages/app/src/extensions/api.extension.js index d1e993a9..4acbbc5c 100644 --- a/packages/app/src/extensions/api.extension.js +++ b/packages/app/src/extensions/api.extension.js @@ -87,8 +87,8 @@ export default class ApiExtension extends Extension { } return new Bridge({ - origin: config.api.address, - wsOrigin: config.ws.address, + origin: config.remotes.mainApi, + wsOrigin: config.remotes.websocketApi, wsOptions: { autoConnect: false, }, From ec249b70dbd14326757903c770e98c9ae2fa43ae Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:27:26 +0200 Subject: [PATCH 39/43] added `get/stream_config_from_username` endpoint --- .../controllers/StreamingController/index.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/server/src/controllers/StreamingController/index.js b/packages/server/src/controllers/StreamingController/index.js index 7612a402..9d94b51f 100644 --- a/packages/server/src/controllers/StreamingController/index.js +++ b/packages/server/src/controllers/StreamingController/index.js @@ -34,6 +34,31 @@ export default class StreamingController extends Controller { } get = { + "/stream_info_from_username": async (req, res) => { + const { username } = req.query + + const userspace = await StreamingKey.findOne({ username }) + + if (!userspace) { + return res.status(403).json({ + error: "This username has not a streaming key" + }) + } + + // TODO: meanwhile linebridge remote linkers are in development we gonna use this methods to fetch + const { data } = await axios.get(`${streamingServerAPIUri}/streams`, { + params: { + username: userspace.username + } + }).catch((error) => { + res.status(500).json({ + error: `Failed to fetch streams from [${streamingServerAPIAddress}]: ${error.message}` + }) + return false + }) + + return res.json(data) + }, "/streams": async (req, res) => { // TODO: meanwhile linebridge remote linkers are in development we gonna use this methods to fetch const { data } = await axios.get(`${streamingServerAPIUri}/streams`).catch((error) => { From b976a595dff0c5f32189147dfaff4750122aa517 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:28:01 +0200 Subject: [PATCH 40/43] update `StreamViewer` --- .../app/src/pages/streams/viewer/index.jsx | 160 +++++++++++++++--- .../app/src/pages/streams/viewer/index.less | 92 ++++++++++ 2 files changed, 231 insertions(+), 21 deletions(-) create mode 100644 packages/app/src/pages/streams/viewer/index.less diff --git a/packages/app/src/pages/streams/viewer/index.jsx b/packages/app/src/pages/streams/viewer/index.jsx index 815b3c27..56020326 100644 --- a/packages/app/src/pages/streams/viewer/index.jsx +++ b/packages/app/src/pages/streams/viewer/index.jsx @@ -1,15 +1,25 @@ -import React from 'react' +import React from "react" +import config from "config" import * as antd from "antd" -import Plyr from 'plyr' -import Hls from 'hls.js' -import mpegts from 'mpegts.js' +import { Icons } from "components/Icons" +import moment from "moment" + +import Plyr from "plyr" +import Hls from "hls.js" +import mpegts from "mpegts.js" import "plyr/dist/plyr.css" +import "./index.less" -const streamsSource = "http://media.ragestudio.net/live" +const streamsSource = config.remotes.streamingApi export default class StreamViewer extends React.Component { state = { + userData: null, + streamInfo: null, + spectators: 0, + timeFromNow: "00:00:00", + player: null, streamKey: null, streamSource: null, @@ -22,18 +32,98 @@ export default class StreamViewer extends React.Component { componentDidMount = async () => { const query = new URLSearchParams(window.location.search) - const requested = query.get("key") + const requestedUsername = query.get("key") - const source = `${streamsSource}/${requested}` - const player = new Plyr('#player') + const source = `${streamsSource}/${requestedUsername}` + const player = new Plyr("#player", { + autoplay: true, + controls: ["play", "mute", "volume", "fullscreen", "options", "settings"], + }) await this.setState({ player, - streamKey: requested, + streamKey: requestedUsername, streamSource: source, }) await this.loadWithProtocol[this.state.loadedProtocol]() + + // make the interface a bit confortable for a video player + app.ThemeController.applyVariant("dark") + app.eventBus.emit("toogleCompactMode", true) + app.SidebarController.toogleVisible(false) + app.HeaderController.toogleVisible(false) + + // fetch user info in the background + this.gatherUserInfo() + + // fetch stream info in the background + // await for it + await this.gatherStreamInfo() + + // create timer + if (this.state.streamInfo.connectCreated) { + this.createTimerCounter() + } + } + + componentWillUnmount = () => { + app.ThemeController.applyVariant(app.settings.get("themeVariant")) + app.eventBus.emit("toogleCompactMode", false) + app.SidebarController.toogleVisible(true) + app.HeaderController.toogleVisible(true) + app.HeaderController.toogleVisible(true) + + if (this.timerCounterInterval) { + this.timerCounterInterval = clearInterval(this.timerCounterInterval) + } + } + + gatherStreamInfo = async () => { + const result = await app.request.get.streamInfoFromUsername(undefined, { + username: this.state.streamKey, + }).catch((error) => { + console.error(error) + antd.message.error(error.message) + return false + }) + + if (result) { + this.setState({ + streamInfo: result, + }) + } + } + + gatherUserInfo = async () => { + const result = await app.request.get.user(undefined, { + username: this.state.streamKey, + }).catch((error) => { + console.error(error) + antd.message.error(error.message) + return false + }) + + if (result) { + this.setState({ + userData: result, + }) + } + } + + createTimerCounter = () => { + this.timerCounterInterval = setInterval(() => { + const secondsFromNow = moment().diff(moment(this.state.streamInfo.connectCreated), "seconds") + + // calculate hours minutes and seconds + const hours = Math.floor(secondsFromNow / 3600) + const minutes = Math.floor((secondsFromNow - hours * 3600) / 60) + const seconds = secondsFromNow - hours * 3600 - minutes * 60 + + this.setState({ + timeFromNow: `${hours}:${minutes}:${seconds}`, + }) + }, 1000) } updateQuality = (newQuality) => { @@ -60,10 +150,10 @@ export default class StreamViewer extends React.Component { console.log("Switching to " + protocol) this.loadWithProtocol[protocol]() } - + loadWithProtocol = { hls: () => { - const source = `${this.state.streamSource}.m3u8` + const source = `${streamsSource}/stream/hls/${this.state.streamKey}` const hls = new Hls() hls.loadSource(source) @@ -72,9 +162,13 @@ export default class StreamViewer extends React.Component { this.setState({ protocolInstance: hls, loadedProtocol: "hls" }) }, flv: () => { - const source = `${this.state.streamSource}.flv` + const source = `${streamsSource}/stream/flv/${this.state.streamKey}` - const instance = mpegts.createPlayer({ type: 'flv', url: source, isLive: true }) + const instance = mpegts.createPlayer({ + type: "flv", + url: source, + isLive: true + }) instance.attachMediaElement(this.videoPlayerRef.current) instance.load() @@ -85,15 +179,39 @@ export default class StreamViewer extends React.Component { } render() { - return
- this.switchProtocol(value)} - value={this.state.loadedProtocol} - > - HLS - FLV - + return
} } \ No newline at end of file diff --git a/packages/app/src/pages/streams/viewer/index.less b/packages/app/src/pages/streams/viewer/index.less new file mode 100644 index 00000000..f9dbc8d5 --- /dev/null +++ b/packages/app/src/pages/streams/viewer/index.less @@ -0,0 +1,92 @@ +.plyr__controls { + width: 100%; + display: inline-flex; + //justify-content: space-between; +} + +.stream { + display: flex; + flex-direction: row; + + align-items: center; + justify-content: center; + + height: 100vh; + width: 100vw; + + color: var(--background-color-contrast); + + h1, + h2, + h3, + h4, + h5, + span, + p { + color: var(--background-color-contrast); + } + + .panel { + display: flex; + flex-direction: column; + + height: 100vh; + width: 20vw; + + .info { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + + width: 100%; + height: 10vh; + + padding: 10px; + + backdrop-filter: 20px; + + h1, + h2, + h3, + h4, + h5 { + margin: 0; + } + + >div { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + height: fit-content; + margin-bottom: 8px; + + >div { + margin-right: 8px; + } + } + } + + .chatbox { + width: 20vw; + padding: 20px; + height: 100vh; + } + + #spectatorCount { + font-size: 0.8em; + } + + #timeCount { + font-size: 0.8em; + } + } + + .plyr { + border-radius: 0 4px 4px 0; + width: 80vw; + height: 100vh; + } +} \ No newline at end of file From 223b8d36f4f1b38332d089208f5cd9e195445f9e Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:28:13 +0200 Subject: [PATCH 41/43] get streams from username --- packages/streaming-server/src/index.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/streaming-server/src/index.js b/packages/streaming-server/src/index.js index 48030268..b7d2f0ea 100644 --- a/packages/streaming-server/src/index.js +++ b/packages/streaming-server/src/index.js @@ -136,14 +136,14 @@ class StreamingServer { "/streams": { method: "get", fn: async (req, res) => { - if (req.query?.user_id) { - const streams = await this.Sessions.getStreamsByUserId(req.query.user_id) + let streams = [] - return res.json(streams) + if (req.query?.username) { + streams = await this.Sessions.getStreamsByUsername(req.query?.username) + } else { + streams = this.Sessions.getPublicStreams() } - let streams = this.Sessions.getPublicStreams() - // retrieve streams details from internal media server api let streamsListDetails = await axios.get(`${internalMediaServerURI}/api/streams`) @@ -172,6 +172,12 @@ class StreamingServer { } }) + // if username is provided, return only streams for that user + // is supposed to be allowed only one stream per user + if (req.query?.username) { + return res.json(streams[0]) + } + return res.json(streams) } }, From 0c0b47527cbaf29b36ca505d6ff8ffb11e5bfb9f Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:30:16 +0200 Subject: [PATCH 42/43] added basic streaming control --- .../app/src/pages/streaming_control/index.jsx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/app/src/pages/streaming_control/index.jsx b/packages/app/src/pages/streaming_control/index.jsx index c6d1c4ea..a0c72309 100644 --- a/packages/app/src/pages/streaming_control/index.jsx +++ b/packages/app/src/pages/streaming_control/index.jsx @@ -56,6 +56,25 @@ export default (props) => { } } + const regenerateStreamingKey = async () => { + antd.Modal.confirm({ + title: "Regenerate streaming key", + content: "Are you sure you want to regenerate the streaming key? After this, all other generated keys will be deleted.", + onOk: async () => { + const result = await app.request.post.regenerateStreamingKey().catch((error) => { + console.error(error) + antd.message.error(error.message) + + return null + }) + + if (result) { + setStreamingKey(result.key) + } + } + }) + } + React.useEffect(() => { checkStreamingKey() checkTagetServer() @@ -98,6 +117,12 @@ export default (props) => {
+
+ regenerateStreamingKey()}> + + Regenerate + +
From c49283243bbb39431aa8832e01fecf7f2add95cc Mon Sep 17 00:00:00 2001 From: srgooglo Date: Thu, 12 May 2022 19:30:47 +0200 Subject: [PATCH 43/43] added `streams` to default sidebar keys --- packages/app/constants/defaultSettings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/app/constants/defaultSettings.json b/packages/app/constants/defaultSettings.json index 5b8190b3..bdb121fd 100644 --- a/packages/app/constants/defaultSettings.json +++ b/packages/app/constants/defaultSettings.json @@ -1,4 +1,5 @@ { + "themeVariant": "light", "forceMobileMode": false, "notifications_sound": true, "notifications_vibrate": true, @@ -13,6 +14,7 @@ "main", "explore", "saved", - "marketplace" + "marketplace", + "streams", ] } \ No newline at end of file