update to streaming service version 5_b7

This commit is contained in:
SrGooglo 2023-12-19 21:55:11 +00:00
parent 3ce53e9dbd
commit d398173010
10 changed files with 201 additions and 56 deletions

View File

@ -15,10 +15,10 @@ import "./index.less"
export default (props) => { export default (props) => {
const [L_Profiles, R_Profiles, E_Profiles, M_Profiles] = app.cores.api.useRequest(Livestream.getProfiles) 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 [selectedProfileId, setSelectedProfileId] = React.useState(null)
const [isConnected, setIsConnected] = React.useState(false) const [isConnected, setIsConnected] = React.useState(false)
const [loadingChanges, setLoadingChanges] = React.useState(false)
React.useEffect(() => { React.useEffect(() => {
if (R_Profiles) { if (R_Profiles) {
@ -30,23 +30,14 @@ export default (props) => {
} }
}, [R_Profiles]) }, [R_Profiles])
if (E_Profiles) { React.useEffect(() => {
console.error(E_Profiles) if (selectedProfileId) {
console.log(R_Profiles)
return <antd.Result setProfileData(R_Profiles.find((profile) => profile._id === selectedProfileId))
status="error"
title="Failed to load profiles"
subTitle="Failed to load profiles, please try again later"
/>
} }
}, [selectedProfileId])
if (L_Profiles) { async function handleCreateProfile(profile_name) {
return <antd.Skeleton active />
}
const profileData = R_Profiles.find((profile) => profile._id === selectedProfileId)
const handleCreateProfile = async (profile_name) => {
if (!profile_name) { if (!profile_name) {
return false return false
} }
@ -70,7 +61,7 @@ export default (props) => {
} }
} }
const handleCurrentProfileDataUpdate = async (newProfileData) => { async function handleCurrentProfileDataUpdate(newProfileData) {
if (!profileData) { if (!profileData) {
return return
} }
@ -95,7 +86,7 @@ export default (props) => {
} }
} }
const handleCurrentProfileDelete = async () => { async function handleCurrentProfileDelete() {
if (!profileData) { if (!profileData) {
return return
} }
@ -126,7 +117,7 @@ export default (props) => {
}) })
} }
const onClickEditInfo = () => { async function onClickEditInfo() {
if (!profileData) { if (!profileData) {
return return
} }
@ -140,7 +131,7 @@ export default (props) => {
}) })
} }
const regenerateStreamingKey = async () => { async function regenerateStreamingKey() {
if (!profileData) { if (!profileData) {
return return
} }
@ -163,7 +154,58 @@ export default (props) => {
}) })
} }
return <div className="streamingControlPanel"> 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 <antd.Result
status="error"
title="Failed to load profiles"
subTitle="Failed to load profiles, please try again later"
/>
}
if (L_Profiles) {
return <antd.Skeleton active />
}
return <div
className="streamingControlPanel"
disabled={!profileData || loadingChanges}
>
<div className="streamingControlPanel_header_thumbnail"> <div className="streamingControlPanel_header_thumbnail">
<img <img
src={ src={
@ -272,23 +314,49 @@ export default (props) => {
<h2><Icons.Tool />Additional options</h2> <h2><Icons.Tool />Additional options</h2>
<div className="content"> <div className="content">
<span>Enable DVR</span> <p className="label">
<Icons.MdFiberDvr /> DVR
</p>
<span className="description">
This function will save a copy of your stream with its entire duration.
You can get this copy after finishing this livestream
</span>
<div className="value"> <div className="value">
<antd.Switch <antd.Switch
checked={profileData?.options?.dvr ?? false} checked={profileData?.options?.dvr ?? false}
onChange={false} onChange={false}
disabled
/> />
</div> </div>
</div> </div>
<div className="content"> <div className="content">
<span>Private mode</span> <p className="label">
<Icons.MdPrivateConnectivity /> Private mode
</p>
<span className="description">
When this is enabled, only users with the livestream url can access the stream. Your stream will not be visible on the app.
</span>
<span
className="description"
style={{
fontWeight: "bold",
}}
>
You must restart the livestream to apply the changes.
</span>
<div className="value"> <div className="value">
<antd.Switch <antd.Switch
checked={profileData?.options?.private ?? false} checked={profileData?.options?.private ?? false}
onChange={false} onChange={(value) => {
updateOption("private", value)
}}
loading={loadingChanges === "option:private"}
/> />
</div> </div>
</div> </div>
@ -298,17 +366,12 @@ export default (props) => {
<h2><Icons.Link /> URL Information</h2> <h2><Icons.Link /> URL Information</h2>
<div className="content"> <div className="content">
<span>AAC URL (Only Audio)</span> <div className="title">
<p>HLS URL</p>
<div className="inline_field"> <p>[6s~12s latency]</p>
<span>
{profileData?.addresses?.aac ?? "No AAC URL available"}
</span>
</div>
</div> </div>
<div className="content"> <span className="description">This protocol is highly compatible with a multitude of devices and services. Recommended for general use.</span>
<span>HLS URL</span>
<div className="inline_field"> <div className="inline_field">
<span> <span>
@ -318,7 +381,12 @@ export default (props) => {
</div> </div>
<div className="content"> <div className="content">
<span>FLV URL</span> <div className="title">
<p>FLV URL</p>
<p>[2s~5s latency]</p>
</div>
<span className="description">This protocol operates at better latency and quality than HLS, but is less compatible for most devices.</span>
<div className="inline_field"> <div className="inline_field">
<span> <span>
@ -326,6 +394,21 @@ export default (props) => {
</span> </span>
</div> </div>
</div> </div>
<div className="content">
<div className="title">
<p>MP3 URL (Only Audio)</p>
<p>[2s ~ 5s latency]</p>
</div>
<span className="description">This protocol will only return an audio file. The maximum quality compatible with this codec will be used (320Kbps, 48KHz)</span>
<div className="inline_field">
<span>
{profileData?.addresses?.mp3 ?? "No MP3 URL available"}
</span>
</div>
</div>
</div> </div>
<div className="streaming_configs_panel"> <div className="streaming_configs_panel">

View File

@ -11,6 +11,10 @@
gap: 20px; gap: 20px;
&[disabled]{
pointer-events: none;
}
.inline_field { .inline_field {
background-color: var(--background-color-accent); background-color: var(--background-color-accent);
@ -133,13 +137,48 @@
width: 100%; width: 100%;
gap: 7px;
.title { .title {
display: inline-flex; display: inline-flex;
flex-direction: row; flex-direction: row;
align-items: center;
justify-content: space-between; justify-content: space-between;
svg {
font-size: 1rem;
margin: 0;
}
p {
margin: 0;
}
width: 100%; 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;
} }
} }
} }

View File

@ -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)
}
}

View File

@ -34,7 +34,7 @@ export default {
profile._id = profile._id.toString() 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 return profile
}) })

View File

@ -5,7 +5,7 @@ export default {
route: "/streams", route: "/streams",
fn: async (req, res) => { fn: async (req, res) => {
if (req.query.username) { 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) { if (!stream) {
return res.status(404).json({ return res.status(404).json({

View File

@ -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) { if (user.username !== username) {
return res.status(403).json({ return res.status(403).json({

View File

@ -1,6 +1,8 @@
import { StreamingProfile } from "@shared-classes/DbModels" import { StreamingProfile } from "@shared-classes/DbModels"
import NewStreamingProfile from "@services/newStreamingProfile" import NewStreamingProfile from "@services/newStreamingProfile"
const AllowedChangesFields = ["profile_name", "info", "options"]
export default { export default {
method: "POST", method: "POST",
route: "/streaming/profile", route: "/streaming/profile",
@ -34,9 +36,11 @@ export default {
if (currentProfile && profile_id) { if (currentProfile && profile_id) {
// update the profile // update the profile
currentProfile.profile_name = profile_name AllowedChangesFields.forEach((field) => {
currentProfile.info = info if (req.body[field]) {
currentProfile.options = options currentProfile[field] = req.body[field]
}
})
await currentProfile.save() await currentProfile.save()
} else { } else {

View File

@ -17,7 +17,6 @@ export default async (stream_id) => {
url: apiURI, url: apiURI,
params: { params: {
stream: stream_id, stream: stream_id,
useFetch: true,
} }
}).catch((err) => { }).catch((err) => {
console.error(err) console.error(err)
@ -34,10 +33,8 @@ export default async (stream_id) => {
streamings = data streamings = data
} }
streamings = streamings.map(async (stream) => { streamings = streamings.map(async (entry) => {
const { video, audio, clients, name } = stream const { stream, profile_id } = entry
const profile_id = name.split(":")[1]
let profile = await StreamingProfile.findById(profile_id) let profile = await StreamingProfile.findById(profile_id)
@ -62,13 +59,10 @@ export default async (stream_id) => {
return { return {
profile_id: profile._id, profile_id: profile._id,
info: profile.info, info: profile.info,
name: name, name: stream,
streamUrl: `${user.username}?profile=${profile._id}`, streamUrl: `${user.username}?profile=${profile._id}`,
user,
video,
audio,
connectedClients: clients ?? 0,
sources: lodash.pick(sources, ["rtmp", "hls", "flv", "aac"]), sources: lodash.pick(sources, ["rtmp", "hls", "flv", "aac"]),
user,
} }
}) })

View File

@ -3,13 +3,13 @@ const streamingServerAPIAddress = process.env.STREAMING_API_SERVER ?? ""
const streamingServerAPIUri = `${streamingServerAPIAddress.startsWith("https") ? "https" : "http"}://${streamingServerAPIAddress.split("://")[1]}` const streamingServerAPIUri = `${streamingServerAPIAddress.startsWith("https") ? "https" : "http"}://${streamingServerAPIAddress.split("://")[1]}`
export default (username, profile_id) => { export default (username, profile_id) => {
const streamId = `${username}${profile_id ? `:${profile_id}` : ""}` const streamId = `${username}${profile_id ? `__${profile_id}` : ""}`
return { return {
ingest: process.env.STREAMING_INGEST_SERVER, ingest: process.env.STREAMING_INGEST_SERVER,
rtmp: `${streamingServerAPIUri}/${streamId}`, rtmp: `${streamingServerAPIUri}/${streamId}`,
hls: `${streamingServerAPIUri}/stream/${streamId}/src.m3u8`, hls: `${streamingServerAPIUri}/stream/hls/${streamId}`,
flv: `${streamingServerAPIUri}/stream/${streamId}/src.flv`, flv: `${streamingServerAPIUri}/stream/flv/${streamId}`,
aac: `${streamingServerAPIUri}/stream/${streamId}/src.aac`, mp3: `${streamingServerAPIUri}/stream/mp3/${streamId}`,
} }
} }

View File

@ -27,6 +27,7 @@ export default {
options: { options: {
type: Object, type: Object,
default: { default: {
connection_protected: true,
private: false, private: false,
chatEnabled: true, chatEnabled: true,
drvEnabled: false, drvEnabled: false,