aboutsummaryrefslogtreecommitdiff
path: root/Emby.Dlna/PlayTo
diff options
context:
space:
mode:
authorPatrick Barron <barronpm@gmail.com>2023-11-09 14:45:16 -0500
committerPatrick Barron <barronpm@gmail.com>2023-11-15 20:53:44 -0500
commitf1aba6b95230474d47c580071370c7dbd00eba13 (patch)
tree4fdc0131e7ae17c724e5bcda8d1e8878475a6461 /Emby.Dlna/PlayTo
parent01fd42cf9555d85469c07ce3d0c0e5842359eb2b (diff)
Remove Emby.Dlna
Diffstat (limited to 'Emby.Dlna/PlayTo')
-rw-r--r--Emby.Dlna/PlayTo/Device.cs1264
-rw-r--r--Emby.Dlna/PlayTo/DeviceInfo.cs66
-rw-r--r--Emby.Dlna/PlayTo/DlnaHttpClient.cs137
-rw-r--r--Emby.Dlna/PlayTo/MediaChangedEventArgs.cs19
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs980
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs258
-rw-r--r--Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs16
-rw-r--r--Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs16
-rw-r--r--Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs16
-rw-r--r--Emby.Dlna/PlayTo/PlaylistItem.cs19
-rw-r--r--Emby.Dlna/PlayTo/PlaylistItemFactory.cs70
-rw-r--r--Emby.Dlna/PlayTo/TransportCommands.cs181
-rw-r--r--Emby.Dlna/PlayTo/TransportState.cs16
-rw-r--r--Emby.Dlna/PlayTo/UpnpContainer.cs25
-rw-r--r--Emby.Dlna/PlayTo/uBaseObject.cs63
-rw-r--r--Emby.Dlna/PlayTo/uPnpNamespaces.cs67
16 files changed, 0 insertions, 3213 deletions
diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
deleted file mode 100644
index 18fa196508..0000000000
--- a/Emby.Dlna/PlayTo/Device.cs
+++ /dev/null
@@ -1,1264 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net.Http;
-using System.Security;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
-using Emby.Dlna.Common;
-using Emby.Dlna.Ssdp;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Dlna.PlayTo
-{
- public class Device : IDisposable
- {
- private readonly IHttpClientFactory _httpClientFactory;
-
- private readonly ILogger _logger;
-
- private readonly object _timerLock = new object();
- private Timer? _timer;
- private int _muteVol;
- private int _volume;
- private DateTime _lastVolumeRefresh;
- private bool _volumeRefreshActive;
- private int _connectFailureCount;
- private bool _disposed;
-
- public Device(DeviceInfo deviceProperties, IHttpClientFactory httpClientFactory, ILogger logger)
- {
- Properties = deviceProperties;
- _httpClientFactory = httpClientFactory;
- _logger = logger;
- }
-
- public event EventHandler<PlaybackStartEventArgs>? PlaybackStart;
-
- public event EventHandler<PlaybackProgressEventArgs>? PlaybackProgress;
-
- public event EventHandler<PlaybackStoppedEventArgs>? PlaybackStopped;
-
- public event EventHandler<MediaChangedEventArgs>? MediaChanged;
-
- public DeviceInfo Properties { get; set; }
-
- public bool IsMuted { get; set; }
-
- public int Volume
- {
- get
- {
- RefreshVolumeIfNeeded().GetAwaiter().GetResult();
- return _volume;
- }
-
- set => _volume = value;
- }
-
- public TimeSpan? Duration { get; set; }
-
- public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
-
- public TransportState TransportState { get; private set; }
-
- public bool IsPlaying => TransportState == TransportState.PLAYING;
-
- public bool IsPaused => TransportState == TransportState.PAUSED_PLAYBACK;
-
- public bool IsStopped => TransportState == TransportState.STOPPED;
-
- public Action? OnDeviceUnavailable { get; set; }
-
- private TransportCommands? AvCommands { get; set; }
-
- private TransportCommands? RendererCommands { get; set; }
-
- public UBaseObject? CurrentMediaInfo { get; private set; }
-
- public void Start()
- {
- _logger.LogDebug("Dlna Device.Start");
- _timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
- }
-
- private Task RefreshVolumeIfNeeded()
- {
- if (_volumeRefreshActive
- && DateTime.UtcNow >= _lastVolumeRefresh.AddSeconds(5))
- {
- _lastVolumeRefresh = DateTime.UtcNow;
- return RefreshVolume();
- }
-
- return Task.CompletedTask;
- }
-
- private async Task RefreshVolume(CancellationToken cancellationToken = default)
- {
- if (_disposed)
- {
- return;
- }
-
- try
- {
- await GetVolume(cancellationToken).ConfigureAwait(false);
- await GetMute(cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error updating device volume info for {DeviceName}", Properties.Name);
- }
- }
-
- private void RestartTimer(bool immediate = false)
- {
- lock (_timerLock)
- {
- if (_disposed)
- {
- return;
- }
-
- _volumeRefreshActive = true;
-
- var time = immediate ? 100 : 10000;
- _timer?.Change(time, Timeout.Infinite);
- }
- }
-
- /// <summary>
- /// Restarts the timer in inactive mode.
- /// </summary>
- private void RestartTimerInactive()
- {
- lock (_timerLock)
- {
- if (_disposed)
- {
- return;
- }
-
- _volumeRefreshActive = false;
-
- _timer?.Change(Timeout.Infinite, Timeout.Infinite);
- }
- }
-
- public Task VolumeDown(CancellationToken cancellationToken)
- {
- var sendVolume = Math.Max(Volume - 5, 0);
-
- return SetVolume(sendVolume, cancellationToken);
- }
-
- public Task VolumeUp(CancellationToken cancellationToken)
- {
- var sendVolume = Math.Min(Volume + 5, 100);
-
- return SetVolume(sendVolume, cancellationToken);
- }
-
- public Task ToggleMute(CancellationToken cancellationToken)
- {
- if (IsMuted)
- {
- return Unmute(cancellationToken);
- }
-
- return Mute(cancellationToken);
- }
-
- public async Task Mute(CancellationToken cancellationToken)
- {
- var success = await SetMute(true, cancellationToken).ConfigureAwait(true);
-
- if (!success)
- {
- await SetVolume(0, cancellationToken).ConfigureAwait(false);
- }
- }
-
- public async Task Unmute(CancellationToken cancellationToken)
- {
- var success = await SetMute(false, cancellationToken).ConfigureAwait(true);
-
- if (!success)
- {
- var sendVolume = _muteVol <= 0 ? 20 : _muteVol;
-
- await SetVolume(sendVolume, cancellationToken).ConfigureAwait(false);
- }
- }
-
- private DeviceService? GetServiceRenderingControl()
- {
- var services = Properties.Services;
-
- return services.FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:RenderingControl:1", StringComparison.OrdinalIgnoreCase)) ??
- services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:RenderingControl", StringComparison.OrdinalIgnoreCase));
- }
-
- private DeviceService? GetAvTransportService()
- {
- var services = Properties.Services;
-
- return services.FirstOrDefault(s => string.Equals(s.ServiceType, "urn:schemas-upnp-org:service:AVTransport:1", StringComparison.OrdinalIgnoreCase)) ??
- services.FirstOrDefault(s => (s.ServiceType ?? string.Empty).StartsWith("urn:schemas-upnp-org:service:AVTransport", StringComparison.OrdinalIgnoreCase));
- }
-
- private async Task<bool> SetMute(bool mute, CancellationToken cancellationToken)
- {
- var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetMute");
- if (command is null)
- {
- return false;
- }
-
- var service = GetServiceRenderingControl();
-
- if (service is null)
- {
- return false;
- }
-
- _logger.LogDebug("Setting mute");
- var value = mute ? 1 : 0;
-
- await new DlnaHttpClient(_logger, _httpClientFactory)
- .SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- rendererCommands!.BuildPost(command, service.ServiceType, value), // null checked above
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
-
- IsMuted = mute;
-
- return true;
- }
-
- /// <summary>
- /// Sets volume on a scale of 0-100.
- /// </summary>
- /// <param name="value">The volume on a scale of 0-100.</param>
- /// <param name="cancellationToken">The cancellation token to cancel operation.</param>
- /// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
- public async Task SetVolume(int value, CancellationToken cancellationToken)
- {
- var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetVolume");
- if (command is null)
- {
- return;
- }
-
- var service = GetServiceRenderingControl() ?? throw new InvalidOperationException("Unable to find service");
-
- // Set it early and assume it will succeed
- // Remote control will perform better
- Volume = value;
-
- await new DlnaHttpClient(_logger, _httpClientFactory)
- .SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- rendererCommands!.BuildPost(command, service.ServiceType, value), // null checked above
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
- }
-
- public async Task Seek(TimeSpan value, CancellationToken cancellationToken)
- {
- var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Seek");
- if (command is null)
- {
- return;
- }
-
- var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
- await new DlnaHttpClient(_logger, _httpClientFactory)
- .SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- avCommands!.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"), // null checked above
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
-
- RestartTimer(true);
- }
-
- public async Task SetAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken)
- {
- var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- url = url.Replace("&", "&amp;", StringComparison.Ordinal);
-
- _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
-
- var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI");
- if (command is null)
- {
- return;
- }
-
- var dictionary = new Dictionary<string, string>
- {
- { "CurrentURI", url },
- { "CurrentURIMetaData", CreateDidlMeta(metaData) }
- };
-
- var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
- var post = avCommands!.BuildPost(command, service.ServiceType, url, dictionary); // null checked above
- await new DlnaHttpClient(_logger, _httpClientFactory)
- .SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- post,
- header: header,
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
-
- await Task.Delay(50, cancellationToken).ConfigureAwait(false);
-
- try
- {
- await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
- }
- catch
- {
- // Some devices will throw an error if you tell it to play when it's already playing
- // Others won't
- }
-
- RestartTimer(true);
- }
-
- /*
- * SetNextAvTransport is used to specify to the DLNA device what is the next track to play.
- * Without that information, the next track command on the device does not work.
- */
- public async Task SetNextAvTransport(string url, string? header, string metaData, CancellationToken cancellationToken = default)
- {
- var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- url = url.Replace("&", "&amp;", StringComparison.Ordinal);
-
- _logger.LogDebug("{PropertyName} - SetNextAvTransport Uri: {Url} DlnaHeaders: {Header}", Properties.Name, url, header);
-
- var command = avCommands?.ServiceActions.FirstOrDefault(c => string.Equals(c.Name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase));
- if (command is null)
- {
- return;
- }
-
- var dictionary = new Dictionary<string, string>
- {
- { "NextURI", url },
- { "NextURIMetaData", CreateDidlMeta(metaData) }
- };
-
- var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
- var post = avCommands!.BuildPost(command, service.ServiceType, url, dictionary); // null checked above
- await new DlnaHttpClient(_logger, _httpClientFactory)
- .SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header, cancellationToken)
- .ConfigureAwait(false);
- }
-
- private static string CreateDidlMeta(string value)
- {
- if (string.IsNullOrEmpty(value))
- {
- return string.Empty;
- }
-
- return SecurityElement.Escape(value);
- }
-
- private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)
- {
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
- if (command is null)
- {
- return Task.CompletedTask;
- }
-
- var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
- return new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- avCommands.BuildPost(command, service.ServiceType, 1),
- cancellationToken: cancellationToken);
- }
-
- public async Task SetPlay(CancellationToken cancellationToken)
- {
- var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
- if (avCommands is null)
- {
- return;
- }
-
- await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
-
- RestartTimer(true);
- }
-
- public async Task SetStop(CancellationToken cancellationToken)
- {
- var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
- if (command is null)
- {
- return;
- }
-
- var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
- await new DlnaHttpClient(_logger, _httpClientFactory)
- .SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- avCommands!.BuildPost(command, service.ServiceType, 1), // null checked above
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
-
- RestartTimer(true);
- }
-
- public async Task SetPause(CancellationToken cancellationToken)
- {
- var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- var command = avCommands?.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
- if (command is null)
- {
- return;
- }
-
- var service = GetAvTransportService() ?? throw new InvalidOperationException("Unable to find service");
- await new DlnaHttpClient(_logger, _httpClientFactory)
- .SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- avCommands!.BuildPost(command, service.ServiceType, 1), // null checked above
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
-
- TransportState = TransportState.PAUSED_PLAYBACK;
-
- RestartTimer(true);
- }
-
- private async void TimerCallback(object? sender)
- {
- if (_disposed)
- {
- return;
- }
-
- try
- {
- var cancellationToken = CancellationToken.None;
-
- var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- if (avCommands is null)
- {
- return;
- }
-
- var transportState = await GetTransportInfo(avCommands, cancellationToken).ConfigureAwait(false);
-
- if (_disposed)
- {
- return;
- }
-
- if (transportState.HasValue)
- {
- // If we're not playing anything no need to get additional data
- if (transportState.Value == TransportState.STOPPED)
- {
- UpdateMediaInfo(null, transportState.Value);
- }
- else
- {
- var tuple = await GetPositionInfo(avCommands, cancellationToken).ConfigureAwait(false);
-
- var currentObject = tuple.Track;
-
- if (tuple.Success && currentObject is null)
- {
- currentObject = await GetMediaInfo(avCommands, cancellationToken).ConfigureAwait(false);
- }
-
- if (currentObject is not null)
- {
- UpdateMediaInfo(currentObject, transportState.Value);
- }
- }
-
- _connectFailureCount = 0;
-
- if (_disposed)
- {
- return;
- }
-
- // If we're not playing anything make sure we don't get data more often than necessary to keep the Session alive
- if (transportState.Value == TransportState.STOPPED)
- {
- RestartTimerInactive();
- }
- else
- {
- RestartTimer();
- }
- }
- else
- {
- RestartTimerInactive();
- }
- }
- catch (Exception ex)
- {
- if (_disposed)
- {
- return;
- }
-
- _logger.LogError(ex, "Error updating device info for {DeviceName}", Properties.Name);
-
- _connectFailureCount++;
-
- if (_connectFailureCount >= 3)
- {
- var action = OnDeviceUnavailable;
- if (action is not null)
- {
- _logger.LogDebug("Disposing device due to loss of connection");
- action();
- return;
- }
- }
-
- RestartTimerInactive();
- }
- }
-
- private async Task GetVolume(CancellationToken cancellationToken)
- {
- if (_disposed)
- {
- return;
- }
-
- var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
- if (command is null)
- {
- return;
- }
-
- var service = GetServiceRenderingControl();
-
- if (service is null)
- {
- return;
- }
-
- var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- rendererCommands!.BuildPost(command, service.ServiceType), // null checked above
- cancellationToken: cancellationToken).ConfigureAwait(false);
-
- if (result is null || result.Document is null)
- {
- return;
- }
-
- var volume = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i is not null);
- var volumeValue = volume?.Value;
-
- if (string.IsNullOrWhiteSpace(volumeValue))
- {
- return;
- }
-
- Volume = int.Parse(volumeValue, CultureInfo.InvariantCulture);
-
- if (Volume > 0)
- {
- _muteVol = Volume;
- }
- }
-
- private async Task GetMute(CancellationToken cancellationToken)
- {
- if (_disposed)
- {
- return;
- }
-
- var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- var command = rendererCommands?.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
- if (command is null)
- {
- return;
- }
-
- var service = GetServiceRenderingControl();
-
- if (service is null)
- {
- return;
- }
-
- var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- rendererCommands!.BuildPost(command, service.ServiceType), // null checked above
- cancellationToken: cancellationToken).ConfigureAwait(false);
-
- if (result is null || result.Document is null)
- {
- return;
- }
-
- var valueNode = result.Document.Descendants(UPnpNamespaces.RenderingControl + "GetMuteResponse")
- .Select(i => i.Element("CurrentMute"))
- .FirstOrDefault(i => i is not null);
-
- IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
- }
-
- private async Task<TransportState?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
- {
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
- if (command is null)
- {
- return null;
- }
-
- var service = GetAvTransportService();
- if (service is null)
- {
- return null;
- }
-
- var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- avCommands.BuildPost(command, service.ServiceType),
- cancellationToken: cancellationToken).ConfigureAwait(false);
-
- if (result is null || result.Document is null)
- {
- return null;
- }
-
- var transportState =
- result.Document.Descendants(UPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i is not null);
-
- var transportStateValue = transportState?.Value;
-
- if (transportStateValue is not null
- && Enum.TryParse(transportStateValue, true, out TransportState state))
- {
- return state;
- }
-
- return null;
- }
-
- private async Task<UBaseObject?> GetMediaInfo(TransportCommands avCommands, CancellationToken cancellationToken)
- {
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
- if (command is null)
- {
- return null;
- }
-
- var service = GetAvTransportService();
- if (service is null)
- {
- throw new InvalidOperationException("Unable to find service");
- }
-
- var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
- if (rendererCommands is null)
- {
- return null;
- }
-
- var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- rendererCommands.BuildPost(command, service.ServiceType),
- cancellationToken: cancellationToken).ConfigureAwait(false);
-
- if (result is null || result.Document is null)
- {
- return null;
- }
-
- var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
-
- if (track is null)
- {
- return null;
- }
-
- var e = track.Element(UPnpNamespaces.Items) ?? track;
-
- var elementString = (string)e;
-
- if (!string.IsNullOrWhiteSpace(elementString))
- {
- return UpnpContainer.Create(e);
- }
-
- track = result.Document.Descendants("CurrentURI").FirstOrDefault();
-
- if (track is null)
- {
- return null;
- }
-
- e = track.Element(UPnpNamespaces.Items) ?? track;
-
- elementString = (string)e;
-
- if (!string.IsNullOrWhiteSpace(elementString))
- {
- return new UBaseObject
- {
- Url = elementString
- };
- }
-
- return null;
- }
-
- private async Task<(bool Success, UBaseObject? Track)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
- {
- var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
- if (command is null)
- {
- return (false, null);
- }
-
- var service = GetAvTransportService();
-
- if (service is null)
- {
- throw new InvalidOperationException("Unable to find service");
- }
-
- var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
-
- if (rendererCommands is null)
- {
- return (false, null);
- }
-
- var result = await new DlnaHttpClient(_logger, _httpClientFactory).SendCommandAsync(
- Properties.BaseUrl,
- service,
- command.Name,
- rendererCommands.BuildPost(command, service.ServiceType),
- cancellationToken: cancellationToken).ConfigureAwait(false);
-
- if (result is null || result.Document is null)
- {
- return (false, null);
- }
-
- var trackUriElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i is not null);
- var trackUri = trackUriElem?.Value;
-
- var durationElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i is not null);
- var duration = durationElem?.Value;
-
- if (!string.IsNullOrWhiteSpace(duration)
- && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
- {
- Duration = TimeSpan.Parse(duration, CultureInfo.InvariantCulture);
- }
- else
- {
- Duration = null;
- }
-
- var positionElem = result.Document.Descendants(UPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i is not null);
- var position = positionElem?.Value;
-
- if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
- {
- Position = TimeSpan.Parse(position, CultureInfo.InvariantCulture);
- }
-
- var track = result.Document.Descendants("TrackMetaData").FirstOrDefault();
-
- if (track is null)
- {
- // If track is null, some vendors do this, use GetMediaInfo instead.
- return (true, null);
- }
-
- var trackString = (string)track;
-
- if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
- {
- return (true, null);
- }
-
- XElement? uPnpResponse = null;
-
- try
- {
- uPnpResponse = ParseResponse(trackString);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Uncaught exception while parsing xml");
- }
-
- if (uPnpResponse is null)
- {
- _logger.LogError("Failed to parse xml: \n {Xml}", trackString);
- return (true, null);
- }
-
- var e = uPnpResponse.Element(UPnpNamespaces.Items);
-
- var uTrack = CreateUBaseObject(e, trackUri);
-
- return (true, uTrack);
- }
-
- private XElement? ParseResponse(string xml)
- {
- // Handle different variations sent back by devices.
- try
- {
- return XElement.Parse(xml);
- }
- catch (XmlException)
- {
- }
-
- // first try to add a root node with a dlna namespace.
- try
- {
- return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
- .Descendants()
- .First();
- }
- catch (XmlException)
- {
- }
-
- // some devices send back invalid xml
- try
- {
- return XElement.Parse(xml.Replace("&", "&amp;", StringComparison.Ordinal));
- }
- catch (XmlException)
- {
- }
-
- return null;
- }
-
- private static UBaseObject CreateUBaseObject(XElement? container, string? trackUri)
- {
- ArgumentNullException.ThrowIfNull(container);
-
- var url = container.GetValue(UPnpNamespaces.Res);
-
- if (string.IsNullOrWhiteSpace(url))
- {
- url = trackUri;
- }
-
- return new UBaseObject
- {
- Id = container.GetAttributeValue(UPnpNamespaces.Id),
- ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId),
- Title = container.GetValue(UPnpNamespaces.Title),
- IconUrl = container.GetValue(UPnpNamespaces.Artwork),
- SecondText = string.Empty,
- Url = url,
- ProtocolInfo = GetProtocolInfo(container),
- MetaData = container.ToString()
- };
- }
-
- private static string[] GetProtocolInfo(XElement container)
- {
- ArgumentNullException.ThrowIfNull(container);
-
- var resElement = container.Element(UPnpNamespaces.Res);
-
- var info = resElement?.Attribute(UPnpNamespaces.ProtocolInfo);
-
- if (info is not null && !string.IsNullOrWhiteSpace(info.Value))
- {
- return info.Value.Split(':');
- }
-
- return new string[4];
- }
-
- private async Task<TransportCommands?> GetAVProtocolAsync(CancellationToken cancellationToken)
- {
- if (AvCommands is not null)
- {
- return AvCommands;
- }
-
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().Name);
- }
-
- var avService = GetAvTransportService();
- if (avService is null)
- {
- return null;
- }
-
- string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
-
- var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
-
- var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
- if (document is null)
- {
- return null;
- }
-
- AvCommands = TransportCommands.Create(document);
- return AvCommands;
- }
-
- private async Task<TransportCommands?> GetRenderingProtocolAsync(CancellationToken cancellationToken)
- {
- if (RendererCommands is not null)
- {
- return RendererCommands;
- }
-
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().Name);
- }
-
- var avService = GetServiceRenderingControl();
- ArgumentNullException.ThrowIfNull(avService);
-
- string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
-
- var httpClient = new DlnaHttpClient(_logger, _httpClientFactory);
- _logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
- var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
- if (document is null)
- {
- return null;
- }
-
- RendererCommands = TransportCommands.Create(document);
- return RendererCommands;
- }
-
- private string NormalizeUrl(string baseUrl, string url)
- {
- // If it's already a complete url, don't stick anything onto the front of it
- if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- return url;
- }
-
- if (!url.Contains('/', StringComparison.Ordinal))
- {
- url = "/dmr/" + url;
- }
-
- if (!url.StartsWith('/'))
- {
- url = "/" + url;
- }
-
- return baseUrl + url;
- }
-
- public static async Task<Device?> CreateuPnpDeviceAsync(Uri url, IHttpClientFactory httpClientFactory, ILogger logger, CancellationToken cancellationToken)
- {
- var ssdpHttpClient = new DlnaHttpClient(logger, httpClientFactory);
-
- var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
- if (document is null)
- {
- return null;
- }
-
- var friendlyNames = new List<string>();
-
- var name = document.Descendants(UPnpNamespaces.Ud.GetName("friendlyName")).FirstOrDefault();
- if (name is not null && !string.IsNullOrWhiteSpace(name.Value))
- {
- friendlyNames.Add(name.Value);
- }
-
- var room = document.Descendants(UPnpNamespaces.Ud.GetName("roomName")).FirstOrDefault();
- if (room is not null && !string.IsNullOrWhiteSpace(room.Value))
- {
- friendlyNames.Add(room.Value);
- }
-
- var deviceProperties = new DeviceInfo()
- {
- Name = string.Join(' ', friendlyNames),
- BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port)
- };
-
- var model = document.Descendants(UPnpNamespaces.Ud.GetName("modelName")).FirstOrDefault();
- if (model is not null)
- {
- deviceProperties.ModelName = model.Value;
- }
-
- var modelNumber = document.Descendants(UPnpNamespaces.Ud.GetName("modelNumber")).FirstOrDefault();
- if (modelNumber is not null)
- {
- deviceProperties.ModelNumber = modelNumber.Value;
- }
-
- var uuid = document.Descendants(UPnpNamespaces.Ud.GetName("UDN")).FirstOrDefault();
- if (uuid is not null)
- {
- deviceProperties.UUID = uuid.Value;
- }
-
- var manufacturer = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturer")).FirstOrDefault();
- if (manufacturer is not null)
- {
- deviceProperties.Manufacturer = manufacturer.Value;
- }
-
- var manufacturerUrl = document.Descendants(UPnpNamespaces.Ud.GetName("manufacturerURL")).FirstOrDefault();
- if (manufacturerUrl is not null)
- {
- deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
- }
-
- var presentationUrl = document.Descendants(UPnpNamespaces.Ud.GetName("presentationURL")).FirstOrDefault();
- if (presentationUrl is not null)
- {
- deviceProperties.PresentationUrl = presentationUrl.Value;
- }
-
- var modelUrl = document.Descendants(UPnpNamespaces.Ud.GetName("modelURL")).FirstOrDefault();
- if (modelUrl is not null)
- {
- deviceProperties.ModelUrl = modelUrl.Value;
- }
-
- var serialNumber = document.Descendants(UPnpNamespaces.Ud.GetName("serialNumber")).FirstOrDefault();
- if (serialNumber is not null)
- {
- deviceProperties.SerialNumber = serialNumber.Value;
- }
-
- var modelDescription = document.Descendants(UPnpNamespaces.Ud.GetName("modelDescription")).FirstOrDefault();
- if (modelDescription is not null)
- {
- deviceProperties.ModelDescription = modelDescription.Value;
- }
-
- var icon = document.Descendants(UPnpNamespaces.Ud.GetName("icon")).FirstOrDefault();
- if (icon is not null)
- {
- deviceProperties.Icon = CreateIcon(icon);
- }
-
- foreach (var services in document.Descendants(UPnpNamespaces.Ud.GetName("serviceList")))
- {
- if (services is null)
- {
- continue;
- }
-
- var servicesList = services.Descendants(UPnpNamespaces.Ud.GetName("service"));
- if (servicesList is null)
- {
- continue;
- }
-
- foreach (var element in servicesList)
- {
- var service = Create(element);
-
- if (service is not null)
- {
- deviceProperties.Services.Add(service);
- }
- }
- }
-
- return new Device(deviceProperties, httpClientFactory, logger);
- }
-
- private static DeviceIcon CreateIcon(XElement element)
- {
- ArgumentNullException.ThrowIfNull(element);
-
- var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
- var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
-
- _ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue);
- _ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue);
-
- return new DeviceIcon
- {
- Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty,
- Height = heightValue,
- MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty,
- Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty,
- Width = widthValue
- };
- }
-
- private static DeviceService Create(XElement element)
- => new DeviceService()
- {
- ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty,
- EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty,
- ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty,
- ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty,
- ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty
- };
-
- private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state)
- {
- TransportState = state;
-
- var previousMediaInfo = CurrentMediaInfo;
- CurrentMediaInfo = mediaInfo;
-
- if (mediaInfo is null)
- {
- if (previousMediaInfo is not null)
- {
- OnPlaybackStop(previousMediaInfo);
- }
- }
- else if (previousMediaInfo is null)
- {
- if (state != TransportState.STOPPED)
- {
- OnPlaybackStart(mediaInfo);
- }
- }
- else if (mediaInfo.Equals(previousMediaInfo))
- {
- OnPlaybackProgress(mediaInfo);
- }
- else
- {
- OnMediaChanged(previousMediaInfo, mediaInfo);
- }
- }
-
- private void OnPlaybackStart(UBaseObject mediaInfo)
- {
- if (string.IsNullOrWhiteSpace(mediaInfo.Url))
- {
- return;
- }
-
- PlaybackStart?.Invoke(this, new PlaybackStartEventArgs(mediaInfo));
- }
-
- private void OnPlaybackProgress(UBaseObject mediaInfo)
- {
- if (string.IsNullOrWhiteSpace(mediaInfo.Url))
- {
- return;
- }
-
- PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs(mediaInfo));
- }
-
- private void OnPlaybackStop(UBaseObject mediaInfo)
- {
- PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs(mediaInfo));
- }
-
- private void OnMediaChanged(UBaseObject old, UBaseObject newMedia)
- {
- MediaChanged?.Invoke(this, new MediaChangedEventArgs(old, newMedia));
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Releases unmanaged and optionally managed resources.
- /// </summary>
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- _timer?.Dispose();
- _timer = null;
- Properties = null!;
- }
-
- _disposed = true;
- }
-
- /// <inheritdoc />
- public override string ToString()
- {
- return string.Format(CultureInfo.InvariantCulture, "{0} - {1}", Properties.Name, Properties.BaseUrl);
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/DeviceInfo.cs b/Emby.Dlna/PlayTo/DeviceInfo.cs
deleted file mode 100644
index 2acfff4eb6..0000000000
--- a/Emby.Dlna/PlayTo/DeviceInfo.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using Emby.Dlna.Common;
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.PlayTo
-{
- public class DeviceInfo
- {
- private readonly List<DeviceService> _services = new List<DeviceService>();
- private string _baseUrl = string.Empty;
-
- public DeviceInfo()
- {
- Name = "Generic Device";
- }
-
- public string UUID { get; set; }
-
- public string Name { get; set; }
-
- public string ModelName { get; set; }
-
- public string ModelNumber { get; set; }
-
- public string ModelDescription { get; set; }
-
- public string ModelUrl { get; set; }
-
- public string Manufacturer { get; set; }
-
- public string SerialNumber { get; set; }
-
- public string ManufacturerUrl { get; set; }
-
- public string PresentationUrl { get; set; }
-
- public string BaseUrl
- {
- get => _baseUrl;
- set => _baseUrl = value;
- }
-
- public DeviceIcon Icon { get; set; }
-
- public List<DeviceService> Services => _services;
-
- public DeviceIdentification ToDeviceIdentification()
- {
- return new DeviceIdentification
- {
- Manufacturer = Manufacturer,
- ModelName = ModelName,
- ModelNumber = ModelNumber,
- FriendlyName = Name,
- ManufacturerUrl = ManufacturerUrl,
- ModelUrl = ModelUrl,
- ModelDescription = ModelDescription,
- SerialNumber = SerialNumber
- };
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/DlnaHttpClient.cs b/Emby.Dlna/PlayTo/DlnaHttpClient.cs
deleted file mode 100644
index 255c51f19a..0000000000
--- a/Emby.Dlna/PlayTo/DlnaHttpClient.cs
+++ /dev/null
@@ -1,137 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.IO;
-using System.Net.Http;
-using System.Net.Mime;
-using System.Text;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
-using System.Xml.Linq;
-using Emby.Dlna.Common;
-using MediaBrowser.Common.Net;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Dlna.PlayTo
-{
- /// <summary>
- /// Http client for Dlna PlayTo function.
- /// </summary>
- public partial class DlnaHttpClient
- {
- private readonly ILogger _logger;
- private readonly IHttpClientFactory _httpClientFactory;
-
- public DlnaHttpClient(ILogger logger, IHttpClientFactory httpClientFactory)
- {
- _logger = logger;
- _httpClientFactory = httpClientFactory;
- }
-
- [GeneratedRegex("(&(?![a-z]*;))")]
- private static partial Regex EscapeAmpersandRegex();
-
- private static string NormalizeServiceUrl(string baseUrl, string serviceUrl)
- {
- // If it's already a complete url, don't stick anything onto the front of it
- if (serviceUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- return serviceUrl;
- }
-
- if (!serviceUrl.StartsWith('/'))
- {
- serviceUrl = "/" + serviceUrl;
- }
-
- return baseUrl + serviceUrl;
- }
-
- private async Task<XDocument?> SendRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- var client = _httpClientFactory.CreateClient(NamedClient.Dlna);
- using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
- response.EnsureSuccessStatusCode();
- Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using (stream.ConfigureAwait(false))
- {
- try
- {
- return await XDocument.LoadAsync(
- stream,
- LoadOptions.None,
- cancellationToken).ConfigureAwait(false);
- }
- catch (XmlException)
- {
- // try correcting the Xml response with common errors
- stream.Position = 0;
- using StreamReader sr = new StreamReader(stream);
- var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
-
- // find and replace unescaped ampersands (&)
- xmlString = EscapeAmpersandRegex().Replace(xmlString, "&amp;");
-
- try
- {
- // retry reading Xml
- using var xmlReader = new StringReader(xmlString);
- return await XDocument.LoadAsync(
- xmlReader,
- LoadOptions.None,
- cancellationToken).ConfigureAwait(false);
- }
- catch (XmlException ex)
- {
- _logger.LogError(ex, "Failed to parse response");
- _logger.LogDebug("Malformed response: {Content}\n", xmlString);
-
- return null;
- }
- }
- }
- }
-
- public async Task<XDocument?> GetDataAsync(string url, CancellationToken cancellationToken)
- {
- using var request = new HttpRequestMessage(HttpMethod.Get, url);
-
- // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
- return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
- }
-
- public async Task<XDocument?> SendCommandAsync(
- string baseUrl,
- DeviceService service,
- string command,
- string postData,
- string? header = null,
- CancellationToken cancellationToken = default)
- {
- using var request = new HttpRequestMessage(HttpMethod.Post, NormalizeServiceUrl(baseUrl, service.ControlUrl))
- {
- Content = new StringContent(postData, Encoding.UTF8, MediaTypeNames.Text.Xml)
- };
-
- request.Headers.TryAddWithoutValidation(
- "SOAPACTION",
- string.Format(
- CultureInfo.InvariantCulture,
- "\"{0}#{1}\"",
- service.ServiceType,
- command));
- request.Headers.Pragma.ParseAdd("no-cache");
-
- if (!string.IsNullOrEmpty(header))
- {
- request.Headers.TryAddWithoutValidation("contentFeatures.dlna.org", header);
- }
-
- // Have to await here instead of returning the Task directly, otherwise request would be disposed too soon
- return await SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs b/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
deleted file mode 100644
index 0f7a524d62..0000000000
--- a/Emby.Dlna/PlayTo/MediaChangedEventArgs.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-
-namespace Emby.Dlna.PlayTo
-{
- public class MediaChangedEventArgs : EventArgs
- {
- public MediaChangedEventArgs(UBaseObject oldMediaInfo, UBaseObject newMediaInfo)
- {
- OldMediaInfo = oldMediaInfo;
- NewMediaInfo = newMediaInfo;
- }
-
- public UBaseObject OldMediaInfo { get; set; }
-
- public UBaseObject NewMediaInfo { get; set; }
- }
-}
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
deleted file mode 100644
index f70ebf3ebc..0000000000
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ /dev/null
@@ -1,980 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Emby.Dlna.Didl;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using Jellyfin.Data.Events;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Session;
-using Microsoft.AspNetCore.WebUtilities;
-using Microsoft.Extensions.Logging;
-using Photo = MediaBrowser.Controller.Entities.Photo;
-
-namespace Emby.Dlna.PlayTo
-{
- public class PlayToController : ISessionController, IDisposable
- {
- private readonly SessionInfo _session;
- private readonly ISessionManager _sessionManager;
- private readonly ILibraryManager _libraryManager;
- private readonly ILogger _logger;
- private readonly IDlnaManager _dlnaManager;
- private readonly IUserManager _userManager;
- private readonly IImageProcessor _imageProcessor;
- private readonly IUserDataManager _userDataManager;
- private readonly ILocalizationManager _localization;
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IMediaEncoder _mediaEncoder;
-
- private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly string _serverAddress;
- private readonly string? _accessToken;
-
- private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
- private Device _device;
- private int _currentPlaylistIndex;
-
- private bool _disposed;
-
- public PlayToController(
- SessionInfo session,
- ISessionManager sessionManager,
- ILibraryManager libraryManager,
- ILogger logger,
- IDlnaManager dlnaManager,
- IUserManager userManager,
- IImageProcessor imageProcessor,
- string serverAddress,
- string? accessToken,
- IDeviceDiscovery deviceDiscovery,
- IUserDataManager userDataManager,
- ILocalizationManager localization,
- IMediaSourceManager mediaSourceManager,
- IMediaEncoder mediaEncoder,
- Device device)
- {
- _session = session;
- _sessionManager = sessionManager;
- _libraryManager = libraryManager;
- _logger = logger;
- _dlnaManager = dlnaManager;
- _userManager = userManager;
- _imageProcessor = imageProcessor;
- _serverAddress = serverAddress;
- _accessToken = accessToken;
- _deviceDiscovery = deviceDiscovery;
- _userDataManager = userDataManager;
- _localization = localization;
- _mediaSourceManager = mediaSourceManager;
- _mediaEncoder = mediaEncoder;
-
- _device = device;
- _device.OnDeviceUnavailable = OnDeviceUnavailable;
- _device.PlaybackStart += OnDevicePlaybackStart;
- _device.PlaybackProgress += OnDevicePlaybackProgress;
- _device.PlaybackStopped += OnDevicePlaybackStopped;
- _device.MediaChanged += OnDeviceMediaChanged;
-
- _device.Start();
-
- _deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
- }
-
- public bool IsSessionActive => !_disposed;
-
- public bool SupportsMediaControl => IsSessionActive;
-
- /*
- * Send a message to the DLNA device to notify what is the next track in the playlist.
- */
- private async Task SendNextTrackMessage(int currentPlayListItemIndex, CancellationToken cancellationToken)
- {
- if (currentPlayListItemIndex >= 0 && currentPlayListItemIndex < _playlist.Count - 1)
- {
- // The current playing item is indeed in the play list and we are not yet at the end of the playlist.
- var nextItemIndex = currentPlayListItemIndex + 1;
- var nextItem = _playlist[nextItemIndex];
-
- // Send the SetNextAvTransport message.
- await _device.SetNextAvTransport(nextItem.StreamUrl, GetDlnaHeaders(nextItem), nextItem.Didl, cancellationToken).ConfigureAwait(false);
- }
- }
-
- private void OnDeviceUnavailable()
- {
- try
- {
- _sessionManager.ReportSessionEnded(_session.Id);
- }
- catch (Exception ex)
- {
- // Could throw if the session is already gone
- _logger.LogError(ex, "Error reporting the end of session {Id}", _session.Id);
- }
- }
-
- private void OnDeviceDiscoveryDeviceLeft(object? sender, GenericEventArgs<UpnpDeviceInfo> e)
- {
- var info = e.Argument;
-
- if (!_disposed
- && info.Headers.TryGetValue("USN", out string? usn)
- && usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1
- && (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1
- || (info.Headers.TryGetValue("NT", out string? nt)
- && nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)))
- {
- OnDeviceUnavailable();
- }
- }
-
- private async void OnDeviceMediaChanged(object? sender, MediaChangedEventArgs e)
- {
- if (_disposed || string.IsNullOrEmpty(e.OldMediaInfo.Url))
- {
- return;
- }
-
- try
- {
- var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
- if (streamInfo.Item is not null)
- {
- var positionTicks = GetProgressPositionTicks(streamInfo);
-
- await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
- }
-
- streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
- if (streamInfo.Item is null)
- {
- return;
- }
-
- var newItemProgress = GetProgressInfo(streamInfo);
-
- await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
-
- // Send a message to the DLNA device to notify what is the next track in the playlist.
- var currentItemIndex = _playlist.FindIndex(item => item.StreamInfo.ItemId.Equals(streamInfo.ItemId));
- if (currentItemIndex >= 0)
- {
- _currentPlaylistIndex = currentItemIndex;
- }
-
- await SendNextTrackMessage(currentItemIndex, CancellationToken.None).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reporting progress");
- }
- }
-
- private async void OnDevicePlaybackStopped(object? sender, PlaybackStoppedEventArgs e)
- {
- if (_disposed)
- {
- return;
- }
-
- try
- {
- var streamInfo = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
-
- if (streamInfo.Item is null)
- {
- return;
- }
-
- var positionTicks = GetProgressPositionTicks(streamInfo);
-
- await ReportPlaybackStopped(streamInfo, positionTicks).ConfigureAwait(false);
-
- var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
-
- var duration = mediaSource is null
- ? _device.Duration?.Ticks
- : mediaSource.RunTimeTicks;
-
- var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
-
- if (!playedToCompletion && duration.HasValue && positionTicks.HasValue)
- {
- double percent = positionTicks.Value;
- percent /= duration.Value;
-
- playedToCompletion = Math.Abs(1 - percent) <= .1;
- }
-
- if (playedToCompletion)
- {
- await SetPlaylistIndex(_currentPlaylistIndex + 1).ConfigureAwait(false);
- }
- else
- {
- _playlist.Clear();
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reporting playback stopped");
- }
- }
-
- private async Task ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
- {
- try
- {
- await _sessionManager.OnPlaybackStopped(new PlaybackStopInfo
- {
- ItemId = streamInfo.ItemId,
- SessionId = _session.Id,
- PositionTicks = positionTicks,
- MediaSourceId = streamInfo.MediaSourceId
- }).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reporting progress");
- }
- }
-
- private async void OnDevicePlaybackStart(object? sender, PlaybackStartEventArgs e)
- {
- if (_disposed)
- {
- return;
- }
-
- try
- {
- var info = StreamParams.ParseFromUrl(e.MediaInfo.Url, _libraryManager, _mediaSourceManager);
-
- if (info.Item is not null)
- {
- var progress = GetProgressInfo(info);
-
- await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reporting progress");
- }
- }
-
- private async void OnDevicePlaybackProgress(object? sender, PlaybackProgressEventArgs e)
- {
- if (_disposed)
- {
- return;
- }
-
- try
- {
- var mediaUrl = e.MediaInfo.Url;
-
- if (string.IsNullOrWhiteSpace(mediaUrl))
- {
- return;
- }
-
- var info = StreamParams.ParseFromUrl(mediaUrl, _libraryManager, _mediaSourceManager);
-
- if (info.Item is not null)
- {
- var progress = GetProgressInfo(info);
-
- await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error reporting progress");
- }
- }
-
- private long? GetProgressPositionTicks(StreamParams info)
- {
- var ticks = _device.Position.Ticks;
-
- if (!EnableClientSideSeek(info))
- {
- ticks += info.StartPositionTicks;
- }
-
- return ticks;
- }
-
- private PlaybackStartInfo GetProgressInfo(StreamParams info)
- {
- return new PlaybackStartInfo
- {
- ItemId = info.ItemId,
- SessionId = _session.Id,
- PositionTicks = GetProgressPositionTicks(info),
- IsMuted = _device.IsMuted,
- IsPaused = _device.IsPaused,
- MediaSourceId = info.MediaSourceId,
- AudioStreamIndex = info.AudioStreamIndex,
- SubtitleStreamIndex = info.SubtitleStreamIndex,
- VolumeLevel = _device.Volume,
-
- CanSeek = true,
-
- PlayMethod = info.IsDirectStream ? PlayMethod.DirectStream : PlayMethod.Transcode
- };
- }
-
- public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
- {
- _logger.LogDebug("{0} - Received PlayRequest: {1}", _session.DeviceName, command.PlayCommand);
-
- var user = command.ControllingUserId.Equals(default)
- ? null :
- _userManager.GetUserById(command.ControllingUserId);
-
- var items = new List<BaseItem>();
- foreach (var id in command.ItemIds)
- {
- AddItemFromId(id, items);
- }
-
- var startIndex = command.StartIndex ?? 0;
- int len = items.Count - startIndex;
- if (startIndex > 0)
- {
- items = items.GetRange(startIndex, len);
- }
-
- var playlist = new PlaylistItem[len];
-
- // Not nullable enabled - so this is required.
- playlist[0] = CreatePlaylistItem(
- items[0],
- user,
- command.StartPositionTicks ?? 0,
- command.MediaSourceId ?? string.Empty,
- command.AudioStreamIndex,
- command.SubtitleStreamIndex);
-
- for (int i = 1; i < len; i++)
- {
- playlist[i] = CreatePlaylistItem(items[i], user, 0, string.Empty, null, null);
- }
-
- _logger.LogDebug("{0} - Playlist created", _session.DeviceName);
-
- if (command.PlayCommand == PlayCommand.PlayLast)
- {
- _playlist.AddRange(playlist);
- }
-
- if (command.PlayCommand == PlayCommand.PlayNext)
- {
- _playlist.AddRange(playlist);
- }
-
- if (!command.ControllingUserId.Equals(default))
- {
- _sessionManager.LogSessionActivity(
- _session.Client,
- _session.ApplicationVersion,
- _session.DeviceId,
- _session.DeviceName,
- _session.RemoteEndPoint,
- user);
- }
-
- return PlayItems(playlist, cancellationToken);
- }
-
- private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
- {
- switch (command.Command)
- {
- case PlaystateCommand.Stop:
- _playlist.Clear();
- return _device.SetStop(CancellationToken.None);
-
- case PlaystateCommand.Pause:
- return _device.SetPause(CancellationToken.None);
-
- case PlaystateCommand.Unpause:
- return _device.SetPlay(CancellationToken.None);
-
- case PlaystateCommand.PlayPause:
- return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None);
-
- case PlaystateCommand.Seek:
- return Seek(command.SeekPositionTicks ?? 0);
-
- case PlaystateCommand.NextTrack:
- return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken);
-
- case PlaystateCommand.PreviousTrack:
- return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken);
- }
-
- return Task.CompletedTask;
- }
-
- private async Task Seek(long newPosition)
- {
- var media = _device.CurrentMediaInfo;
-
- if (media is not null)
- {
- var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager);
-
- if (info.Item is not null && !EnableClientSideSeek(info))
- {
- var user = _session.UserId.Equals(default)
- ? null
- : _userManager.GetUserById(_session.UserId);
- var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, info.SubtitleStreamIndex);
-
- await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
-
- // Send a message to the DLNA device to notify what is the next track in the play list.
- var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
- await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false);
-
- return;
- }
-
- await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
- }
- }
-
- private bool EnableClientSideSeek(StreamParams info)
- {
- return info.IsDirectStream;
- }
-
- private bool EnableClientSideSeek(StreamInfo info)
- {
- return info.IsDirectStream;
- }
-
- private void AddItemFromId(Guid id, List<BaseItem> list)
- {
- var item = _libraryManager.GetItemById(id);
- if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
- {
- list.Add(item);
- }
- }
-
- private PlaylistItem CreatePlaylistItem(
- BaseItem item,
- User? user,
- long startPostionTicks,
- string? mediaSourceId,
- int? audioStreamIndex,
- int? subtitleStreamIndex)
- {
- var deviceInfo = _device.Properties;
-
- var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
- _dlnaManager.GetDefaultProfile();
-
- var mediaSources = item is IHasMediaSources
- ? _mediaSourceManager.GetStaticMediaSources(item, true, user).ToArray()
- : Array.Empty<MediaSourceInfo>();
-
- var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
- playlistItem.StreamInfo.StartPositionTicks = startPostionTicks;
-
- playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
-
- var itemXml = new DidlBuilder(
- profile,
- user,
- _imageProcessor,
- _serverAddress,
- _accessToken,
- _userDataManager,
- _localization,
- _mediaSourceManager,
- _logger,
- _mediaEncoder,
- _libraryManager)
- .GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
-
- playlistItem.Didl = itemXml;
-
- return playlistItem;
- }
-
- private string? GetDlnaHeaders(PlaylistItem item)
- {
- var profile = item.Profile;
- var streamInfo = item.StreamInfo;
-
- if (streamInfo.MediaType == DlnaProfileType.Audio)
- {
- return ContentFeatureBuilder.BuildAudioHeader(
- profile,
- streamInfo.Container,
- streamInfo.TargetAudioCodec.FirstOrDefault(),
- streamInfo.TargetAudioBitrate,
- streamInfo.TargetAudioSampleRate,
- streamInfo.TargetAudioChannels,
- streamInfo.TargetAudioBitDepth,
- streamInfo.IsDirectStream,
- streamInfo.RunTimeTicks ?? 0,
- streamInfo.TranscodeSeekInfo);
- }
-
- if (streamInfo.MediaType == DlnaProfileType.Video)
- {
- var list = ContentFeatureBuilder.BuildVideoHeader(
- profile,
- streamInfo.Container,
- streamInfo.TargetVideoCodec.FirstOrDefault(),
- streamInfo.TargetAudioCodec.FirstOrDefault(),
- streamInfo.TargetWidth,
- streamInfo.TargetHeight,
- streamInfo.TargetVideoBitDepth,
- streamInfo.TargetVideoBitrate,
- streamInfo.TargetTimestamp,
- streamInfo.IsDirectStream,
- streamInfo.RunTimeTicks ?? 0,
- streamInfo.TargetVideoProfile,
- streamInfo.TargetVideoRangeType,
- streamInfo.TargetVideoLevel,
- streamInfo.TargetFramerate ?? 0,
- streamInfo.TargetPacketLength,
- streamInfo.TranscodeSeekInfo,
- streamInfo.IsTargetAnamorphic,
- streamInfo.IsTargetInterlaced,
- streamInfo.TargetRefFrames,
- streamInfo.TargetVideoStreamCount,
- streamInfo.TargetAudioStreamCount,
- streamInfo.TargetVideoCodecTag,
- streamInfo.IsTargetAVC);
-
- return list.FirstOrDefault();
- }
-
- return null;
- }
-
- private PlaylistItem GetPlaylistItem(BaseItem item, MediaSourceInfo[] mediaSources, DeviceProfile profile, string deviceId, string? mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
- {
- if (item.MediaType == MediaType.Video)
- {
- return new PlaylistItem
- {
- StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalVideoStream(new MediaOptions
- {
- ItemId = item.Id,
- MediaSources = mediaSources,
- Profile = profile,
- DeviceId = deviceId,
- MaxBitrate = profile.MaxStreamingBitrate,
- MediaSourceId = mediaSourceId,
- AudioStreamIndex = audioStreamIndex,
- SubtitleStreamIndex = subtitleStreamIndex
- }),
-
- Profile = profile
- };
- }
-
- if (item.MediaType == MediaType.Audio)
- {
- return new PlaylistItem
- {
- StreamInfo = new StreamBuilder(_mediaEncoder, _logger).GetOptimalAudioStream(new MediaOptions
- {
- ItemId = item.Id,
- MediaSources = mediaSources,
- Profile = profile,
- DeviceId = deviceId,
- MaxBitrate = profile.MaxStreamingBitrate,
- MediaSourceId = mediaSourceId
- }),
-
- Profile = profile
- };
- }
-
- if (item.MediaType == MediaType.Photo)
- {
- return PlaylistItemFactory.Create((Photo)item, profile);
- }
-
- throw new ArgumentException("Unrecognized item type.");
- }
-
- /// <summary>
- /// Plays the items.
- /// </summary>
- /// <param name="items">The items.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns><c>true</c> on success.</returns>
- private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items, CancellationToken cancellationToken = default)
- {
- _playlist.Clear();
- _playlist.AddRange(items);
- _logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count);
-
- await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false);
- return true;
- }
-
- private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default)
- {
- if (index < 0 || index >= _playlist.Count)
- {
- _playlist.Clear();
- await _device.SetStop(cancellationToken).ConfigureAwait(false);
- return;
- }
-
- _currentPlaylistIndex = index;
- var currentitem = _playlist[index];
-
- await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
-
- // Send a message to the DLNA device to notify what is the next track in the play list.
- await SendNextTrackMessage(index, cancellationToken).ConfigureAwait(false);
-
- var streamInfo = currentitem.StreamInfo;
- if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
- {
- await SeekAfterTransportChange(streamInfo.StartPositionTicks, CancellationToken.None).ConfigureAwait(false);
- }
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Releases unmanaged and optionally managed resources.
- /// </summary>
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- _device.PlaybackStart -= OnDevicePlaybackStart;
- _device.PlaybackProgress -= OnDevicePlaybackProgress;
- _device.PlaybackStopped -= OnDevicePlaybackStopped;
- _device.MediaChanged -= OnDeviceMediaChanged;
- _deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
- _device.OnDeviceUnavailable = null;
- _device.Dispose();
- }
-
- _disposed = true;
- }
-
- private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
- {
- switch (command.Name)
- {
- case GeneralCommandType.VolumeDown:
- return _device.VolumeDown(cancellationToken);
- case GeneralCommandType.VolumeUp:
- return _device.VolumeUp(cancellationToken);
- case GeneralCommandType.Mute:
- return _device.Mute(cancellationToken);
- case GeneralCommandType.Unmute:
- return _device.Unmute(cancellationToken);
- case GeneralCommandType.ToggleMute:
- return _device.ToggleMute(cancellationToken);
- case GeneralCommandType.SetAudioStreamIndex:
- if (command.Arguments.TryGetValue("Index", out string? index))
- {
- if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
- {
- return SetAudioStreamIndex(val);
- }
-
- throw new ArgumentException("Unsupported SetAudioStreamIndex value supplied.");
- }
-
- throw new ArgumentException("SetAudioStreamIndex argument cannot be null");
- case GeneralCommandType.SetSubtitleStreamIndex:
- if (command.Arguments.TryGetValue("Index", out index))
- {
- if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
- {
- return SetSubtitleStreamIndex(val);
- }
-
- throw new ArgumentException("Unsupported SetSubtitleStreamIndex value supplied.");
- }
-
- throw new ArgumentException("SetSubtitleStreamIndex argument cannot be null");
- case GeneralCommandType.SetVolume:
- if (command.Arguments.TryGetValue("Volume", out string? vol))
- {
- if (int.TryParse(vol, NumberStyles.Integer, CultureInfo.InvariantCulture, out var volume))
- {
- return _device.SetVolume(volume, cancellationToken);
- }
-
- throw new ArgumentException("Unsupported volume value supplied.");
- }
-
- throw new ArgumentException("Volume argument cannot be null");
- default:
- return Task.CompletedTask;
- }
- }
-
- private async Task SetAudioStreamIndex(int? newIndex)
- {
- var media = _device.CurrentMediaInfo;
-
- if (media is not null)
- {
- var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager);
-
- if (info.Item is not null)
- {
- var newPosition = GetProgressPositionTicks(info) ?? 0;
-
- var user = _session.UserId.Equals(default)
- ? null
- : _userManager.GetUserById(_session.UserId);
- var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
-
- await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
-
- // Send a message to the DLNA device to notify what is the next track in the play list.
- var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
- await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false);
-
- if (EnableClientSideSeek(newItem.StreamInfo))
- {
- await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
- }
- }
- }
- }
-
- private async Task SetSubtitleStreamIndex(int? newIndex)
- {
- var media = _device.CurrentMediaInfo;
-
- if (media is not null)
- {
- var info = StreamParams.ParseFromUrl(media.Url, _libraryManager, _mediaSourceManager);
-
- if (info.Item is not null)
- {
- var newPosition = GetProgressPositionTicks(info) ?? 0;
-
- var user = _session.UserId.Equals(default)
- ? null
- : _userManager.GetUserById(_session.UserId);
- var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
-
- await _device.SetAvTransport(newItem.StreamUrl, GetDlnaHeaders(newItem), newItem.Didl, CancellationToken.None).ConfigureAwait(false);
-
- // Send a message to the DLNA device to notify what is the next track in the play list.
- var newItemIndex = _playlist.FindIndex(item => item.StreamUrl == newItem.StreamUrl);
- await SendNextTrackMessage(newItemIndex, CancellationToken.None).ConfigureAwait(false);
-
- if (EnableClientSideSeek(newItem.StreamInfo) && newPosition > 0)
- {
- await SeekAfterTransportChange(newPosition, CancellationToken.None).ConfigureAwait(false);
- }
- }
- }
- }
-
- private async Task SeekAfterTransportChange(long positionTicks, CancellationToken cancellationToken)
- {
- const int MaxWait = 15000000;
- const int Interval = 500;
-
- var currentWait = 0;
- while (_device.TransportState != TransportState.PLAYING && currentWait < MaxWait)
- {
- await Task.Delay(Interval, cancellationToken).ConfigureAwait(false);
- currentWait += Interval;
- }
-
- await _device.Seek(TimeSpan.FromTicks(positionTicks), cancellationToken).ConfigureAwait(false);
- }
-
- private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
- {
- var value = values.GetValueOrDefault(name);
-
- if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
-
- return null;
- }
-
- private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
- {
- var value = values.GetValueOrDefault(name);
-
- if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
-
- return 0;
- }
-
- /// <inheritdoc />
- public Task SendMessage<T>(SessionMessageType name, Guid messageId, T data, CancellationToken cancellationToken)
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().Name);
- }
-
- return name switch
- {
- SessionMessageType.Play => SendPlayCommand((data as PlayRequest)!, cancellationToken),
- SessionMessageType.Playstate => SendPlaystateCommand((data as PlaystateRequest)!, cancellationToken),
- SessionMessageType.GeneralCommand => SendGeneralCommand((data as GeneralCommand)!, cancellationToken),
- _ => Task.CompletedTask // Not supported or needed right now
- };
- }
-
- private class StreamParams
- {
- private MediaSourceInfo? _mediaSource;
- private IMediaSourceManager? _mediaSourceManager;
-
- public Guid ItemId { get; set; }
-
- public bool IsDirectStream { get; set; }
-
- public long StartPositionTicks { get; set; }
-
- public int? AudioStreamIndex { get; set; }
-
- public int? SubtitleStreamIndex { get; set; }
-
- public string? DeviceProfileId { get; set; }
-
- public string? DeviceId { get; set; }
-
- public string? MediaSourceId { get; set; }
-
- public string? LiveStreamId { get; set; }
-
- public BaseItem? Item { get; set; }
-
- public async Task<MediaSourceInfo?> GetMediaSource(CancellationToken cancellationToken)
- {
- if (_mediaSource is not null)
- {
- return _mediaSource;
- }
-
- if (Item is not IHasMediaSources)
- {
- return null;
- }
-
- if (_mediaSourceManager is not null)
- {
- _mediaSource = await _mediaSourceManager.GetMediaSource(Item, MediaSourceId, LiveStreamId, false, cancellationToken).ConfigureAwait(false);
- }
-
- return _mediaSource;
- }
-
- private static Guid GetItemId(string url)
- {
- ArgumentException.ThrowIfNullOrEmpty(url);
-
- var parts = url.Split('/');
-
- for (var i = 0; i < parts.Length - 1; i++)
- {
- var part = parts[i];
-
- if (string.Equals(part, "audio", StringComparison.OrdinalIgnoreCase)
- || string.Equals(part, "videos", StringComparison.OrdinalIgnoreCase))
- {
- if (Guid.TryParse(parts[i + 1], out var result))
- {
- return result;
- }
- }
- }
-
- return default;
- }
-
- public static StreamParams ParseFromUrl(string url, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager)
- {
- ArgumentException.ThrowIfNullOrEmpty(url);
-
- var request = new StreamParams
- {
- ItemId = GetItemId(url)
- };
-
- if (request.ItemId.Equals(default))
- {
- return request;
- }
-
- var index = url.IndexOf('?', StringComparison.Ordinal);
- if (index == -1)
- {
- return request;
- }
-
- var query = url.Substring(index + 1);
- Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
-
- request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId");
- request.DeviceId = values.GetValueOrDefault("DeviceId");
- request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
- request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
- request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
- request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
- request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
- request.StartPositionTicks = GetLongValue(values, "StartPositionTicks");
-
- request.Item = libraryManager.GetItemById(request.ItemId);
-
- request._mediaSourceManager = mediaSourceManager;
-
- return request;
- }
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
deleted file mode 100644
index b05e0a0957..0000000000
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ /dev/null
@@ -1,258 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Events;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Session;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Dlna.PlayTo
-{
- public sealed class PlayToManager : IDisposable
- {
- private readonly ILogger _logger;
- private readonly ISessionManager _sessionManager;
-
- private readonly ILibraryManager _libraryManager;
- private readonly IUserManager _userManager;
- private readonly IDlnaManager _dlnaManager;
- private readonly IServerApplicationHost _appHost;
- private readonly IImageProcessor _imageProcessor;
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly IUserDataManager _userDataManager;
- private readonly ILocalizationManager _localization;
-
- private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IMediaEncoder _mediaEncoder;
-
- private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
- private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
- private bool _disposed;
-
- public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
- {
- _logger = logger;
- _sessionManager = sessionManager;
- _libraryManager = libraryManager;
- _userManager = userManager;
- _dlnaManager = dlnaManager;
- _appHost = appHost;
- _imageProcessor = imageProcessor;
- _deviceDiscovery = deviceDiscovery;
- _httpClientFactory = httpClientFactory;
- _userDataManager = userDataManager;
- _localization = localization;
- _mediaSourceManager = mediaSourceManager;
- _mediaEncoder = mediaEncoder;
- }
-
- public void Start()
- {
- _deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
- }
-
- private async void OnDeviceDiscoveryDeviceDiscovered(object? sender, GenericEventArgs<UpnpDeviceInfo> e)
- {
- if (_disposed)
- {
- return;
- }
-
- var info = e.Argument;
-
- if (!info.Headers.TryGetValue("USN", out string? usn))
- {
- usn = string.Empty;
- }
-
- if (!info.Headers.TryGetValue("NT", out string? nt))
- {
- nt = string.Empty;
- }
-
- // It has to report that it's a media renderer
- if (!usn.Contains("MediaRenderer:", StringComparison.OrdinalIgnoreCase)
- && !nt.Contains("MediaRenderer:", StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
-
- var cancellationToken = _disposeCancellationTokenSource.Token;
-
- await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- if (_disposed)
- {
- return;
- }
-
- if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
- {
- return;
- }
-
- await AddDevice(info, cancellationToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error creating PlayTo device.");
- }
- finally
- {
- _sessionLock.Release();
- }
- }
-
- internal static string GetUuid(string usn)
- {
- const string UuidStr = "uuid:";
- const string UuidColonStr = "::";
-
- var index = usn.IndexOf(UuidStr, StringComparison.OrdinalIgnoreCase);
- if (index == -1)
- {
- return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- }
-
- ReadOnlySpan<char> tmp = usn.AsSpan()[(index + UuidStr.Length)..];
-
- index = tmp.IndexOf(UuidColonStr, StringComparison.OrdinalIgnoreCase);
- if (index != -1)
- {
- tmp = tmp[..index];
- }
-
- index = tmp.IndexOf('{');
- if (index != -1)
- {
- int endIndex = tmp.IndexOf('}');
- if (endIndex != -1)
- {
- tmp = tmp[(index + 1)..endIndex];
- }
- }
-
- return tmp.ToString();
- }
-
- private async Task AddDevice(UpnpDeviceInfo info, CancellationToken cancellationToken)
- {
- var uri = info.Location;
- _logger.LogDebug("Attempting to create PlayToController from location {0}", uri);
-
- if (info.Headers.TryGetValue("USN", out string? uuid))
- {
- uuid = GetUuid(uuid);
- }
- else
- {
- uuid = uri.ToString().GetMD5().ToString("N", CultureInfo.InvariantCulture);
- }
-
- var sessionInfo = await _sessionManager
- .LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null)
- .ConfigureAwait(false);
-
- var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
-
- if (controller is null)
- {
- var device = await Device.CreateuPnpDeviceAsync(uri, _httpClientFactory, _logger, cancellationToken).ConfigureAwait(false);
- if (device is null)
- {
- _logger.LogError("Ignoring device as xml response is invalid.");
- return;
- }
-
- string deviceName = device.Properties.Name;
-
- _sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
-
- string serverAddress = _appHost.GetSmartApiUrl(info.RemoteIPAddress);
-
- controller = new PlayToController(
- sessionInfo,
- _sessionManager,
- _libraryManager,
- _logger,
- _dlnaManager,
- _userManager,
- _imageProcessor,
- serverAddress,
- null,
- _deviceDiscovery,
- _userDataManager,
- _localization,
- _mediaSourceManager,
- _mediaEncoder,
- device);
-
- sessionInfo.AddController(controller);
-
- var profile = _dlnaManager.GetProfile(device.Properties.ToDeviceIdentification()) ??
- _dlnaManager.GetDefaultProfile();
-
- _sessionManager.ReportCapabilities(sessionInfo.Id, new ClientCapabilities
- {
- PlayableMediaTypes = profile.GetSupportedMediaTypes(),
-
- SupportedCommands = new[]
- {
- GeneralCommandType.VolumeDown,
- GeneralCommandType.VolumeUp,
- GeneralCommandType.Mute,
- GeneralCommandType.Unmute,
- GeneralCommandType.ToggleMute,
- GeneralCommandType.SetVolume,
- GeneralCommandType.SetAudioStreamIndex,
- GeneralCommandType.SetSubtitleStreamIndex,
- GeneralCommandType.PlayMediaSource
- },
-
- SupportsMediaControl = true
- });
-
- _logger.LogInformation("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName);
- }
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- _deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
-
- try
- {
- _disposeCancellationTokenSource.Cancel();
- }
- catch (Exception ex)
- {
- _logger.LogDebug(ex, "Error while disposing PlayToManager");
- }
-
- _sessionLock.Dispose();
- _disposeCancellationTokenSource.Dispose();
-
- _disposed = true;
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
deleted file mode 100644
index c95d8b1e84..0000000000
--- a/Emby.Dlna/PlayTo/PlaybackProgressEventArgs.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-
-namespace Emby.Dlna.PlayTo
-{
- public class PlaybackProgressEventArgs : EventArgs
- {
- public PlaybackProgressEventArgs(UBaseObject mediaInfo)
- {
- MediaInfo = mediaInfo;
- }
-
- public UBaseObject MediaInfo { get; set; }
- }
-}
diff --git a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
deleted file mode 100644
index 619c861ed9..0000000000
--- a/Emby.Dlna/PlayTo/PlaybackStartEventArgs.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-
-namespace Emby.Dlna.PlayTo
-{
- public class PlaybackStartEventArgs : EventArgs
- {
- public PlaybackStartEventArgs(UBaseObject mediaInfo)
- {
- MediaInfo = mediaInfo;
- }
-
- public UBaseObject MediaInfo { get; set; }
- }
-}
diff --git a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs b/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
deleted file mode 100644
index d0ec250591..0000000000
--- a/Emby.Dlna/PlayTo/PlaybackStoppedEventArgs.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-
-namespace Emby.Dlna.PlayTo
-{
- public class PlaybackStoppedEventArgs : EventArgs
- {
- public PlaybackStoppedEventArgs(UBaseObject mediaInfo)
- {
- MediaInfo = mediaInfo;
- }
-
- public UBaseObject MediaInfo { get; set; }
- }
-}
diff --git a/Emby.Dlna/PlayTo/PlaylistItem.cs b/Emby.Dlna/PlayTo/PlaylistItem.cs
deleted file mode 100644
index 5056e69ae7..0000000000
--- a/Emby.Dlna/PlayTo/PlaylistItem.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using MediaBrowser.Model.Dlna;
-
-namespace Emby.Dlna.PlayTo
-{
- public class PlaylistItem
- {
- public string StreamUrl { get; set; }
-
- public string Didl { get; set; }
-
- public StreamInfo StreamInfo { get; set; }
-
- public DeviceProfile Profile { get; set; }
- }
-}
diff --git a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs b/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
deleted file mode 100644
index 53cd05cfda..0000000000
--- a/Emby.Dlna/PlayTo/PlaylistItemFactory.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.IO;
-using System.Linq;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Session;
-
-namespace Emby.Dlna.PlayTo
-{
- public static class PlaylistItemFactory
- {
- public static PlaylistItem Create(Photo item, DeviceProfile profile)
- {
- var playlistItem = new PlaylistItem
- {
- StreamInfo = new StreamInfo
- {
- ItemId = item.Id,
- MediaType = DlnaProfileType.Photo,
- DeviceProfile = profile
- },
-
- Profile = profile
- };
-
- var directPlay = profile.DirectPlayProfiles
- .FirstOrDefault(i => i.Type == DlnaProfileType.Photo && IsSupported(i, item));
-
- if (directPlay is not null)
- {
- playlistItem.StreamInfo.PlayMethod = PlayMethod.DirectStream;
- playlistItem.StreamInfo.Container = Path.GetExtension(item.Path);
-
- return playlistItem;
- }
-
- var transcodingProfile = profile.TranscodingProfiles
- .FirstOrDefault(i => i.Type == DlnaProfileType.Photo);
-
- if (transcodingProfile is not null)
- {
- playlistItem.StreamInfo.PlayMethod = PlayMethod.Transcode;
- playlistItem.StreamInfo.Container = "." + transcodingProfile.Container.TrimStart('.');
- }
-
- return playlistItem;
- }
-
- private static bool IsSupported(DirectPlayProfile profile, Photo item)
- {
- var mediaPath = item.Path;
-
- if (profile.Container.Length > 0)
- {
- // Check container type
- var mediaContainer = (Path.GetExtension(mediaPath) ?? string.Empty).TrimStart('.');
-
- if (!profile.SupportsContainer(mediaContainer))
- {
- return false;
- }
- }
-
- return true;
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs
deleted file mode 100644
index 6b2096d9dc..0000000000
--- a/Emby.Dlna/PlayTo/TransportCommands.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Xml.Linq;
-using Emby.Dlna.Common;
-using Emby.Dlna.Ssdp;
-
-namespace Emby.Dlna.PlayTo
-{
- public class TransportCommands
- {
- private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
-
- public List<StateVariable> StateVariables { get; } = new List<StateVariable>();
-
- public List<ServiceAction> ServiceActions { get; } = new List<ServiceAction>();
-
- public static TransportCommands Create(XDocument document)
- {
- var command = new TransportCommands();
-
- var actionList = document.Descendants(UPnpNamespaces.Svc + "actionList");
-
- foreach (var container in actionList.Descendants(UPnpNamespaces.Svc + "action"))
- {
- command.ServiceActions.Add(ServiceActionFromXml(container));
- }
-
- var stateValues = document.Descendants(UPnpNamespaces.ServiceStateTable).FirstOrDefault();
-
- if (stateValues is not null)
- {
- foreach (var container in stateValues.Elements(UPnpNamespaces.Svc + "stateVariable"))
- {
- command.StateVariables.Add(FromXml(container));
- }
- }
-
- return command;
- }
-
- private static ServiceAction ServiceActionFromXml(XElement container)
- {
- var serviceAction = new ServiceAction
- {
- Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
- };
-
- var argumentList = serviceAction.ArgumentList;
-
- foreach (var arg in container.Descendants(UPnpNamespaces.Svc + "argument"))
- {
- argumentList.Add(ArgumentFromXml(arg));
- }
-
- return serviceAction;
- }
-
- private static Argument ArgumentFromXml(XElement container)
- {
- ArgumentNullException.ThrowIfNull(container);
-
- return new Argument
- {
- Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
- Direction = container.GetValue(UPnpNamespaces.Svc + "direction") ?? string.Empty,
- RelatedStateVariable = container.GetValue(UPnpNamespaces.Svc + "relatedStateVariable") ?? string.Empty
- };
- }
-
- private static StateVariable FromXml(XElement container)
- {
- var allowedValues = Array.Empty<string>();
- var element = container.Descendants(UPnpNamespaces.Svc + "allowedValueList")
- .FirstOrDefault();
-
- if (element is not null)
- {
- var values = element.Descendants(UPnpNamespaces.Svc + "allowedValue");
-
- allowedValues = values.Select(child => child.Value).ToArray();
- }
-
- return new StateVariable
- {
- Name = container.GetValue(UPnpNamespaces.Svc + "name") ?? string.Empty,
- DataType = container.GetValue(UPnpNamespaces.Svc + "dataType") ?? string.Empty,
- AllowedValues = allowedValues
- };
- }
-
- public string BuildPost(ServiceAction action, string xmlNamespace)
- {
- var stateString = string.Empty;
-
- foreach (var arg in action.ArgumentList)
- {
- if (string.Equals(arg.Direction, "out", StringComparison.Ordinal))
- {
- continue;
- }
-
- if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal))
- {
- stateString += BuildArgumentXml(arg, "0");
- }
- else
- {
- stateString += BuildArgumentXml(arg, null);
- }
- }
-
- return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
- }
-
- public string BuildPost(ServiceAction action, string xmlNamespace, object value, string commandParameter = "")
- {
- var stateString = string.Empty;
-
- foreach (var arg in action.ArgumentList)
- {
- if (string.Equals(arg.Direction, "out", StringComparison.Ordinal))
- {
- continue;
- }
-
- if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal))
- {
- stateString += BuildArgumentXml(arg, "0");
- }
- else
- {
- stateString += BuildArgumentXml(arg, value.ToString(), commandParameter);
- }
- }
-
- return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
- }
-
- public string BuildPost(ServiceAction action, string xmlNamespace, object value, Dictionary<string, string> dictionary)
- {
- var stateString = string.Empty;
-
- foreach (var arg in action.ArgumentList)
- {
- if (string.Equals(arg.Name, "InstanceID", StringComparison.Ordinal))
- {
- stateString += BuildArgumentXml(arg, "0");
- }
- else if (dictionary.TryGetValue(arg.Name, out var argValue))
- {
- stateString += BuildArgumentXml(arg, argValue);
- }
- else
- {
- stateString += BuildArgumentXml(arg, value.ToString());
- }
- }
-
- return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
- }
-
- private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "")
- {
- var state = StateVariables.FirstOrDefault(a => string.Equals(a.Name, argument.RelatedStateVariable, StringComparison.OrdinalIgnoreCase));
-
- if (state is not null)
- {
- var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
- (state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
-
- return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType, sendValue);
- }
-
- return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/TransportState.cs b/Emby.Dlna/PlayTo/TransportState.cs
deleted file mode 100644
index 0d6a78438c..0000000000
--- a/Emby.Dlna/PlayTo/TransportState.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-namespace Emby.Dlna.PlayTo
-{
- /// <summary>
- /// Core of the AVTransport service. It defines the conceptually top-
- /// level state of the transport, for example, whether it is playing, recording, etc.
- /// </summary>
- public enum TransportState
- {
- STOPPED,
- PLAYING,
- TRANSITIONING,
- PAUSED_PLAYBACK
- }
-}
diff --git a/Emby.Dlna/PlayTo/UpnpContainer.cs b/Emby.Dlna/PlayTo/UpnpContainer.cs
deleted file mode 100644
index 017d51e606..0000000000
--- a/Emby.Dlna/PlayTo/UpnpContainer.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Xml.Linq;
-using Emby.Dlna.Ssdp;
-
-namespace Emby.Dlna.PlayTo
-{
- public class UpnpContainer : UBaseObject
- {
- public static UBaseObject Create(XElement container)
- {
- ArgumentNullException.ThrowIfNull(container);
-
- return new UBaseObject
- {
- Id = container.GetAttributeValue(UPnpNamespaces.Id),
- ParentId = container.GetAttributeValue(UPnpNamespaces.ParentId),
- Title = container.GetValue(UPnpNamespaces.Title),
- IconUrl = container.GetValue(UPnpNamespaces.Artwork),
- UpnpClass = container.GetValue(UPnpNamespaces.Class)
- };
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/uBaseObject.cs b/Emby.Dlna/PlayTo/uBaseObject.cs
deleted file mode 100644
index a8f451405c..0000000000
--- a/Emby.Dlna/PlayTo/uBaseObject.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using Jellyfin.Data.Enums;
-
-namespace Emby.Dlna.PlayTo
-{
- public class UBaseObject
- {
- public string Id { get; set; }
-
- public string ParentId { get; set; }
-
- public string Title { get; set; }
-
- public string SecondText { get; set; }
-
- public string IconUrl { get; set; }
-
- public string MetaData { get; set; }
-
- public string Url { get; set; }
-
- public IReadOnlyList<string> ProtocolInfo { get; set; }
-
- public string UpnpClass { get; set; }
-
- public string MediaType
- {
- get
- {
- var classType = UpnpClass ?? string.Empty;
-
- if (classType.Contains("Audio", StringComparison.Ordinal))
- {
- return "Audio";
- }
-
- if (classType.Contains("Video", StringComparison.Ordinal))
- {
- return "Video";
- }
-
- if (classType.Contains("image", StringComparison.Ordinal))
- {
- return "Photo";
- }
-
- return null;
- }
- }
-
- public bool Equals(UBaseObject obj)
- {
- ArgumentNullException.ThrowIfNull(obj);
-
- return string.Equals(Id, obj.Id, StringComparison.Ordinal);
- }
- }
-}
diff --git a/Emby.Dlna/PlayTo/uPnpNamespaces.cs b/Emby.Dlna/PlayTo/uPnpNamespaces.cs
deleted file mode 100644
index 5042d44938..0000000000
--- a/Emby.Dlna/PlayTo/uPnpNamespaces.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Xml.Linq;
-
-namespace Emby.Dlna.PlayTo
-{
- public static class UPnpNamespaces
- {
- public static XNamespace Dc { get; } = "http://purl.org/dc/elements/1.1/";
-
- public static XNamespace Ns { get; } = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
-
- public static XNamespace Svc { get; } = "urn:schemas-upnp-org:service-1-0";
-
- public static XNamespace Ud { get; } = "urn:schemas-upnp-org:device-1-0";
-
- public static XNamespace UPnp { get; } = "urn:schemas-upnp-org:metadata-1-0/upnp/";
-
- public static XNamespace RenderingControl { get; } = "urn:schemas-upnp-org:service:RenderingControl:1";
-
- public static XNamespace AvTransport { get; } = "urn:schemas-upnp-org:service:AVTransport:1";
-
- public static XNamespace ContentDirectory { get; } = "urn:schemas-upnp-org:service:ContentDirectory:1";
-
- public static XName Containers { get; } = Ns + "container";
-
- public static XName Items { get; } = Ns + "item";
-
- public static XName Title { get; } = Dc + "title";
-
- public static XName Creator { get; } = Dc + "creator";
-
- public static XName Artist { get; } = UPnp + "artist";
-
- public static XName Id { get; } = "id";
-
- public static XName ParentId { get; } = "parentID";
-
- public static XName Class { get; } = UPnp + "class";
-
- public static XName Artwork { get; } = UPnp + "albumArtURI";
-
- public static XName Description { get; } = Dc + "description";
-
- public static XName LongDescription { get; } = UPnp + "longDescription";
-
- public static XName Album { get; } = UPnp + "album";
-
- public static XName Author { get; } = UPnp + "author";
-
- public static XName Director { get; } = UPnp + "director";
-
- public static XName PlayCount { get; } = UPnp + "playbackCount";
-
- public static XName Tracknumber { get; } = UPnp + "originalTrackNumber";
-
- public static XName Res { get; } = Ns + "res";
-
- public static XName Duration { get; } = "duration";
-
- public static XName ProtocolInfo { get; } = "protocolInfo";
-
- public static XName ServiceStateTable { get; } = Svc + "serviceStateTable";
-
- public static XName StateVariable { get; } = Svc + "stateVariable";
- }
-}