diff --git a/.corenode b/.corenode
index d92b1b00..6ad21024 100755
--- a/.corenode
+++ b/.corenode
@@ -1,3 +1,3 @@
{
- "version": "0.31.1"
+ "version": "0.33.0"
}
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
deleted file mode 100755
index f183adc3..00000000
--- a/.github/workflows/changelog.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-name: Create Changelogs
-on:
- push:
- branches: [ master ]
-jobs:
- create_changelog:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - name: Create Changelogs
- uses: heineiuo/create-changelogs@v0.2.8
diff --git a/.github/workflows/trello_issue.yml b/.github/workflows/trello_issue.yml
deleted file mode 100755
index 02263052..00000000
--- a/.github/workflows/trello_issue.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Trello Issue List
-on:
- issues:
- types: [opened]
-env:
- TRELLO_KEY: ${{ secrets.TRELLO_KEY }}
- TRELLO_TOKEN: ${{ secrets.TRELLO_TOKEN }}
-
-jobs:
- issue_send:
- name: Send Issue to Trello
- runs-on: ubuntu-latest
- steps:
- - name: Runs trello manage
- uses: sisodiya2421/trello-manage@master
- with:
- repo-name: Comty Development
- trello-username: ${{ secrets.TRELLO_USERNAME }}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
deleted file mode 100755
index dc7e8b50..00000000
--- a/.github/workflows/validate.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-name: Validate code
-
-on:
- push:
- branches:
- - '**'
-
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v1
- with:
- node-version: 12
- - run: npm ci
- - run: npm test
\ No newline at end of file
diff --git a/package.json b/package.json
index f524c4b8..243c2d0b 100755
--- a/package.json
+++ b/package.json
@@ -22,5 +22,5 @@
"devDependencies": {
"concurrently": "^7.5.0"
},
- "version": "0.31.1"
+ "version": "0.33.0"
}
diff --git a/packages/app/constants/defaultSettings.json b/packages/app/constants/defaultSettings.json
index 291e27d4..d0b0873d 100755
--- a/packages/app/constants/defaultSettings.json
+++ b/packages/app/constants/defaultSettings.json
@@ -19,7 +19,11 @@
"language": "en",
"sidebarKeys": [
"home",
+ "tv",
+ "music",
+ "events",
+ "groups",
"marketplace",
- "groups"
+ "dev"
]
}
\ No newline at end of file
diff --git a/packages/app/constants/routes.json b/packages/app/constants/routes.json
index 1eeb4750..e278adf9 100755
--- a/packages/app/constants/routes.json
+++ b/packages/app/constants/routes.json
@@ -14,10 +14,31 @@
"reachable": true
},
{
- "id": "saved",
- "path": "/saved",
- "title": "Saved",
- "icon": "Archive",
+ "id": "events",
+ "path": "/events",
+ "title": "Events",
+ "icon": "MdLocalActivity",
+ "reachable": true
+ },
+ {
+ "id": "tv",
+ "path": "/tv",
+ "title": "Tv",
+ "icon": "Tv",
+ "reachable": true
+ },
+ {
+ "id": "music",
+ "path": "/music",
+ "title": "Music",
+ "icon": "MdMusicVideo",
+ "reachable": true
+ },
+ {
+ "id": "groups",
+ "path": "/groups",
+ "title": "Groups",
+ "icon": "Users",
"reachable": true
},
{
@@ -28,23 +49,10 @@
"reachable": true
},
{
- "id": "streams",
- "path": "/streams",
- "title": "Streams",
- "icon": "Tv",
- "reachable": true
- },
- {
- "id": "streaming_control",
- "path": "/streaming_control",
- "title": "Streaming Control",
- "icon": "Video"
- },
- {
- "id": "groups",
- "path": "/groups",
- "title": "Groups",
- "icon": "Users",
+ "id": "dev",
+ "path": "/dev",
+ "title": "Development",
+ "icon": "MdOutlineCode",
"reachable": true
}
]
\ No newline at end of file
diff --git a/packages/app/package.json b/packages/app/package.json
index c9644eec..caaab578 100755
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "comty",
- "version": "0.31.1",
+ "version": "0.33.0",
"license": "LGPL-2.1",
"main": "electron/main",
"author": "RageStudio",
diff --git a/packages/app/public/apple-touch-icon.png b/packages/app/public/apple-touch-icon.png
index fd009a7b..d6a2e99f 100644
Binary files a/packages/app/public/apple-touch-icon.png and b/packages/app/public/apple-touch-icon.png differ
diff --git a/packages/app/public/favicon.ico b/packages/app/public/favicon.ico
index 73adbb34..2156b823 100644
Binary files a/packages/app/public/favicon.ico and b/packages/app/public/favicon.ico differ
diff --git a/packages/app/public/icon-192-maskable.png b/packages/app/public/icon-192-maskable.png
index ecb54e31..851de9d6 100644
Binary files a/packages/app/public/icon-192-maskable.png and b/packages/app/public/icon-192-maskable.png differ
diff --git a/packages/app/public/icon-192.png b/packages/app/public/icon-192.png
index ab0a6bab..6f91de5b 100644
Binary files a/packages/app/public/icon-192.png and b/packages/app/public/icon-192.png differ
diff --git a/packages/app/public/icon-512-maskable.png b/packages/app/public/icon-512-maskable.png
index 10ac0b7c..5c7dbf11 100644
Binary files a/packages/app/public/icon-512-maskable.png and b/packages/app/public/icon-512-maskable.png differ
diff --git a/packages/app/public/icon-512.png b/packages/app/public/icon-512.png
index 5935d10c..8e57eb15 100644
Binary files a/packages/app/public/icon-512.png and b/packages/app/public/icon-512.png differ
diff --git a/packages/app/src/components/UserRegister/index.jsx b/packages/app/src/components/UserRegister/index.jsx
index e1ac3400..45c59e7c 100755
--- a/packages/app/src/components/UserRegister/index.jsx
+++ b/packages/app/src/components/UserRegister/index.jsx
@@ -85,7 +85,10 @@ const steps = [
setValidCharacters(hasValidCharacters(username))
const timer = setTimeout(async () => {
- if (!validCharacters) return
+ if (!validCharacters) {
+ setLoading(false)
+ return
+ }
const request = await app.api.customRequest("main", {
method: "GET",
@@ -120,7 +123,7 @@ const steps = [
autoCorrect="off"
autoCapitalize="none"
onPressEnter={submit}
- placeholder="@newuser"
+ placeholder="newuser"
value={username}
onChange={handleUpdate}
status={username.length == 0 ? "default" : loading ? "default" : (isValid() ? "success" : "error")}
diff --git a/packages/app/src/models/livestream/index.js b/packages/app/src/models/livestream/index.js
index ce658117..228a3a69 100644
--- a/packages/app/src/models/livestream/index.js
+++ b/packages/app/src/models/livestream/index.js
@@ -27,6 +27,12 @@ export default class Livestream {
return data
}
+ static async getCategories() {
+ const request = await Livestream.bridge.get.streamingCategories()
+
+ return request
+ }
+
static async getStreamInfo(payload) {
let { username } = payload ?? {}
diff --git a/packages/app/src/pages/home/[type].jsx b/packages/app/src/pages/home/[type].jsx
index e8edf9c4..2b3825b4 100755
--- a/packages/app/src/pages/home/[type].jsx
+++ b/packages/app/src/pages/home/[type].jsx
@@ -6,36 +6,10 @@ import { Icons, createIconRender } from "components/Icons"
import { HashtagTrendings, FeaturedEventsAnnouncements, ConnectedFriends } from "components"
-import FeedBrowser from "./components/feed"
-import ExploreBrowser from "./components/explore"
-import LivestreamsBrowser from "./components/livestreams"
-import SavedPostsBrowser from "./components/savedPosts"
+import Tabs from "./tabs"
import "./index.less"
-const Tabs = {
- "feed": {
- title: "Feed",
- icon: "Rss",
- component: FeedBrowser
- },
- "explore": {
- title: "Explore",
- icon: "Search",
- component: ExploreBrowser
- },
- "savedPosts": {
- title: "Saved posts",
- icon: "Bookmark",
- component: SavedPostsBrowser
- },
- "livestreams": {
- title: "Livestreams",
- icon: "Tv",
- component: LivestreamsBrowser
- },
-}
-
export default class Dashboard extends React.Component {
state = {
activeTab: this.props.match.params.type ?? "feed"
@@ -80,7 +54,7 @@ export default class Dashboard extends React.Component {
}
render() {
- return
+ return
+ return
{
+ return
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/home/components/trendings/index.less b/packages/app/src/pages/home/components/trendings/index.less
new file mode 100644
index 00000000..db49f864
--- /dev/null
+++ b/packages/app/src/pages/home/components/trendings/index.less
@@ -0,0 +1 @@
+.trendingsBrowser {}
\ No newline at end of file
diff --git a/packages/app/src/pages/home/index.jsx b/packages/app/src/pages/home/index.jsx
index 138fffe9..0c9a5808 100644
--- a/packages/app/src/pages/home/index.jsx
+++ b/packages/app/src/pages/home/index.jsx
@@ -1,7 +1,7 @@
import React from "react"
export default () => {
- app.setLocation("home/feed")
+ app.setLocation("/home/feed")
return <>>
}
\ No newline at end of file
diff --git a/packages/app/src/pages/home/index.less b/packages/app/src/pages/home/index.less
index 95c3abdc..66c22bab 100755
--- a/packages/app/src/pages/home/index.less
+++ b/packages/app/src/pages/home/index.less
@@ -1,4 +1,4 @@
-.dashboard {
+.postingDashboard {
display: grid;
grid-template-columns: 10vw 1fr 0.5fr;
diff --git a/packages/app/src/pages/home/tabs.jsx b/packages/app/src/pages/home/tabs.jsx
new file mode 100644
index 00000000..16b19096
--- /dev/null
+++ b/packages/app/src/pages/home/tabs.jsx
@@ -0,0 +1,27 @@
+import FeedTab from "./components/feed"
+import ExploreTab from "./components/explore"
+import TrendingsTab from "./components/trendings"
+import SavedPostsTab from "./components/savedPosts"
+
+export default {
+ "feed": {
+ title: "Feed",
+ icon: "Rss",
+ component: FeedTab
+ },
+ "explore": {
+ title: "Explore",
+ icon: "Search",
+ component: ExploreTab
+ },
+ "trendings": {
+ title: "Trendings",
+ icon: "TrendingUp",
+ component: TrendingsTab
+ },
+ "savedPosts": {
+ title: "Saved posts",
+ icon: "Bookmark",
+ component: SavedPostsTab
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/tv/[type].jsx b/packages/app/src/pages/tv/[type].jsx
new file mode 100644
index 00000000..709efe9c
--- /dev/null
+++ b/packages/app/src/pages/tv/[type].jsx
@@ -0,0 +1,87 @@
+import React from "react"
+import * as antd from "antd"
+import classnames from "classnames"
+
+import { Icons, createIconRender } from "components/Icons"
+
+import Tabs from "./tabs"
+
+import "./index.less"
+
+export default class TVDashboard extends React.Component {
+ state = {
+ activeTab: this.props.match.params.type ?? "feed"
+ }
+
+ primaryPanelRef = React.createRef()
+
+ componentDidMount() {
+ app.eventBus.emit("style.compactMode", false)
+ }
+
+ renderActiveTab() {
+ const tab = Tabs[this.state.activeTab]
+
+ if (!tab) {
+ return
+ }
+
+ return React.createElement(tab.component)
+ }
+
+ handleTabChange = (key) => {
+ if (this.state.activeTab === key) return
+
+ // set to primary panel fade-opacity-leave class
+ this.primaryPanelRef.current.classList.add("fade-opacity-leave")
+
+ setTimeout(() => {
+ this.setState({ activeTab: key })
+ // update location
+ app.history.replace(key)
+ }, 200)
+
+ // remove fade-opacity-leave class after animation
+ setTimeout(() => {
+ this.primaryPanelRef.current.classList.remove("fade-opacity-leave")
+ }, 300)
+ }
+
+ render() {
+ return
+
+ {this.renderActiveTab()}
+
+
+
+
+
TV
+
this.handleTabChange(key)}
+ >
+ {Object.keys(Tabs).map((key) => {
+ const tab = Tabs[key]
+
+ return
+ {tab.title}
+
+ })}
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/live_control/index.jsx b/packages/app/src/pages/tv/components/controlPanel/index.jsx
similarity index 61%
rename from packages/app/src/pages/live_control/index.jsx
rename to packages/app/src/pages/tv/components/controlPanel/index.jsx
index c307e7f9..d1dbd192 100644
--- a/packages/app/src/pages/live_control/index.jsx
+++ b/packages/app/src/pages/tv/components/controlPanel/index.jsx
@@ -3,7 +3,7 @@ import * as antd from "antd"
import { Icons } from "components/Icons"
-import Livestream from "../../models/livestream"
+import Livestream from "../../../../models/livestream"
import "./index.less"
@@ -32,6 +32,131 @@ const StreamingKeyView = (props) => {
}
+const LivestreamsCategoriesSelector = (props) => {
+ const [categories, setCategories] = React.useState([])
+ const [loading, setLoading] = React.useState(true)
+
+ const loadData = async () => {
+ setLoading(true)
+
+ const categories = await Livestream.getCategories().catch((err) => {
+ console.error(err)
+
+ app.message.error("Failed to load categories")
+
+ return null
+ })
+
+ console.log(`Loaded categories >`, categories)
+
+ setLoading(false)
+
+ if (categories) {
+ setCategories(categories)
+ }
+ }
+
+ React.useEffect(() => {
+ loadData()
+ }, [])
+
+ if (loading) {
+ return
+ }
+
+ return
props.updateStreamInfo("category", value)}
+ >
+ {
+ categories.map((category) => {
+ return {category?.label ?? "No category"}
+ })
+ }
+
+}
+
+const StreamInfoEditor = (props) => {
+ const [streamInfo, setStreamInfo] = React.useState(props.defaultStreamInfo ?? {})
+
+ const updateStreamInfo = (key, value) => {
+ setStreamInfo({
+ ...streamInfo,
+ [key]: value,
+ })
+ }
+
+ const saveStreamInfo = async () => {
+ if (typeof props.onSave === "function") {
+ return await props.onSave(streamInfo)
+ }
+
+ // peform default save
+ const result = await Livestream.updateLivestreamInfo(streamInfo).catch((err) => {
+ console.error(err)
+
+ app.message.error("Failed to update stream info")
+
+ return false
+ })
+
+ if (result) {
+ app.message.success("Stream info updated")
+ }
+
+ if (typeof props.onSaveComplete === "function") {
+ await props.onSaveComplete(result)
+ }
+
+ return result
+ }
+
+ return
+
+
+ Title
+
+
+
updateStreamInfo("title", e.target.value)}
+ />
+
+
+
+
+ Description
+
+
+
updateStreamInfo("description", e.target.value)}
+ />
+
+
+
+
+ Save
+
+
+}
+
export default (props) => {
const [streamInfo, setStreamInfo] = React.useState({})
const [addresses, setAddresses] = React.useState({})
@@ -39,6 +164,19 @@ export default (props) => {
const [isConnected, setIsConnected] = React.useState(false)
const [streamingKey, setStreamingKey] = React.useState(null)
+ const onClickEditInfo = () => {
+ app.ModalController.open(() =>
{
+ if (result) {
+ app.ModalController.close()
+
+ fetchStreamInfo()
+ }
+ }}
+ />)
+ }
+
const regenerateStreamingKey = async () => {
antd.Modal.confirm({
title: "Regenerate streaming key",
@@ -125,15 +263,35 @@ export default (props) => {
+
+
+ Description
+
+
+
+ {streamInfo?.description ?? "No description"}
+
+
+
Category
- {streamInfo?.category ?? "No category"}
+ {streamInfo?.category?.label ?? "No category"}
+
+
+
}
+ onClick={onClickEditInfo}
+ >
+ Edit info
+
+
diff --git a/packages/app/src/pages/live_control/index.less b/packages/app/src/pages/tv/components/controlPanel/index.less
similarity index 82%
rename from packages/app/src/pages/live_control/index.less
rename to packages/app/src/pages/tv/components/controlPanel/index.less
index ce9dda92..e73da2bb 100644
--- a/packages/app/src/pages/live_control/index.less
+++ b/packages/app/src/pages/tv/components/controlPanel/index.less
@@ -2,13 +2,15 @@
display: flex;
flex-direction: column;
+ width: 100%;
+
transition: all 0.3s ease-in-out;
.header {
display: flex;
flex-direction: row;
- height: 20vh;
+ height: fit-content;
padding: 15px;
@@ -21,7 +23,7 @@
.preview {
height: 100%;
- max-width: 400px;
+ width: 300px;
img {
width: 100%;
@@ -38,6 +40,7 @@
flex-direction: column;
padding: 20px 0;
+ width: 100%;
.status {
margin-bottom: 20px;
@@ -47,6 +50,7 @@
.config {
display: flex;
+ flex-direction: column;
padding: 0 40px;
@@ -83,7 +87,7 @@
.title {
display: inline-flex;
flex-direction: row;
-
+
justify-content: space-between;
align-items: center;
@@ -114,8 +118,29 @@
div {
display: inline-flex;
flex-direction: row;
-
+
align-items: center;
justify-content: center;
}
+}
+
+.streamInfoEditor {
+ display: flex;
+ flex-direction: column;
+
+ .field {
+ display: flex;
+ flex-direction: column;
+
+ margin-bottom: 20px;
+
+ .value {
+ margin-top: 5px;
+ margin-left: 20px;
+
+ .ant-select {
+ min-width: 200px;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/app/src/pages/home/components/livestreams/index.jsx b/packages/app/src/pages/tv/components/explore/index.jsx
similarity index 85%
rename from packages/app/src/pages/home/components/livestreams/index.jsx
rename to packages/app/src/pages/tv/components/explore/index.jsx
index 7691ee4d..9f6f0bc1 100644
--- a/packages/app/src/pages/home/components/livestreams/index.jsx
+++ b/packages/app/src/pages/tv/components/explore/index.jsx
@@ -1,11 +1,10 @@
import React from "react"
+import Livestream from "models/livestream"
import * as antd from "antd"
import { UserPreview } from "components"
import { Icons } from "components/Icons"
-import Livestream from "../../../../models/livestream"
-
import "./index.less"
const LivestreamItem = (props) => {
@@ -34,7 +33,7 @@ const LivestreamItem = (props) => {
{livestream.info?.description ?? "No description"}
- {livestream.info?.catagory ?? "No category"}
+ {livestream.info?.category?.label ?? "No category"}
@@ -71,10 +70,6 @@ export default (props) => {
app.setLocation(`/live/${livestream.username}`)
}
- const onClickControlPanel = () => {
- app.setLocation("/live_control")
- }
-
const renderList = () => {
if (loading) {
return
@@ -105,19 +100,10 @@ export default (props) => {
Livestreams
-
-
-
}
- onClick={onClickControlPanel}
- >
- Control Panel
-
-
{renderList()}
-}
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/home/components/livestreams/index.less b/packages/app/src/pages/tv/components/explore/index.less
similarity index 100%
rename from packages/app/src/pages/home/components/livestreams/index.less
rename to packages/app/src/pages/tv/components/explore/index.less
diff --git a/packages/app/src/pages/tv/components/feed/index.jsx b/packages/app/src/pages/tv/components/feed/index.jsx
new file mode 100644
index 00000000..3dce8530
--- /dev/null
+++ b/packages/app/src/pages/tv/components/feed/index.jsx
@@ -0,0 +1,14 @@
+import React from "react"
+import { Result } from "antd"
+
+import "./index.less"
+
+export default (props) => {
+ return
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/tv/components/feed/index.less b/packages/app/src/pages/tv/components/feed/index.less
new file mode 100644
index 00000000..112802cc
--- /dev/null
+++ b/packages/app/src/pages/tv/components/feed/index.less
@@ -0,0 +1 @@
+.livestreamsFeed {}
\ No newline at end of file
diff --git a/packages/app/src/pages/tv/index.jsx b/packages/app/src/pages/tv/index.jsx
new file mode 100644
index 00000000..76411c7b
--- /dev/null
+++ b/packages/app/src/pages/tv/index.jsx
@@ -0,0 +1,7 @@
+import React from "react"
+
+export default () => {
+ app.setLocation("/tv/feed")
+
+ return <>>
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/tv/index.less b/packages/app/src/pages/tv/index.less
new file mode 100644
index 00000000..5889fc82
--- /dev/null
+++ b/packages/app/src/pages/tv/index.less
@@ -0,0 +1,47 @@
+.tvDashboard {
+ display: grid;
+
+ grid-template-columns: 3fr 1fr;
+ grid-template-rows: 1fr;
+ grid-column-gap: 10px;
+ grid-row-gap: 0px;
+
+ width: 100%;
+
+ padding-left: 30px;
+
+ .panel {
+ position: sticky;
+ top: 0;
+
+ height: fit-content;
+
+ display: flex;
+ flex-direction: column;
+
+ align-items: center;
+
+ >div {
+ margin-bottom: 15px;
+ }
+
+ .card {
+ background-color: var(--background-color-accent);
+ border-radius: 12px;
+ padding: 20px;
+
+ min-width: 20vw;
+
+ h1,
+ h2 {
+ font-family: "Space Grotesk", sans-serif;
+ }
+ }
+ }
+
+ .ant-menu {
+ svg {
+ margin-right: 0 !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/tv/tabs.jsx b/packages/app/src/pages/tv/tabs.jsx
new file mode 100644
index 00000000..9c2fb8d3
--- /dev/null
+++ b/packages/app/src/pages/tv/tabs.jsx
@@ -0,0 +1,21 @@
+import FeedTab from "./components/feed"
+import ExploreTab from "./components/explore"
+import ControlPanelTab from "./components/controlPanel"
+
+export default {
+ "feed": {
+ title: "Feed",
+ icon: "Rss",
+ component: FeedTab
+ },
+ "explore": {
+ title: "Explore",
+ icon: "Search",
+ component: ExploreTab
+ },
+ "controlPanel": {
+ title: "Control Panel",
+ icon: "Settings",
+ component: ControlPanelTab
+ }
+}
\ No newline at end of file
diff --git a/packages/server/package.json b/packages/server/package.json
index d3ef419f..e110b4ad 100755
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -1,6 +1,6 @@
{
"name": "@comty/server",
- "version": "0.31.1",
+ "version": "0.33.0",
"main": "dist/index.js",
"scripts": {
"build": "corenode-cli build",
diff --git a/packages/server/src/controllers/StreamingController/index.js b/packages/server/src/controllers/StreamingController/index.js
index 9e9bc3e6..66a42c0d 100755
--- a/packages/server/src/controllers/StreamingController/index.js
+++ b/packages/server/src/controllers/StreamingController/index.js
@@ -3,19 +3,17 @@ import { nanoid } from "nanoid"
import lodash from "lodash"
import axios from "axios"
-import { User, StreamingKey, StreamingInfo } from "../../models"
+import { Schematized } from "../../lib"
+import { User, StreamingKey, StreamingInfo, StreamingCategory } from "../../models"
-const streamingIngestServer = process.env.STREAMING_INGEST_SERVER
-const streamingServerAPIAddress = process.env.STREAMING_API_SERVER
-const streamingServerAPIProtocol = streamingServerAPIAddress.startsWith("https") ? "https" : "http"
+const streamingIngestServer = process.env.STREAMING_INGEST_SERVER ?? ""
+const streamingServerAPIAddress = process.env.STREAMING_API_SERVER ?? ""
-const streamingServerAPIUri = `${streamingServerAPIProtocol}://${streamingServerAPIAddress.split("://")[1]}`
+const streamingServerAPIUri = `${streamingServerAPIAddress.startsWith("https") ? "https" : "http"}://${streamingServerAPIAddress.split("://")[1]}`
const FILTER_KEYS = ["stream"]
export default class StreamingController extends Controller {
- streamings = []
-
methods = {
genereteKey: async (user_id) => {
// this will generate a new key for the user
@@ -34,78 +32,61 @@ export default class StreamingController extends Controller {
return streamingKey
},
- regenerateStreamingList: async () => {
+ fetchStreams: async () => {
// fetch all streams from api
- let streams = await axios.get(`${streamingServerAPIUri}/api/v1/streams`).catch((err) => {
- console.log(err)
+ let { data } = await axios.get(`${streamingServerAPIUri}/api/v1/streams`).catch((err) => {
+ console.error(err)
return false
})
- if (streams) {
- streams = streams.data.streams
+ let streamings = []
- // FIXME: this method is not totally async
- streams.forEach((stream) => {
- // check if the stream is already in the list
- const streamInList = this.streamings.find((s) => s.stream === stream.name)
+ if (!data) return streamings
- if (!streamInList) {
- // if not, add it
- this.methods.pushToLocalList({
- stream: stream.name,
- app: stream.app,
- }).catch((err) => {
- // sorry for you
- })
- }
+ streamings = data.streams
+
+ streamings = streamings.map(async (stream) => {
+ stream = await this.methods.generateStreamFromStreamkey(stream.name)
+
+ let info = await StreamingInfo.findOne({
+ user_id: stream.user_id
})
- }
- },
- pushToLocalList: async (payload) => {
- const { stream, app } = payload
- const username = app.split("/")[1]
- const user_id = await User.findOne({ username }).then((user) => user._id)
+ if (info) {
+ stream.info = info.toObject()
- const streamingKey = await StreamingKey.findOne({
- key: stream
+ stream.info.category = await StreamingCategory.findOne({
+ key: stream.info.category
+ })
+ }
+
+ return stream
})
- if (!streamingKey) {
- throw new Error("Invalid streaming key")
- }
+ streamings = await Promise.all(streamings)
- if (username !== streamingKey.username) {
- throw new Error("Invalid streaming key for this username")
- }
+ return streamings.map((stream) => {
+ return lodash.omit(stream, FILTER_KEYS)
+ })
+ },
+ generateStreamFromStreamkey: async (streamKey) => {
+ // generate a stream from a streamkey
+ const streamingKey = await StreamingKey.findOne({
+ key: streamKey
+ })
+
+ if (!streamingKey) return false
const streaming = {
- stream,
- user_id: user_id.toString(),
+ user_id: streamingKey.user_id,
username: streamingKey.username,
sources: {
- rtmp: `${streamingIngestServer}/live/${username}`,
- hls: `${streamingServerAPIAddress}/live/${username}/src.m3u8`,
- flv: `${streamingServerAPIAddress}/live/${username}/src.flv`,
+ rtmp: `${streamingIngestServer}/live/${streamingKey.username}`,
+ hls: `${streamingServerAPIAddress}/live/${streamingKey.username}/src.m3u8`,
+ flv: `${streamingServerAPIAddress}/live/${streamingKey.username}/src.flv`,
}
}
- this.streamings.push(streaming)
-
- return streaming
- },
- removeFromLocalList: async (payload) => {
- const { stream } = payload
-
- // remove from streamings array
- const streaming = this.streamings.find((streaming) => streaming.stream === stream)
-
- if (!streaming) {
- throw new Error("Stream not found")
- }
-
- this.streamings = this.streamings.filter((streaming) => streaming.stream !== stream)
-
return streaming
},
handleInfoUpdate: async (payload) => {
@@ -147,28 +128,15 @@ export default class StreamingController extends Controller {
}
get = {
+ "/streaming/categories": async (req, res) => {
+ const categories = await StreamingCategory.find()
+
+ return res.json(categories)
+ },
"/streams": async (req, res) => {
- await this.methods.regenerateStreamingList()
+ const remoteStreams = await this.methods.fetchStreams()
- let data = this.streamings.map((stream) => {
- return lodash.omit(stream, FILTER_KEYS)
- })
-
- data = data.map(async (stream) => {
- let info = await StreamingInfo.findOne({
- user_id: stream.user_id
- })
-
- if (info) {
- stream.info = info.toObject()
- }
-
- return stream
- })
-
- data = await Promise.all(data)
-
- return res.json(data)
+ return res.json(remoteStreams)
},
"/stream/info": {
middleware: ["withAuthentication"],
@@ -189,11 +157,32 @@ export default class StreamingController extends Controller {
user_id = user_id["_id"].toString()
}
- const info = await StreamingInfo.findOne({
+ let info = await StreamingInfo.findOne({
user_id,
})
- return res.json(info)
+ if (!info) {
+ info = new StreamingInfo({
+ user_id,
+ })
+
+ await info.save()
+ }
+
+ const category = await StreamingCategory.findOne({
+ key: info.category
+ }).catch((err) => {
+ console.error(err)
+ return {}
+ }) ?? {}
+
+ return res.json({
+ ...info.toObject(),
+ ["category"]: {
+ key: category?.key ?? "unknown",
+ label: category?.label ?? "Unknown",
+ }
+ })
}
},
"/streaming/addresses": {
@@ -218,8 +207,10 @@ export default class StreamingController extends Controller {
"/streaming/:username": async (req, res) => {
const { username } = req.params
+ const streamings = await this.methods.fetchStreams()
+
// search on this.streamings
- const streaming = this.streamings.find((streaming) => streaming.username === username)
+ const streaming = streamings.find((streaming) => streaming.username === username)
if (streaming) {
return res.json(lodash.omit(streaming, FILTER_KEYS))
@@ -257,6 +248,36 @@ export default class StreamingController extends Controller {
},
}
+ put = {
+ "/streaming/category": {
+ middlewares: ["withAuthentication", "onlyAdmin"],
+ fn: Schematized({
+ required: ["key", "label"]
+ }, async (req, res) => {
+ const { key, label } = req.selection
+
+ const existingCategory = await StreamingCategory.findOne({
+ key
+ })
+
+ if (existingCategory) {
+ return res.status(400).json({
+ error: "Category already exists"
+ })
+ }
+
+ const category = new StreamingCategory({
+ key,
+ label,
+ })
+
+ await category.save()
+
+ return res.json(category)
+ })
+ }
+ }
+
post = {
"/streaming/update_info": {
middlewares: ["withAuthentication"],
@@ -285,25 +306,22 @@ export default class StreamingController extends Controller {
}
},
"/streaming/publish": async (req, res) => {
- const { app, stream, tcUrl } = req.body
+ const { stream } = req.body
+
+ const streaming = await this.methods.generateStreamFromStreamkey(stream).catch((err) => {
+ console.error(err)
- const streaming = await this.methods.pushToLocalList({
- app,
- stream,
- tcUrl
- }).catch((err) => {
res.status(500).json({
- code: 1,
- error: err.message
+ error: `Cannot generate stream: ${err.message}`,
})
- return false
+ return null
})
if (streaming) {
- global.wsInterface.io.emit(`streaming.new`, {
- username: streaming.username,
- })
+ global.wsInterface.io.emit(`streaming.new`, streaming)
+
+ global.wsInterface.io.emit(`streaming.new.${streaming.username}`, streaming)
return res.json({
code: 0,
@@ -314,21 +332,16 @@ export default class StreamingController extends Controller {
"/streaming/unpublish": async (req, res) => {
const { stream } = req.body
- const streaming = await this.methods.removeFromLocalList({
- stream
- }).catch((err) => {
- res.status(500).json({
- code: 2,
- status: err.message
- })
+ const streaming = await this.methods.generateStreamFromStreamkey(stream).catch((err) => {
+ console.error(err)
- return false
+ return null
})
if (streaming) {
- global.wsInterface.io.emit(`streaming.end`, {
- username: streaming.username,
- })
+ global.wsInterface.io.emit(`streaming.end`, streaming)
+
+ global.wsInterface.io.emit(`streaming.end.${streaming.username}`, streaming)
return res.json({
code: 0,
diff --git a/packages/server/src/models/index.js b/packages/server/src/models/index.js
index 5e0978a8..0462d351 100755
--- a/packages/server/src/models/index.js
+++ b/packages/server/src/models/index.js
@@ -37,6 +37,7 @@ export const Playlist = mongoose.model("Playlist", schemas.Playlist, "playlists"
// streamings
export const StreamingKey = mongoose.model("StreamingKey", schemas.StreamingKey, "streamingKeys")
export const StreamingInfo = mongoose.model("StreamingInfo", schemas.StreamingInfo, "streamingInfos")
+export const StreamingCategory = mongoose.model("StreamingCategory", schemas.StreamingCategory, "streamingCategories")
// others
export const FeaturedWallpaper = mongoose.model("FeaturedWallpaper", schemas.FeaturedWallpaper, "featuredWallpapers")
diff --git a/packages/server/src/schemas/index.js b/packages/server/src/schemas/index.js
index f76ebf53..47b3f69a 100755
--- a/packages/server/src/schemas/index.js
+++ b/packages/server/src/schemas/index.js
@@ -18,4 +18,5 @@ export { default as FeaturedWallpaper } from "./featuredWallpaper"
export { default as FeaturedEvent } from "./featuredEvent"
export { default as StreamingKey } from "./streamingKey"
-export { default as StreamingInfo } from "./streamingInfo"
\ No newline at end of file
+export { default as StreamingInfo } from "./streamingInfo"
+export { default as StreamingCategory } from "./streamingCategory"
\ No newline at end of file
diff --git a/packages/server/src/schemas/streamingCategory/index.js b/packages/server/src/schemas/streamingCategory/index.js
new file mode 100644
index 00000000..5a3d7beb
--- /dev/null
+++ b/packages/server/src/schemas/streamingCategory/index.js
@@ -0,0 +1,10 @@
+export default {
+ key: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+}
\ No newline at end of file
diff --git a/packages/wrapper/package.json b/packages/wrapper/package.json
index e982c218..3fe3d881 100755
--- a/packages/wrapper/package.json
+++ b/packages/wrapper/package.json
@@ -1,6 +1,6 @@
{
"name": "wrapper",
- "version": "0.31.1",
+ "version": "0.33.0",
"main": "./src/index.js",
"license": "MIT",
"scripts": {