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.UI/Playback/PlaybackManager.cs

299 lines
10 KiB

using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Kernel;
using MediaBrowser.Model.DTO;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Net;
using MediaBrowser.UI.Configuration;
using MediaBrowser.UI.Controller;
using MediaBrowser.UI.Playback.InternalPlayer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.UI.Playback
{
/// <summary>
/// Class PlaybackManager
/// </summary>
public class PlaybackManager : BaseManager<UIKernel>
{
/// <summary>
/// Initializes a new instance of the <see cref="PlaybackManager" /> class.
/// </summary>
/// <param name="kernel">The kernel.</param>
public PlaybackManager(UIKernel kernel)
: base(kernel)
{
}
#region PlaybackStarted Event
/// <summary>
/// Occurs when [playback started].
/// </summary>
public event EventHandler<PlaybackEventArgs> PlaybackStarted;
/// <summary>
/// Called when [playback started].
/// </summary>
/// <param name="player">The player.</param>
/// <param name="options">The options.</param>
/// <param name="playerConfiguration">The player configuration.</param>
private void OnPlaybackStarted(BaseMediaPlayer player, PlayOptions options, PlayerConfiguration playerConfiguration)
{
EventHelper.QueueEventIfNotNull(PlaybackStarted, this, new PlaybackEventArgs
{
Options = options,
Player = player,
PlayerConfiguration = playerConfiguration
});
}
#endregion
#region PlaybackCompleted Event
/// <summary>
/// Occurs when [playback completed].
/// </summary>
public event EventHandler<PlaybackStopEventArgs> PlaybackCompleted;
/// <summary>
/// Called when [playback completed].
/// </summary>
/// <param name="player">The player.</param>
/// <param name="items">The items.</param>
internal void OnPlaybackCompleted(BaseMediaPlayer player, List<DtoBaseItem> items)
{
EventHelper.QueueEventIfNotNull(PlaybackCompleted, this, new PlaybackStopEventArgs
{
Items = items,
Player = player
});
}
#endregion
/// <summary>
/// Gets the active players.
/// </summary>
/// <value>The active players.</value>
public IEnumerable<BaseMediaPlayer> ActivePlayers
{
get
{
return Kernel.MediaPlayers.Where(m => m.PlayState != PlayState.Idle);
}
}
/// <summary>
/// Gets the active internal players.
/// </summary>
/// <value>The active internal players.</value>
public IEnumerable<BaseMediaPlayer> ActiveInternalPlayers
{
get { return ActivePlayers.Where(p => p is BaseInternalMediaPlayer); }
}
/// <summary>
/// Plays the specified items.
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.InvalidOperationException"></exception>
public async Task Play(PlayOptions options)
{
if (options == null)
{
throw new ArgumentNullException("options");
}
if (options.Items == null || options.Items.Count == 0)
{
throw new ArgumentNullException("options");
}
var player = GetPlayer(options.Items);
if (player != null)
{
await StopAllPlayback();
await Play(player.Item1, options, player.Item2);
}
else
{
throw new InvalidOperationException();
}
}
/// <summary>
/// Plays the specified player.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="options">The options.</param>
/// <param name="playerConfiguration">The player configuration.</param>
/// <returns>Task.</returns>
private async Task Play(BaseMediaPlayer player, PlayOptions options, PlayerConfiguration playerConfiguration)
{
if (options.Shuffle)
{
options.Items = options.Items.Shuffle().ToList();
}
var firstItem = options.Items[0];
if (options.StartPositionTicks == 0 && player.SupportsMultiFilePlayback && firstItem.IsVideo && firstItem.LocationType == LocationType.FileSystem)
{
try
{
var intros = await UIKernel.Instance.ApiClient.GetIntrosAsync(firstItem.Id, App.Instance.CurrentUser.Id);
options.Items.InsertRange(0, intros.Select(GetPlayableItem));
}
catch (HttpException ex)
{
Logger.ErrorException("Error retrieving intros", ex);
}
}
await player.Play(options, playerConfiguration);
OnPlaybackStarted(player, options, playerConfiguration);
}
/// <summary>
/// Gets the playable item.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>DtoBaseItem.</returns>
public DtoBaseItem GetPlayableItem(string path)
{
return new DtoBaseItem
{
Path = path,
Name = Path.GetFileName(path),
Type = "Video",
VideoType = VideoType.VideoFile,
IsFolder = false
};
}
/// <summary>
/// Gets the playable item.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="name">The name.</param>
/// <returns>DtoBaseItem.</returns>
public DtoBaseItem GetPlayableItem(Uri uri, string name)
{
return new DtoBaseItem
{
Path = uri.ToString(),
Name = name,
Type = "Video",
VideoType = VideoType.VideoFile,
IsFolder = false,
LocationType = LocationType.Remote
};
}
/// <summary>
/// Stops all playback.
/// </summary>
/// <returns>Task.</returns>
public async Task StopAllPlayback()
{
var tasks = Kernel.MediaPlayers.Where(p => p.PlayState == PlayState.Playing || p.PlayState == PlayState.Paused).Select(p => p.Stop());
await Task.WhenAll(tasks);
}
/// <summary>
/// Gets the player.
/// </summary>
/// <param name="items">The items.</param>
/// <returns>BaseMediaPlayer.</returns>
private Tuple<BaseMediaPlayer, PlayerConfiguration> GetPlayer(List<DtoBaseItem> items)
{
var player = GetConfiguredPlayer(items);
if (player != null)
{
return player;
}
// If there's no explicit configuration just find the first matching player
var mediaPlayer = Kernel.MediaPlayers.OfType<BaseInternalMediaPlayer>().FirstOrDefault(p => items.All(p.CanPlay));
if (mediaPlayer != null)
{
return new Tuple<BaseMediaPlayer, PlayerConfiguration>(mediaPlayer, null);
}
return null;
}
/// <summary>
/// Gets the configured player.
/// </summary>
/// <param name="items">The items.</param>
/// <returns>BaseMediaPlayer.</returns>
private Tuple<BaseMediaPlayer, PlayerConfiguration> GetConfiguredPlayer(List<DtoBaseItem> items)
{
if (UIKernel.Instance.Configuration.MediaPlayers == null)
{
return null;
}
return UIKernel.Instance.Configuration.MediaPlayers.Where(p => IsConfiguredToPlay(p, items))
.Select(p => new Tuple<BaseMediaPlayer, PlayerConfiguration>(UIKernel.Instance.MediaPlayers.FirstOrDefault(m => m.Name.Equals(p.PlayerName, StringComparison.OrdinalIgnoreCase)), p))
.FirstOrDefault(p => p.Item1 != null);
}
/// <summary>
/// Determines whether [is configured to play] [the specified configuration].
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="items">The items.</param>
/// <returns><c>true</c> if [is configured to play] [the specified configuration]; otherwise, <c>false</c>.</returns>
private bool IsConfiguredToPlay(PlayerConfiguration configuration, List<DtoBaseItem> items)
{
if (configuration.ItemTypes != null && configuration.ItemTypes.Length > 0)
{
if (items.Any(i => !configuration.ItemTypes.Contains(i.Type, StringComparer.OrdinalIgnoreCase)))
{
return false;
}
}
if (configuration.FileExtensions != null && configuration.FileExtensions.Length > 0)
{
if (items.Any(i => !configuration.FileExtensions.Select(ext => ext.TrimStart('.')).Contains((Path.GetExtension(i.Path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase)))
{
return false;
}
}
if (configuration.VideoTypes != null && configuration.VideoTypes.Length > 0)
{
if (items.Any(i => i.VideoType.HasValue && !configuration.VideoTypes.Contains(i.VideoType.Value)))
{
return false;
}
}
if (configuration.VideoFormats != null && configuration.VideoFormats.Length > 0)
{
if (items.Any(i => i.VideoFormat.HasValue && !configuration.VideoFormats.Contains(i.VideoFormat.Value)))
{
return false;
}
}
return true;
}
}
}