You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jellyfin/MediaBrowser.Dlna/PlayTo/DlnaController.cs

530 lines
18 KiB

using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Dlna.PlayTo
{
public class PlayToController : ISessionController, IDisposable
{
private Device _device;
private BaseItem _currentItem = null;
private readonly SessionInfo _session;
private readonly ISessionManager _sessionManager;
private readonly IItemRepository _itemRepository;
private readonly ILibraryManager _libraryManager;
private readonly INetworkManager _networkManager;
private readonly ILogger _logger;
private readonly IDlnaManager _dlnaManager;
private readonly IUserManager _userManager;
private readonly IServerApplicationHost _appHost;
private bool _playbackStarted = false;
private const int UpdateTimerIntervalMs = 1000;
public bool SupportsMediaRemoteControl
{
get { return true; }
}
public bool IsSessionActive
{
get
{
if (_device == null || _device.UpdateTime == default(DateTime))
return false;
return DateTime.UtcNow <= _device.UpdateTime.AddSeconds(30);
}
}
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost)
{
_session = session;
_itemRepository = itemRepository;
_sessionManager = sessionManager;
_libraryManager = libraryManager;
_networkManager = networkManager;
_dlnaManager = dlnaManager;
_userManager = userManager;
_appHost = appHost;
_logger = logger;
}
public void Init(Device device)
{
_device = device;
_device.PlaybackChanged += Device_PlaybackChanged;
_device.CurrentIdChanged += Device_CurrentIdChanged;
_device.Start();
_updateTimer = new Timer(updateTimer_Elapsed, null, UpdateTimerIntervalMs, UpdateTimerIntervalMs);
}
#region Device EventHandlers & Update Timer
Timer _updateTimer;
async void Device_PlaybackChanged(object sender, TransportStateEventArgs e)
{
if (_currentItem == null)
return;
if (e.Stopped == false)
await ReportProgress().ConfigureAwait(false);
else if (e.Stopped && _playbackStarted)
{
_playbackStarted = false;
await _sessionManager.OnPlaybackStopped(new Controller.Session.PlaybackStopInfo
{
Item = _currentItem,
SessionId = _session.Id,
PositionTicks = _device.Position.Ticks
}).ConfigureAwait(false);
await SetNext().ConfigureAwait(false);
}
}
async void Device_CurrentIdChanged(object sender, CurrentIdEventArgs e)
{
if (e.Id != Guid.Empty)
{
if (_currentItem != null && _currentItem.Id == e.Id)
{
return;
}
var item = _libraryManager.GetItemById(e.Id);
if (item != null)
{
_logger.Debug("{0} - CurrentId {1}", _session.DeviceName, item.Id);
_currentItem = item;
_playbackStarted = false;
await ReportProgress().ConfigureAwait(false);
}
}
}
/// <summary>
/// Handles the Elapsed event of the updateTimer control.
/// </summary>
/// <param name="state">The state.</param>
private async void updateTimer_Elapsed(object state)
{
if (_disposed)
return;
if (IsSessionActive)
{
await ReportProgress().ConfigureAwait(false);
}
else
{
_updateTimer.Change(Timeout.Infinite, Timeout.Infinite);
//Session is inactive, mark it for Disposal and don't start the elapsed timer.
await _sessionManager.ReportSessionEnded(_session.Id);
}
}
/// <summary>
/// Reports the playback progress.
/// </summary>
/// <returns></returns>
private async Task ReportProgress()
{
if (_currentItem == null || _device.IsStopped)
return;
if (!_playbackStarted)
{
await _sessionManager.OnPlaybackStart(new PlaybackInfo { Item = _currentItem, SessionId = _session.Id, CanSeek = true, QueueableMediaTypes = new List<string> { "Audio", "Video" } }).ConfigureAwait(false);
_playbackStarted = true;
}
if ((_device.IsPlaying || _device.IsPaused))
{
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
if (playlistItem != null && playlistItem.Transcode)
{
await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
{
Item = _currentItem,
SessionId = _session.Id,
PositionTicks = _device.Position.Ticks + playlistItem.StartPositionTicks,
IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused
}).ConfigureAwait(false);
}
else if (_currentItem != null)
{
await _sessionManager.OnPlaybackProgress(new Controller.Session.PlaybackProgressInfo
{
Item = _currentItem,
SessionId = _session.Id,
PositionTicks = _device.Position.Ticks,
IsMuted = _device.IsMuted,
IsPaused = _device.IsPaused
}).ConfigureAwait(false);
}
}
}
#endregion
#region SendCommands
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{
_logger.Debug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
var items = new List<BaseItem>();
foreach (string id in command.ItemIds)
{
AddItemFromId(Guid.Parse(id), items);
}
var playlist = new List<PlaylistItem>();
var isFirst = true;
var serverAddress = GetServerAddress();
foreach (var item in items)
{
if (isFirst && command.StartPositionTicks.HasValue)
{
playlist.Add(CreatePlaylistItem(item, command.StartPositionTicks.Value, serverAddress));
isFirst = false;
}
else
{
playlist.Add(CreatePlaylistItem(item, 0, serverAddress));
}
}
_logger.Debug("{0} - Playlist created", _session.DeviceName);
if (command.PlayCommand == PlayCommand.PlayLast)
{
AddItemsToPlaylist(playlist);
}
if (command.PlayCommand == PlayCommand.PlayNext)
{
AddItemsToPlaylist(playlist);
}
_logger.Debug("{0} - Playing {1} items", _session.DeviceName, playlist.Count);
if (!string.IsNullOrWhiteSpace(command.ControllingUserId))
{
var userId = new Guid(command.ControllingUserId);
var user = _userManager.GetUserById(userId);
await _sessionManager.LogSessionActivity(_session.Client, _session.ApplicationVersion, _session.DeviceId,
_session.DeviceName, _session.RemoteEndPoint, user).ConfigureAwait(false);
}
await PlayItems(playlist).ConfigureAwait(false);
}
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
{
switch (command.Command)
{
case PlaystateCommand.Stop:
Playlist.Clear();
return _device.SetStop();
case PlaystateCommand.Pause:
return _device.SetPause();
case PlaystateCommand.Unpause:
return _device.SetPlay();
case PlaystateCommand.Seek:
var playlistItem = Playlist.FirstOrDefault(p => p.PlayState == 1);
if (playlistItem != null && playlistItem.Transcode && playlistItem.MediaType == DlnaProfileType.Video && _currentItem != null)
{
var newItem = CreatePlaylistItem(_currentItem, command.SeekPositionTicks ?? 0, GetServerAddress());
playlistItem.StartPositionTicks = newItem.StartPositionTicks;
playlistItem.StreamUrl = newItem.StreamUrl;
playlistItem.Didl = newItem.Didl;
return _device.SetAvTransport(playlistItem.StreamUrl, playlistItem.DlnaHeaders, playlistItem.Didl);
}
return _device.Seek(TimeSpan.FromTicks(command.SeekPositionTicks ?? 0));
case PlaystateCommand.NextTrack:
_currentItem = null;
return SetNext();
case PlaystateCommand.PreviousTrack:
_currentItem = null;
return SetPrevious();
}
return Task.FromResult(true);
}
public Task SendSystemCommand(SystemCommand command, CancellationToken cancellationToken)
{
switch (command)
{
case SystemCommand.VolumeDown:
return _device.VolumeDown();
case SystemCommand.VolumeUp:
return _device.VolumeUp();
case SystemCommand.Mute:
return _device.VolumeDown(true);
case SystemCommand.Unmute:
return _device.VolumeUp(true);
case SystemCommand.ToggleMute:
return _device.ToggleMute();
default:
return Task.FromResult(true);
}
}
public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendServerRestartNotification(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendServerShutdownNotification(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendBrowseCommand(BrowseRequest command, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
public Task SendMessageCommand(MessageCommand command, CancellationToken cancellationToken)
{
return Task.FromResult(true);
}
#endregion
#region Playlist
private List<PlaylistItem> _playlist = new List<PlaylistItem>();
private List<PlaylistItem> Playlist
{
get
{
return _playlist;
}
set
{
_playlist = value;
}
}
private void AddItemFromId(Guid id, List<BaseItem> list)
{
var item = _libraryManager.GetItemById(id);
if (item.IsFolder)
{
foreach (var childId in _itemRepository.GetChildren(item.Id))
{
AddItemFromId(childId, list);
}
}
else
{
if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
{
list.Add(item);
}
}
}
private string GetServerAddress()
{
return string.Format("{0}://{1}:{2}/mediabrowser",
"http",
_networkManager.GetLocalIpAddresses().FirstOrDefault() ?? "localhost",
_appHost.HttpServerPort
);
}
private PlaylistItem CreatePlaylistItem(BaseItem item, long startPostionTicks, string serverAddress)
{
var streams = _itemRepository.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id }).ToList();
var deviceInfo = _device.Properties;
var playlistItem = GetPlaylistItem(item, _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()));
playlistItem.StartPositionTicks = startPostionTicks;
if (playlistItem.MediaType == DlnaProfileType.Audio)
{
playlistItem.StreamUrl = StreamHelper.GetAudioUrl(playlistItem, serverAddress);
}
else
{
playlistItem.StreamUrl = StreamHelper.GetVideoUrl(_device.Properties, playlistItem, streams, serverAddress);
}
var didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, streams);
playlistItem.Didl = didl;
var header = StreamHelper.GetDlnaHeaders(playlistItem);
playlistItem.DlnaHeaders = header;
return playlistItem;
}
private PlaylistItem GetPlaylistItem(BaseItem item, DeviceProfile profile)
{
var video = item as Video;
if (video != null)
{
return new PlaylistItemFactory(_itemRepository).Create(video, profile);
}
var audio = item as Audio;
if (audio != null)
{
return new PlaylistItemFactory(_itemRepository).Create(audio, profile);
}
var photo = item as Photo;
if (photo != null)
{
return new PlaylistItemFactory(_itemRepository).Create(photo, profile);
}
throw new ArgumentException("Unrecognized item type.");
}
/// <summary>
/// Plays the items.
/// </summary>
/// <param name="items">The items.</param>
/// <returns></returns>
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
{
Playlist.Clear();
Playlist.AddRange(items);
await SetNext();
return true;
}
/// <summary>
/// Adds the items to playlist.
/// </summary>
/// <param name="items">The items.</param>
private void AddItemsToPlaylist(IEnumerable<PlaylistItem> items)
{
Playlist.AddRange(items);
}
private async Task<bool> SetNext()
{
if (!Playlist.Any() || Playlist.All(i => i.PlayState != 0))
{
return true;
}
var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
if (currentitem != null)
{
currentitem.PlayState = 2;
}
var nextTrack = Playlist.FirstOrDefault(i => i.PlayState == 0);
if (nextTrack == null)
{
await _device.SetStop();
return true;
}
nextTrack.PlayState = 1;
_logger.Debug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", _device.Properties.Name, nextTrack.StreamUrl, nextTrack.DlnaHeaders);
await _device.SetAvTransport(nextTrack.StreamUrl, nextTrack.DlnaHeaders, nextTrack.Didl);
if (nextTrack.StartPositionTicks > 0 && !nextTrack.Transcode)
await _device.Seek(TimeSpan.FromTicks(nextTrack.StartPositionTicks));
return true;
}
public Task<bool> SetPrevious()
{
if (!Playlist.Any() || Playlist.All(i => i.PlayState != 2))
return Task.FromResult(false);
var currentitem = Playlist.FirstOrDefault(i => i.PlayState == 1);
var prevTrack = Playlist.LastOrDefault(i => i.PlayState == 2);
if (currentitem != null)
{
currentitem.PlayState = 0;
}
if (prevTrack == null)
return Task.FromResult(false);
prevTrack.PlayState = 1;
return _device.SetAvTransport(prevTrack.StreamUrl, prevTrack.DlnaHeaders, prevTrack.Didl);
}
#endregion
private bool _disposed;
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_updateTimer.Dispose();
_device.Dispose();
_logger.Log(LogSeverity.Debug, "Controller disposed");
}
}
}
}