diff --git a/docs/configuration.md b/docs/configuration.md
index f853e418..2b282d1c 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -186,32 +186,6 @@ editing the `conf` file in a text editor. Use the examples as reference.
-### channels
-
-
-
-
Description
-
- Sunshine can support multiple clients streaming simultaneously,
- at the cost of higher CPU and GPU usage.
- @note{All connected clients share control of the same streaming session.}
- @warning{Some hardware encoders may have limitations that reduce performance with multiple streams.}
-
-
-
-
Default
-
@code{}
- 1
- @endcode
-
-
-
Example
-
@code{}
- channels = 1
- @endcode
-
-
-
### global_prep_cmd
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index da91630d..133a063b 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -165,6 +165,14 @@ high as long as the encoder is used.
### Gamescope compatibility
Some users have reported stuttering issues when streaming games running within Gamescope.
+### Occasional flickering in KDE
+
+The `blur` plugin causes flickering during streaming for some people. Disable it from the
+KDE System Settings or from the commandline:
+```bash
+qdbus org.kde.KWin /Effects unloadEffect blur
+```
+
## macOS
### Dynamic session lookup failed
diff --git a/package.json b/package.json
index 78d6994c..0b29b400 100644
--- a/package.json
+++ b/package.json
@@ -13,9 +13,9 @@
"vue-i18n": "9.14.0"
},
"devDependencies": {
- "@vitejs/plugin-vue": "4.6.2",
- "serve": "14.2.3",
- "vite": "4.5.2",
- "vite-plugin-ejs": "1.6.4"
+ "@vitejs/plugin-vue": "5.1.4",
+ "serve": "14.2.4",
+ "vite": "5.3.6",
+ "vite-plugin-ejs": "1.7.0"
}
}
diff --git a/src/config.cpp b/src/config.cpp
index fa32fb26..d9f59b12 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -403,7 +403,6 @@ namespace config {
APPS_JSON_PATH,
20, // fecPercentage
- 1, // channels
ENCRYPTION_MODE_NEVER, // lan_encryption_mode
ENCRYPTION_MODE_OPPORTUNISTIC, // wan_encryption_mode
@@ -1096,8 +1095,6 @@ namespace config {
stream.ping_timeout = std::chrono::milliseconds(to);
}
- int_between_f(vars, "channels", stream.channels, { 1, std::numeric_limits::max() });
-
int_between_f(vars, "lan_encryption_mode", stream.lan_encryption_mode, { 0, 2 });
int_between_f(vars, "wan_encryption_mode", stream.wan_encryption_mode, { 0, 2 });
diff --git a/src/config.h b/src/config.h
index 5a3ab9f1..74c89c21 100644
--- a/src/config.h
+++ b/src/config.h
@@ -101,9 +101,6 @@ namespace config {
int fec_percentage;
- // max unique instances of video and audio streams
- int channels;
-
// Video encryption settings for LAN and WAN streams
int lan_encryption_mode;
int wan_encryption_mode;
diff --git a/src/network.cpp b/src/network.cpp
index 6012e32e..dcfdb0e9 100644
--- a/src/network.cpp
+++ b/src/network.cpp
@@ -163,7 +163,7 @@ namespace net {
}
host_t
- host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port) {
+ host_create(af_e af, ENetAddress &addr, std::uint16_t port) {
static std::once_flag enet_init_flag;
std::call_once(enet_init_flag, []() {
enet_initialize();
@@ -173,7 +173,8 @@ namespace net {
enet_address_set_host(&addr, any_addr.data());
enet_address_set_port(&addr, port);
- auto host = host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, peers, 0, 0, 0) };
+ // Maximum of 128 clients, which should be enough for anyone
+ auto host = host_t { enet_host_create(af == IPV4 ? AF_INET : AF_INET6, &addr, 128, 0, 0, 0) };
// Enable opportunistic QoS tagging (automatically disables if the network appears to drop tagged packets)
enet_socket_set_option(host->socket, ENET_SOCKOPT_QOS, 1);
diff --git a/src/network.h b/src/network.h
index dbae81b9..98de905b 100644
--- a/src/network.h
+++ b/src/network.h
@@ -53,7 +53,7 @@ namespace net {
from_address(const std::string_view &view);
host_t
- host_create(af_e af, ENetAddress &addr, std::size_t peers, std::uint16_t port);
+ host_create(af_e af, ENetAddress &addr, std::uint16_t port);
/**
* @brief Get the address family enum value from a string.
diff --git a/src/nvhttp.cpp b/src/nvhttp.cpp
index 7782d81c..5c21bc60 100644
--- a/src/nvhttp.cpp
+++ b/src/nvhttp.cpp
@@ -1004,14 +1004,6 @@ namespace nvhttp {
return;
}
- if (rtsp_stream::session_count() == config::stream.channels) {
- tree.put("root.resume", 0);
- tree.put("root..status_code", 503);
- tree.put("root..status_message", "The host's concurrent stream limit has been reached. Stop an existing stream or increase the 'Channels' value in the Sunshine Web UI.");
-
- return;
- }
-
auto args = request->parse_query_string();
if (
args.find("rikey"s) == std::end(args) ||
@@ -1112,16 +1104,6 @@ namespace nvhttp {
return;
}
- // It is possible that due a race condition that this if-statement gives a false negative,
- // that is automatically resolved in rtsp_server_t
- if (rtsp_stream::session_count() == config::stream.channels) {
- tree.put("root.resume", 0);
- tree.put("root..status_code", 503);
- tree.put("root..status_message", "The host's concurrent stream limit has been reached. Stop an existing stream or increase the 'Channels' value in the Sunshine Web UI.");
-
- return;
- }
-
auto current_appid = proc::proc.running();
if (current_appid == 0) {
tree.put("root.resume", 0);
@@ -1213,19 +1195,11 @@ namespace nvhttp {
return;
}
- // It is possible that due a race condition that this if-statement gives a false positive,
- // the client should try again
- if (rtsp_stream::session_count() != 0) {
- tree.put("root.resume", 0);
- tree.put("root..status_code", 503);
- tree.put("root..status_message", "All sessions must be disconnected before quitting");
-
- return;
- }
-
tree.put("root.cancel", 1);
tree.put("root..status_code", 200);
+ rtsp_stream::terminate_sessions();
+
if (proc::proc.running() > 0) {
proc::proc.terminate();
}
diff --git a/src/rtsp.cpp b/src/rtsp.cpp
index c6e1ccfd..f985f234 100644
--- a/src/rtsp.cpp
+++ b/src/rtsp.cpp
@@ -26,6 +26,7 @@ extern "C" {
#include "sync.h"
#include "video.h"
+#include
#include
namespace asio = boost::asio;
@@ -417,13 +418,6 @@ namespace rtsp_stream {
int
bind(net::af_e af, std::uint16_t port, boost::system::error_code &ec) {
- {
- auto lg = _session_slots.lock();
-
- _session_slots->resize(config::stream.channels);
- _slot_count = config::stream.channels;
- }
-
acceptor.open(af == net::IPV4 ? tcp::v4() : tcp::v6(), ec);
if (ec) {
return -1;
@@ -529,7 +523,6 @@ namespace rtsp_stream {
}
raised_timeout = now + config::stream.ping_timeout;
- --_slot_count;
launch_event.raise(std::move(launch_session));
}
@@ -552,9 +545,14 @@ namespace rtsp_stream {
}
}
+ /**
+ * @brief Get the number of active sessions.
+ * @return Count of active sessions.
+ */
int
- session_count() const {
- return config::stream.channels - _slot_count;
+ session_count() {
+ auto lg = _session_slots.lock();
+ return _session_slots->size();
}
safe::event_t> launch_event;
@@ -573,20 +571,21 @@ namespace rtsp_stream {
auto discarded = launch_event.pop(0s);
if (discarded) {
BOOST_LOG(debug) << "Event timeout: "sv << discarded->unique_id;
- ++_slot_count;
}
}
auto lg = _session_slots.lock();
- for (auto &slot : *_session_slots) {
- if (slot && (all || stream::session::state(*slot) == stream::session::state_e::STOPPING)) {
- stream::session::stop(*slot);
- stream::session::join(*slot);
+ for (auto i = _session_slots->begin(); i != _session_slots->end();) {
+ auto &slot = *(*i);
+ if (all || stream::session::state(slot) == stream::session::state_e::STOPPING) {
+ stream::session::stop(slot);
+ stream::session::join(slot);
- slot.reset();
-
- ++_slot_count;
+ i = _session_slots->erase(i);
+ }
+ else {
+ i++;
}
}
@@ -595,27 +594,25 @@ namespace rtsp_stream {
}
}
+ /**
+ * @brief Removes the provided session from the set of sessions.
+ * @param session The session to remove.
+ */
void
- clear(std::shared_ptr *session_p) {
+ remove(const std::shared_ptr &session) {
auto lg = _session_slots.lock();
-
- session_p->reset();
-
- ++_slot_count;
+ _session_slots->erase(session);
}
- std::shared_ptr *
- accept(std::shared_ptr &session) {
+ /**
+ * @brief Inserts the provided session into the set of sessions.
+ * @param session The session to insert.
+ */
+ void
+ insert(const std::shared_ptr &session) {
auto lg = _session_slots.lock();
-
- for (auto &slot : *_session_slots) {
- if (!slot) {
- slot = session;
- return &slot;
- }
- }
-
- return nullptr;
+ _session_slots->emplace(session);
+ BOOST_LOG(info) << "New streaming session started [active sessions: "sv << _session_slots->size() << ']';
}
std::shared_ptr
@@ -646,10 +643,9 @@ namespace rtsp_stream {
private:
std::unordered_map _map_cmd_cb;
- sync_util::sync_t>> _session_slots;
+ sync_util::sync_t>> _session_slots;
std::chrono::steady_clock::time_point raised_timeout;
- int _slot_count;
boost::asio::io_service ios;
tcp::acceptor acceptor { ios };
@@ -687,6 +683,11 @@ namespace rtsp_stream {
return server.get_all_session_uuids();
}
+ void
+ terminate_sessions() {
+ server.clear(true);
+ }
+
int
send(tcp::socket &sock, const std::string_view &sv) {
std::size_t bytes_send = 0;
@@ -1145,19 +1146,12 @@ namespace rtsp_stream {
}
auto stream_session = stream::session::alloc(config, session);
-
- auto slot = server->accept(stream_session);
- if (!slot) {
- BOOST_LOG(info) << "Ran out of slots for client from ["sv << ']';
-
- respond(sock, session, &option, 503, "Service Unavailable", req->sequenceNumber, {});
- return;
- }
+ server->insert(stream_session);
if (stream::session::start(*stream_session, sock.remote_endpoint().address().to_string())) {
BOOST_LOG(error) << "Failed to start a streaming session"sv;
- server->clear(slot);
+ server->remove(stream_session);
respond(sock, session, &option, 500, "Internal Server Error", req->sequenceNumber, {});
return;
}
diff --git a/src/rtsp.h b/src/rtsp.h
index 0792756c..e1111796 100644
--- a/src/rtsp.h
+++ b/src/rtsp.h
@@ -63,6 +63,10 @@ namespace rtsp_stream {
void
launch_session_clear(uint32_t launch_session_id);
+ /**
+ * @brief Get the number of active sessions.
+ * @return Count of active sessions.
+ */
int
session_count();
@@ -72,6 +76,12 @@ namespace rtsp_stream {
std::list
get_all_session_uuids();
+ /**
+ * @brief Terminates all running streaming sessions.
+ */
+ void
+ terminate_sessions();
+
void
rtpThread();
diff --git a/src/stream.cpp b/src/stream.cpp
index 2526e5c3..0a2b6066 100644
--- a/src/stream.cpp
+++ b/src/stream.cpp
@@ -259,7 +259,7 @@ namespace stream {
public:
int
bind(net::af_e address_family, std::uint16_t port) {
- _host = net::host_create(address_family, _addr, config::stream.channels, port);
+ _host = net::host_create(address_family, _addr, port);
return !(bool) _host;
}
diff --git a/src_assets/common/assets/web/config.html b/src_assets/common/assets/web/config.html
index e53d56b6..073297dd 100644
--- a/src_assets/common/assets/web/config.html
+++ b/src_assets/common/assets/web/config.html
@@ -219,7 +219,6 @@
id: "advanced",
name: "Advanced",
options: {
- "channels": 1,
"fec_percentage": 20,
"qp": 28,
"min_threads": 2,
diff --git a/src_assets/common/assets/web/configs/tabs/General.vue b/src_assets/common/assets/web/configs/tabs/General.vue
index 28a2ad9b..692b6816 100644
--- a/src_assets/common/assets/web/configs/tabs/General.vue
+++ b/src_assets/common/assets/web/configs/tabs/General.vue
@@ -89,16 +89,6 @@ onMounted(() => {