aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortimminator <150205162+timminator@users.noreply.github.com>2025-03-28 00:59:08 +0100
committerGitHub <noreply@github.com>2025-03-27 17:59:08 -0600
commit181a37a8cd512a46dfc5af55c43ef1468d206d75 (patch)
treeee3c5c9f04eee538155341ec7ef0119ed3013fa7
parentae4b35da462ad569bdc6f10df1cc8095cb1466e5 (diff)
Fix consumer count off by one when closing a browser tab with a livestream that is transcoding (#13220)
Rework Implementation Fix review issues Add missing nullorempty check Fix closely related #13721
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs87
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs9
3 files changed, 87 insertions, 17 deletions
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index 42f7deca1..ac3e10594 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -64,6 +64,9 @@ namespace Emby.Server.Implementations.Session
private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections
= new(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, string>> _activeLiveStreamSessions
+ = new(StringComparer.OrdinalIgnoreCase);
+
private Timer _idleTimer;
private Timer _inactiveTimer;
@@ -311,7 +314,7 @@ namespace Emby.Server.Implementations.Session
_activeConnections.TryRemove(key, out _);
if (!string.IsNullOrEmpty(session.PlayState?.LiveStreamId))
{
- await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false);
+ await CloseLiveStreamIfNeededAsync(session.PlayState.LiveStreamId, session.Id).ConfigureAwait(false);
}
await OnSessionEnded(session).ConfigureAwait(false);
@@ -319,6 +322,42 @@ namespace Emby.Server.Implementations.Session
}
/// <inheritdoc />
+ public async Task CloseLiveStreamIfNeededAsync(string liveStreamId, string sessionIdOrPlaySessionId)
+ {
+ bool liveStreamNeedsToBeClosed = false;
+
+ if (_activeLiveStreamSessions.TryGetValue(liveStreamId, out var activeSessionMappings))
+ {
+ if (activeSessionMappings.TryRemove(sessionIdOrPlaySessionId, out var correspondingId))
+ {
+ if (!string.IsNullOrEmpty(correspondingId))
+ {
+ activeSessionMappings.TryRemove(correspondingId, out _);
+ }
+
+ liveStreamNeedsToBeClosed = true;
+ }
+
+ if (activeSessionMappings.IsEmpty)
+ {
+ _activeLiveStreamSessions.TryRemove(liveStreamId, out _);
+ }
+ }
+
+ if (liveStreamNeedsToBeClosed)
+ {
+ try
+ {
+ await _mediaSourceManager.CloseLiveStream(liveStreamId).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error closing live stream");
+ }
+ }
+ }
+
+ /// <inheritdoc />
public async ValueTask ReportSessionEnded(string sessionId)
{
CheckDisposed();
@@ -737,6 +776,11 @@ namespace Emby.Server.Implementations.Session
}
}
+ if (!string.IsNullOrEmpty(info.LiveStreamId))
+ {
+ UpdateLiveStreamActiveSessionMappings(info.LiveStreamId, info.SessionId, info.PlaySessionId);
+ }
+
var eventArgs = new PlaybackStartEventArgs
{
Item = libraryItem,
@@ -794,6 +838,32 @@ namespace Emby.Server.Implementations.Session
return OnPlaybackProgress(info, false);
}
+ private void UpdateLiveStreamActiveSessionMappings(string liveStreamId, string sessionId, string playSessionId)
+ {
+ var activeSessionMappings = _activeLiveStreamSessions.GetOrAdd(liveStreamId, _ => new ConcurrentDictionary<string, string>());
+
+ if (!string.IsNullOrEmpty(playSessionId))
+ {
+ if (!activeSessionMappings.TryGetValue(sessionId, out var currentPlaySessionId) || currentPlaySessionId != playSessionId)
+ {
+ if (!string.IsNullOrEmpty(currentPlaySessionId))
+ {
+ activeSessionMappings.TryRemove(currentPlaySessionId, out _);
+ }
+
+ activeSessionMappings[sessionId] = playSessionId;
+ activeSessionMappings[playSessionId] = sessionId;
+ }
+ }
+ else
+ {
+ if (!activeSessionMappings.TryGetValue(sessionId, out _))
+ {
+ activeSessionMappings[sessionId] = string.Empty;
+ }
+ }
+ }
+
/// <summary>
/// Used to report playback progress for an item.
/// </summary>
@@ -834,6 +904,11 @@ namespace Emby.Server.Implementations.Session
}
}
+ if (!string.IsNullOrEmpty(info.LiveStreamId))
+ {
+ UpdateLiveStreamActiveSessionMappings(info.LiveStreamId, info.SessionId, info.PlaySessionId);
+ }
+
var eventArgs = new PlaybackProgressEventArgs
{
Item = libraryItem,
@@ -1016,14 +1091,7 @@ namespace Emby.Server.Implementations.Session
if (!string.IsNullOrEmpty(info.LiveStreamId))
{
- try
- {
- await _mediaSourceManager.CloseLiveStream(info.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error closing live stream");
- }
+ await CloseLiveStreamIfNeededAsync(info.LiveStreamId, session.Id).ConfigureAwait(false);
}
var eventArgs = new PlaybackStopEventArgs
@@ -2071,6 +2139,7 @@ namespace Emby.Server.Implementations.Session
}
_activeConnections.Clear();
+ _activeLiveStreamSessions.Clear();
}
}
}
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index 21131e6b5..47bcfdb6e 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -342,5 +342,13 @@ namespace MediaBrowser.Controller.Session
Task RevokeUserTokens(Guid userId, string currentAccessToken);
Task CloseIfNeededAsync(SessionInfo session);
+
+ /// <summary>
+ /// Used to close the livestream if needed.
+ /// </summary>
+ /// <param name="liveStreamId">The livestream id.</param>
+ /// <param name="sessionIdOrPlaySessionId">The session id or playsession id.</param>
+ /// <returns>Task.</returns>
+ Task CloseLiveStreamIfNeededAsync(string liveStreamId, string sessionIdOrPlaySessionId);
}
}
diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
index 85bb862c7..c7f9cf2cc 100644
--- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
+++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs
@@ -242,14 +242,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
{
- try
- {
- await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error closing live stream for {Path}", job.Path);
- }
+ await _sessionManager.CloseLiveStreamIfNeededAsync(job.LiveStreamId, job.PlaySessionId).ConfigureAwait(false);
}
}