diff --git a/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.jsx b/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.jsx index 8430825d..82984cee 100755 --- a/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.jsx +++ b/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.jsx @@ -15,10 +15,10 @@ import "./index.less" export default (props) => { const [L_Profiles, R_Profiles, E_Profiles, M_Profiles] = app.cores.api.useRequest(Livestream.getProfiles) - + const [profileData, setProfileData] = React.useState(null) const [selectedProfileId, setSelectedProfileId] = React.useState(null) - const [isConnected, setIsConnected] = React.useState(false) + const [loadingChanges, setLoadingChanges] = React.useState(false) React.useEffect(() => { if (R_Profiles) { @@ -30,23 +30,14 @@ export default (props) => { } }, [R_Profiles]) - if (E_Profiles) { - console.error(E_Profiles) + React.useEffect(() => { + if (selectedProfileId) { + console.log(R_Profiles) + setProfileData(R_Profiles.find((profile) => profile._id === selectedProfileId)) + } + }, [selectedProfileId]) - return - } - - if (L_Profiles) { - return - } - - const profileData = R_Profiles.find((profile) => profile._id === selectedProfileId) - - const handleCreateProfile = async (profile_name) => { + async function handleCreateProfile(profile_name) { if (!profile_name) { return false } @@ -70,7 +61,7 @@ export default (props) => { } } - const handleCurrentProfileDataUpdate = async (newProfileData) => { + async function handleCurrentProfileDataUpdate(newProfileData) { if (!profileData) { return } @@ -95,7 +86,7 @@ export default (props) => { } } - const handleCurrentProfileDelete = async () => { + async function handleCurrentProfileDelete() { if (!profileData) { return } @@ -126,7 +117,7 @@ export default (props) => { }) } - const onClickEditInfo = () => { + async function onClickEditInfo() { if (!profileData) { return } @@ -140,7 +131,7 @@ export default (props) => { }) } - const regenerateStreamingKey = async () => { + async function regenerateStreamingKey() { if (!profileData) { return } @@ -163,7 +154,58 @@ export default (props) => { }) } - return
+ async function updateOption(key, value) { + if (!profileData) { + return + } + + setLoadingChanges(`option:${key}`) + + const result = await Livestream.postProfile({ + profile_id: profileData._id, + profile_name: profileData.profile_name, + options: { + [key]: value + } + }).catch((err) => { + console.error(err) + app.message.error(`Failed to update option`) + setLoadingChanges(false) + + return false + }) + + if (result) { + console.log("Updated options >", result) + + setProfileData((prev) => { + return { + ...prev, + ...result, + } + }) + setLoadingChanges(false) + } + } + + if (E_Profiles) { + console.error(E_Profiles) + + return + } + + if (L_Profiles) { + return + } + + return
{

Additional options

- Enable DVR +

+ DVR +

+ + + This function will save a copy of your stream with its entire duration. + You can get this copy after finishing this livestream +
- Private mode +

+ Private mode +

+ + + When this is enabled, only users with the livestream url can access the stream. Your stream will not be visible on the app. + + + + You must restart the livestream to apply the changes. +
{ + updateOption("private", value) + }} + loading={loadingChanges === "option:private"} />
@@ -298,17 +366,12 @@ export default (props) => {

URL Information

- AAC URL (Only Audio) - -
- - {profileData?.addresses?.aac ?? "No AAC URL available"} - +
+

HLS URL

+

[6s~12s latency]

-
-
- HLS URL + This protocol is highly compatible with a multitude of devices and services. Recommended for general use.
@@ -318,7 +381,12 @@ export default (props) => {
- FLV URL +
+

FLV URL

+

[2s~5s latency]

+
+ + This protocol operates at better latency and quality than HLS, but is less compatible for most devices.
@@ -326,6 +394,21 @@ export default (props) => {
+ +
+
+

MP3 URL (Only Audio)

+

[2s ~ 5s latency]

+
+ + This protocol will only return an audio file. The maximum quality compatible with this codec will be used (320Kbps, 48KHz) + +
+ + {profileData?.addresses?.mp3 ?? "No MP3 URL available"} + +
+
diff --git a/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.less b/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.less index 6a5bc645..702652e6 100755 --- a/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.less +++ b/packages/app/src/pages/tv/tabs/livestreamControlPanel/index.less @@ -11,6 +11,10 @@ gap: 20px; + &[disabled]{ + pointer-events: none; + } + .inline_field { background-color: var(--background-color-accent); @@ -133,13 +137,48 @@ width: 100%; + gap: 7px; + .title { display: inline-flex; flex-direction: row; + align-items: center; justify-content: space-between; + svg { + font-size: 1rem; + margin: 0; + } + + p { + margin: 0; + } + width: 100%; + margin: 0; + } + + .label { + display: inline-flex; + flex-direction: row; + + align-items: center; + + gap: 8px; + + svg { + font-size: 1.3rem; + margin: 0; + } + + width: 100%; + margin: 0; + } + + .description { + font-size: 0.8rem; + margin: 0; } } } diff --git a/packages/server/src/controllers/StreamingController/endpoints/getProfilesVisibility.js b/packages/server/src/controllers/StreamingController/endpoints/getProfilesVisibility.js new file mode 100644 index 00000000..5d968b39 --- /dev/null +++ b/packages/server/src/controllers/StreamingController/endpoints/getProfilesVisibility.js @@ -0,0 +1,24 @@ +import { StreamingProfile } from "@shared-classes/DbModels" + +export default { + method: "GET", + route: "/profile/visibility", + middlewares: ["withAuthentication"], + fn: async (req, res) => { + let { ids } = req.query + + if (typeof ids === "string") { + ids = [ids] + } + + let visibilities = await StreamingProfile.find({ + _id: { $in: ids } + }) + + visibilities = visibilities.map((visibility) => { + return [visibility._id.toString(), visibility.options.private] + }) + + return res.json(visibilities) + } +} \ No newline at end of file diff --git a/packages/server/src/controllers/StreamingController/endpoints/getStreamingProfiles.js b/packages/server/src/controllers/StreamingController/endpoints/getStreamingProfiles.js index c8d8bac2..f92b53b1 100644 --- a/packages/server/src/controllers/StreamingController/endpoints/getStreamingProfiles.js +++ b/packages/server/src/controllers/StreamingController/endpoints/getStreamingProfiles.js @@ -34,7 +34,7 @@ export default { profile._id = profile._id.toString() - profile.stream_key = `${req.user.username}:${profile._id}?secret=${profile.stream_key}` + profile.stream_key = `${req.user.username}__${profile._id}?secret=${profile.stream_key}` return profile }) diff --git a/packages/server/src/controllers/StreamingController/endpoints/getStreams.js b/packages/server/src/controllers/StreamingController/endpoints/getStreams.js index b600c9e5..20546e2f 100755 --- a/packages/server/src/controllers/StreamingController/endpoints/getStreams.js +++ b/packages/server/src/controllers/StreamingController/endpoints/getStreams.js @@ -5,7 +5,7 @@ export default { route: "/streams", fn: async (req, res) => { if (req.query.username) { - const stream = await fetchRemoteStreams(`${req.query.username}${req.query.profile_id ? `:${req.query.profile_id}` : ""}`) + const stream = await fetchRemoteStreams(`${req.query.username}${req.query.profile_id ? `__${req.query.profile_id}` : ""}`) if (!stream) { return res.status(404).json({ diff --git a/packages/server/src/controllers/StreamingController/endpoints/handleStreamPublish.js b/packages/server/src/controllers/StreamingController/endpoints/handleStreamPublish.js index d84a74e5..27810578 100755 --- a/packages/server/src/controllers/StreamingController/endpoints/handleStreamPublish.js +++ b/packages/server/src/controllers/StreamingController/endpoints/handleStreamPublish.js @@ -30,7 +30,7 @@ export default { }) } - const [username, profile_id] = app.split("/")[1].split(":") + const [username, profile_id] = app.split("/")[1].split("__") if (user.username !== username) { return res.status(403).json({ diff --git a/packages/server/src/controllers/StreamingController/endpoints/postStreamingProfile.js b/packages/server/src/controllers/StreamingController/endpoints/postStreamingProfile.js index 03664217..3bedabce 100644 --- a/packages/server/src/controllers/StreamingController/endpoints/postStreamingProfile.js +++ b/packages/server/src/controllers/StreamingController/endpoints/postStreamingProfile.js @@ -1,6 +1,8 @@ import { StreamingProfile } from "@shared-classes/DbModels" import NewStreamingProfile from "@services/newStreamingProfile" +const AllowedChangesFields = ["profile_name", "info", "options"] + export default { method: "POST", route: "/streaming/profile", @@ -34,9 +36,11 @@ export default { if (currentProfile && profile_id) { // update the profile - currentProfile.profile_name = profile_name - currentProfile.info = info - currentProfile.options = options + AllowedChangesFields.forEach((field) => { + if (req.body[field]) { + currentProfile[field] = req.body[field] + } + }) await currentProfile.save() } else { diff --git a/packages/server/src/services/fetchRemoteStreams/index.js b/packages/server/src/services/fetchRemoteStreams/index.js index 58ec1ebc..564ced96 100644 --- a/packages/server/src/services/fetchRemoteStreams/index.js +++ b/packages/server/src/services/fetchRemoteStreams/index.js @@ -17,7 +17,6 @@ export default async (stream_id) => { url: apiURI, params: { stream: stream_id, - useFetch: true, } }).catch((err) => { console.error(err) @@ -34,10 +33,8 @@ export default async (stream_id) => { streamings = data } - streamings = streamings.map(async (stream) => { - const { video, audio, clients, name } = stream - - const profile_id = name.split(":")[1] + streamings = streamings.map(async (entry) => { + const { stream, profile_id } = entry let profile = await StreamingProfile.findById(profile_id) @@ -62,13 +59,10 @@ export default async (stream_id) => { return { profile_id: profile._id, info: profile.info, - name: name, + name: stream, streamUrl: `${user.username}?profile=${profile._id}`, - user, - video, - audio, - connectedClients: clients ?? 0, sources: lodash.pick(sources, ["rtmp", "hls", "flv", "aac"]), + user, } }) diff --git a/packages/server/src/utils/compose-streaming-sources/index.js b/packages/server/src/utils/compose-streaming-sources/index.js index c9e87d28..dca4b990 100644 --- a/packages/server/src/utils/compose-streaming-sources/index.js +++ b/packages/server/src/utils/compose-streaming-sources/index.js @@ -3,13 +3,13 @@ const streamingServerAPIAddress = process.env.STREAMING_API_SERVER ?? "" const streamingServerAPIUri = `${streamingServerAPIAddress.startsWith("https") ? "https" : "http"}://${streamingServerAPIAddress.split("://")[1]}` export default (username, profile_id) => { - const streamId = `${username}${profile_id ? `:${profile_id}` : ""}` + const streamId = `${username}${profile_id ? `__${profile_id}` : ""}` return { ingest: process.env.STREAMING_INGEST_SERVER, rtmp: `${streamingServerAPIUri}/${streamId}`, - hls: `${streamingServerAPIUri}/stream/${streamId}/src.m3u8`, - flv: `${streamingServerAPIUri}/stream/${streamId}/src.flv`, - aac: `${streamingServerAPIUri}/stream/${streamId}/src.aac`, + hls: `${streamingServerAPIUri}/stream/hls/${streamId}`, + flv: `${streamingServerAPIUri}/stream/flv/${streamId}`, + mp3: `${streamingServerAPIUri}/stream/mp3/${streamId}`, } } \ No newline at end of file diff --git a/shared/classes/DbModels/streamingProfile/index.js b/shared/classes/DbModels/streamingProfile/index.js index b07073ac..1332700c 100644 --- a/shared/classes/DbModels/streamingProfile/index.js +++ b/shared/classes/DbModels/streamingProfile/index.js @@ -27,6 +27,7 @@ export default { options: { type: Object, default: { + connection_protected: true, private: false, chatEnabled: true, drvEnabled: false,