mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-29 21:36:31 +01:00
@@ -23,7 +23,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
@@ -120,7 +120,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
if (query.IsFavorite.HasValue)
|
||||
{
|
||||
var val = query.IsFavorite.Value;
|
||||
channels = channels.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val)
|
||||
channels = channels.Where(i => _userDataManager.GetUserData(user, i).IsFavorite == val)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -273,7 +273,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
_jsonSerializer.SerializeToFile(mediaSources, path);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MediaSourceInfo>> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
IEnumerable<ChannelMediaInfo> results = GetSavedMediaSources(item);
|
||||
|
||||
@@ -460,12 +460,12 @@ namespace Emby.Server.Implementations.Channels
|
||||
|
||||
public IEnumerable<ChannelFeatures> GetAllChannelFeatures()
|
||||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery
|
||||
return _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||
SortBy = new[] { ItemSortBy.SortName }
|
||||
|
||||
}).Select(i => GetChannelFeatures(i.Id.ToString("N")));
|
||||
}).Select(i => GetChannelFeatures(i.ToString("N")));
|
||||
}
|
||||
|
||||
public ChannelFeatures GetChannelFeatures(string id)
|
||||
@@ -963,7 +963,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
}
|
||||
}
|
||||
|
||||
return await GetReturnItems(internalItems, providerTotalRecordCount, user, query).ConfigureAwait(false);
|
||||
return GetReturnItems(internalItems, providerTotalRecordCount, user, query);
|
||||
}
|
||||
|
||||
public async Task<QueryResult<BaseItemDto>> GetChannelItems(ChannelItemQuery query, CancellationToken cancellationToken)
|
||||
@@ -1154,7 +1154,7 @@ namespace Emby.Server.Implementations.Channels
|
||||
filename + ".json");
|
||||
}
|
||||
|
||||
private async Task<QueryResult<BaseItem>> GetReturnItems(IEnumerable<BaseItem> items,
|
||||
private QueryResult<BaseItem> GetReturnItems(IEnumerable<BaseItem> items,
|
||||
int? totalCountFromProvider,
|
||||
User user,
|
||||
ChannelItemQuery query)
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
return base.Supports(item);
|
||||
}
|
||||
|
||||
protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var playlist = (BoxSet)item;
|
||||
|
||||
@@ -73,10 +73,10 @@ namespace Emby.Server.Implementations.Collections
|
||||
.DistinctBy(i => i.Id)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(GetFinalItems(items, 2));
|
||||
return GetFinalItems(items, 2);
|
||||
}
|
||||
|
||||
protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
{
|
||||
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
|
||||
}
|
||||
|
||||
@@ -170,6 +170,11 @@ namespace Emby.Server.Implementations.Collections
|
||||
{
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.Path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentException("No item exists with the supplied Id");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.IO;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
|
||||
@@ -16,7 +16,6 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
|
||||
@@ -24,7 +24,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
@@ -107,7 +107,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
var dto = await GetBaseItemDtoInternal(item, options, user, owner).ConfigureAwait(false);
|
||||
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
||||
|
||||
var tvChannel = item as LiveTvChannel;
|
||||
if (tvChannel != null)
|
||||
@@ -127,7 +127,11 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
var libraryItems = byName.GetTaggedItems(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
}
|
||||
});
|
||||
|
||||
SetItemByNameInfo(item, dto, libraryItems.ToList(), user);
|
||||
@@ -156,7 +160,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
var syncDictionary = GetSyncedItemProgress(options);
|
||||
|
||||
var dto = GetBaseItemDtoInternal(item, options, user, owner).Result;
|
||||
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
||||
var tvChannel = item as LiveTvChannel;
|
||||
if (tvChannel != null)
|
||||
{
|
||||
@@ -177,7 +181,11 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
if (options.Fields.Contains(ItemFields.ItemCounts))
|
||||
{
|
||||
SetItemByNameInfo(item, dto, GetTaggedItems(byName, user), user);
|
||||
SetItemByNameInfo(item, dto, GetTaggedItems(byName, user, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
|
||||
}), user);
|
||||
}
|
||||
|
||||
FillSyncInfo(dto, item, options, user, syncDictionary);
|
||||
@@ -189,11 +197,12 @@ namespace Emby.Server.Implementations.Dto
|
||||
return dto;
|
||||
}
|
||||
|
||||
private List<BaseItem> GetTaggedItems(IItemByName byName, User user)
|
||||
private List<BaseItem> GetTaggedItems(IItemByName byName, User user, DtoOptions options)
|
||||
{
|
||||
var items = byName.GetTaggedItems(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = options
|
||||
|
||||
}).ToList();
|
||||
|
||||
@@ -283,7 +292,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<BaseItemDto> GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null)
|
||||
{
|
||||
var fields = options.Fields;
|
||||
|
||||
@@ -332,7 +341,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
await AttachUserSpecificInfo(dto, item, user, options).ConfigureAwait(false);
|
||||
AttachUserSpecificInfo(dto, item, user, options);
|
||||
}
|
||||
|
||||
var hasMediaSources = item as IHasMediaSources;
|
||||
@@ -393,7 +402,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
public BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, Dictionary<string, SyncedItemProgress> syncProgress, User user = null)
|
||||
{
|
||||
var dto = GetBaseItemDtoInternal(item, options, user).Result;
|
||||
var dto = GetBaseItemDtoInternal(item, options, user);
|
||||
|
||||
if (taggedItems != null && options.Fields.Contains(ItemFields.ItemCounts))
|
||||
{
|
||||
@@ -446,7 +455,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
/// <summary>
|
||||
/// Attaches the user specific info.
|
||||
/// </summary>
|
||||
private async Task AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions)
|
||||
private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var fields = dtoOptions.Fields;
|
||||
|
||||
@@ -456,7 +465,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (dtoOptions.EnableUserData)
|
||||
{
|
||||
dto.UserData = await _userDataRepository.GetUserDataDto(item, dto, user, dtoOptions.Fields).ConfigureAwait(false);
|
||||
dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, dtoOptions.Fields);
|
||||
}
|
||||
|
||||
if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
|
||||
@@ -488,7 +497,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
if (dtoOptions.EnableUserData)
|
||||
{
|
||||
dto.UserData = await _userDataRepository.GetUserDataDto(item, user).ConfigureAwait(false);
|
||||
dto.UserData = _userDataRepository.GetUserDataDto(item, user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,16 +604,17 @@ namespace Emby.Server.Implementations.Dto
|
||||
{
|
||||
if (!string.IsNullOrEmpty(item.Album))
|
||||
{
|
||||
var parentAlbum = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
var parentAlbumIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
|
||||
Name = item.Album
|
||||
Name = item.Album,
|
||||
Limit = 1
|
||||
|
||||
}).FirstOrDefault();
|
||||
});
|
||||
|
||||
if (parentAlbum != null)
|
||||
if (parentAlbumIds.Count > 0)
|
||||
{
|
||||
dto.AlbumId = GetDtoId(parentAlbum);
|
||||
dto.AlbumId = parentAlbumIds[0].ToString("N");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,45 +761,41 @@ namespace Emby.Server.Implementations.Dto
|
||||
/// <returns>Task.</returns>
|
||||
private void AttachStudios(BaseItemDto dto, BaseItem item)
|
||||
{
|
||||
var studios = item.Studios.ToList();
|
||||
dto.Studios = item.Studios
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i,
|
||||
Id = _libraryManager.GetStudioId(i).ToString("N")
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
dto.Studios = new StudioDto[studios.Count];
|
||||
private void AttachGenreItems(BaseItemDto dto, BaseItem item)
|
||||
{
|
||||
dto.GenreItems = item.Genres
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i,
|
||||
Id = GetStudioId(i, item)
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
var dictionary = studios.Distinct(StringComparer.OrdinalIgnoreCase).Select(name =>
|
||||
private string GetStudioId(string name, BaseItem owner)
|
||||
{
|
||||
if (owner is IHasMusicGenres)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _libraryManager.GetStudio(name);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting studio {0}", ex, name);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (var i = 0; i < studios.Count; i++)
|
||||
{
|
||||
var studio = studios[i];
|
||||
|
||||
var studioDto = new StudioDto
|
||||
{
|
||||
Name = studio
|
||||
};
|
||||
|
||||
Studio entity;
|
||||
|
||||
if (dictionary.TryGetValue(studio, out entity))
|
||||
{
|
||||
studioDto.Id = entity.Id.ToString("N");
|
||||
studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
|
||||
}
|
||||
|
||||
dto.Studios[i] = studioDto;
|
||||
return _libraryManager.GetGameGenreId(name).ToString("N");
|
||||
}
|
||||
|
||||
if (owner is Game || owner is GameSystem)
|
||||
{
|
||||
return _libraryManager.GetGameGenreId(name).ToString("N");
|
||||
}
|
||||
|
||||
return _libraryManager.GetGenreId(name).ToString("N");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -901,6 +907,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
if (fields.Contains(ItemFields.Genres))
|
||||
{
|
||||
dto.Genres = item.Genres;
|
||||
AttachGenreItems(dto, item);
|
||||
}
|
||||
|
||||
if (options.EnableImages)
|
||||
@@ -1130,7 +1137,10 @@ namespace Emby.Server.Implementations.Dto
|
||||
return null;
|
||||
}
|
||||
|
||||
var artist = _libraryManager.GetArtist(i);
|
||||
var artist = _libraryManager.GetArtist(i, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
});
|
||||
if (artist != null)
|
||||
{
|
||||
return new NameIdPair
|
||||
@@ -1179,7 +1189,10 @@ namespace Emby.Server.Implementations.Dto
|
||||
return null;
|
||||
}
|
||||
|
||||
var artist = _libraryManager.GetArtist(i);
|
||||
var artist = _libraryManager.GetArtist(i, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
});
|
||||
if (artist != null)
|
||||
{
|
||||
return new NameIdPair
|
||||
@@ -1449,7 +1462,7 @@ namespace Emby.Server.Implementations.Dto
|
||||
var musicAlbum = item as MusicAlbum;
|
||||
if (musicAlbum != null)
|
||||
{
|
||||
var artist = musicAlbum.MusicArtist;
|
||||
var artist = musicAlbum.GetMusicArtist(new DtoOptions(false));
|
||||
if (artist != null)
|
||||
{
|
||||
return artist;
|
||||
@@ -1582,24 +1595,30 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
ImageSize size;
|
||||
|
||||
if (supportedEnhancers.Count == 0)
|
||||
{
|
||||
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
||||
var defaultAspectRatio = item.GetDefaultPrimaryImageAspectRatio();
|
||||
|
||||
if (defaultAspectRatio.HasValue)
|
||||
if (defaultAspectRatio.HasValue)
|
||||
{
|
||||
if (supportedEnhancers.Count == 0)
|
||||
{
|
||||
return defaultAspectRatio.Value;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
size = _imageProcessor.GetImageSize(imageInfo);
|
||||
double dummyWidth = 200;
|
||||
double dummyHeight = dummyWidth / defaultAspectRatio.Value;
|
||||
size = new ImageSize(dummyWidth, dummyHeight);
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
//_logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path);
|
||||
return null;
|
||||
try
|
||||
{
|
||||
size = _imageProcessor.GetImageSize(imageInfo);
|
||||
}
|
||||
catch
|
||||
{
|
||||
//_logger.ErrorException("Failed to determine primary image aspect ratio for {0}", ex, path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var enhancer in supportedEnhancers)
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
<RootNamespace>Emby.Server.Implementations</RootNamespace>
|
||||
<AssemblyName>Emby.Server.Implementations</AssemblyName>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile />
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -103,6 +102,7 @@
|
||||
<Compile Include="HttpServer\SocketSharp\WebSocketSharpResponse.cs" />
|
||||
<Compile Include="HttpServer\StreamWriter.cs" />
|
||||
<Compile Include="Images\BaseDynamicImageProvider.cs" />
|
||||
<Compile Include="IO\AsyncStreamCopier.cs" />
|
||||
<Compile Include="IO\FileRefresher.cs" />
|
||||
<Compile Include="IO\MbLinkShortcutHandler.cs" />
|
||||
<Compile Include="IO\ThrottledStream.cs" />
|
||||
@@ -182,7 +182,6 @@
|
||||
<Compile Include="Migrations\IVersionMigration.cs" />
|
||||
<Compile Include="Migrations\LibraryScanMigration.cs" />
|
||||
<Compile Include="Migrations\GuideMigration.cs" />
|
||||
<Compile Include="Migrations\UpdateLevelMigration.cs" />
|
||||
<Compile Include="News\NewsEntryPoint.cs" />
|
||||
<Compile Include="News\NewsService.cs" />
|
||||
<Compile Include="Notifications\CoreNotificationTypes.cs" />
|
||||
@@ -291,9 +290,9 @@
|
||||
<Project>{2e781478-814d-4a48-9d80-bff206441a65}</Project>
|
||||
<Name>MediaBrowser.Server.Implementations</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\SocketHttpListener.Portable\SocketHttpListener.Portable.csproj">
|
||||
<Project>{4f26d5d8-a7b0-42b3-ba42-7cb7d245934e}</Project>
|
||||
<Name>SocketHttpListener.Portable</Name>
|
||||
<ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj">
|
||||
<Project>{1d74413b-e7cf-455b-b021-f52bdf881542}</Project>
|
||||
<Name>SocketHttpListener</Name>
|
||||
</ProjectReference>
|
||||
<Reference Include="Emby.XmlTv, Version=1.0.6299.28292, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Emby.XmlTv.1.0.8\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath>
|
||||
@@ -312,6 +311,17 @@
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Localization\Core\ar.json" />
|
||||
<EmbeddedResource Include="Localization\Core\bg-BG.json" />
|
||||
@@ -410,7 +420,7 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Localization\Ratings\uk.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
||||
@@ -122,7 +122,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
.DistinctBy(i => i.Id)
|
||||
.Select(i =>
|
||||
{
|
||||
var dto = _userDataManager.GetUserDataDto(i, user).Result;
|
||||
var dto = _userDataManager.GetUserDataDto(i, user);
|
||||
dto.ItemId = i.Id.ToString("N");
|
||||
return dto;
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Library;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -588,7 +588,8 @@ namespace Emby.Server.Implementations.FileOrganization
|
||||
var series = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
})
|
||||
.Cast<Series>()
|
||||
.Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i))
|
||||
@@ -607,7 +608,8 @@ namespace Emby.Server.Implementations.FileOrganization
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
Recursive = true,
|
||||
Name = info.ItemName
|
||||
Name = info.ItemName,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).Cast<Series>().FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.HttpServer.SocketSharp;
|
||||
@@ -445,10 +446,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// <summary>
|
||||
/// Overridable method that can be used to implement a custom hnandler
|
||||
/// </summary>
|
||||
/// <param name="httpReq">The HTTP req.</param>
|
||||
/// <param name="url">The URL.</param>
|
||||
/// <returns>Task.</returns>
|
||||
protected async Task RequestHandler(IHttpRequest httpReq, Uri url)
|
||||
protected async Task RequestHandler(IHttpRequest httpReq, Uri url, CancellationToken cancellationToken)
|
||||
{
|
||||
var date = DateTime.Now;
|
||||
var httpRes = httpReq.Response;
|
||||
@@ -589,7 +587,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName).ConfigureAwait(false);
|
||||
await handler.ProcessRequestAsync(this, httpReq, httpRes, Logger, operationName, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -58,6 +58,18 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return GetHttpResult(content, contentType, true, responseHeaders);
|
||||
}
|
||||
|
||||
public object GetRedirectResult(string url)
|
||||
{
|
||||
var responseHeaders = new Dictionary<string, string>();
|
||||
responseHeaders["Location"] = url;
|
||||
|
||||
var result = new HttpResult(new byte[] { }, "text/plain", HttpStatusCode.Redirect);
|
||||
|
||||
AddResponseHeaders(result, responseHeaders);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HTTP result.
|
||||
/// </summary>
|
||||
@@ -599,9 +611,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IHasHeaders> GetCompressedResult(Stream stream,
|
||||
string requestedCompressionType,
|
||||
IDictionary<string,string> responseHeaders,
|
||||
private async Task<IHasHeaders> GetCompressedResult(Stream stream,
|
||||
string requestedCompressionType,
|
||||
IDictionary<string, string> responseHeaders,
|
||||
bool isHeadRequest,
|
||||
string contentType)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediaBrowser.Controller.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
@@ -18,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
/// Gets or sets the request handler.
|
||||
/// </summary>
|
||||
/// <value>The request handler.</value>
|
||||
Func<IHttpRequest, Uri, Task> RequestHandler { get; set; }
|
||||
Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the web socket handler.
|
||||
|
||||
@@ -4,6 +4,7 @@ using SocketHttpListener.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
@@ -33,6 +34,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
private readonly bool _enableDualMode;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||
private CancellationToken _disposeCancellationToken;
|
||||
|
||||
public WebSocketSharpListener(ILogger logger, ICertificate certificate, IMemoryStreamFactory memoryStreamProvider, ITextEncoding textEncoding, INetworkManager networkManager, ISocketFactory socketFactory, ICryptoProvider cryptoProvider, IStreamFactory streamFactory, bool enableDualMode, Func<HttpListenerContext, IHttpRequest> httpRequestFactory, IFileSystem fileSystem, IEnvironmentInfo environment)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -47,10 +51,12 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
_httpRequestFactory = httpRequestFactory;
|
||||
_fileSystem = fileSystem;
|
||||
_environment = environment;
|
||||
|
||||
_disposeCancellationToken = _disposeCancellationTokenSource.Token;
|
||||
}
|
||||
|
||||
public Action<Exception, IRequest, bool> ErrorHandler { get; set; }
|
||||
public Func<IHttpRequest, Uri, Task> RequestHandler { get; set; }
|
||||
public Func<IHttpRequest, Uri, CancellationToken, Task> RequestHandler { get; set; }
|
||||
|
||||
public Action<WebSocketConnectingEventArgs> WebSocketConnecting { get; set; }
|
||||
|
||||
@@ -81,11 +87,11 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
|
||||
private void ProcessContext(HttpListenerContext context)
|
||||
{
|
||||
//Task.Factory.StartNew(() => InitTask(context), TaskCreationOptions.DenyChildAttach | TaskCreationOptions.PreferFairness);
|
||||
Task.Run(() => InitTask(context));
|
||||
InitTask(context, _disposeCancellationToken);
|
||||
//Task.Run(() => InitTask(context, _disposeCancellationToken));
|
||||
}
|
||||
|
||||
private Task InitTask(HttpListenerContext context)
|
||||
private Task InitTask(HttpListenerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
IHttpRequest httpReq = null;
|
||||
var request = context.Request;
|
||||
@@ -111,7 +117,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return RequestHandler(httpReq, request.Url);
|
||||
return RequestHandler(httpReq, request.Url, cancellationToken);
|
||||
}
|
||||
|
||||
private void ProcessWebSocketRequest(HttpListenerContext ctx)
|
||||
@@ -172,6 +178,8 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
_disposeCancellationTokenSource.Cancel();
|
||||
|
||||
if (_listener != null)
|
||||
{
|
||||
foreach (var prefix in _listener.Prefixes.ToList())
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.HttpServer.SocketSharp;
|
||||
@@ -374,7 +373,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
this.pathInfo = request.RawUrl;
|
||||
}
|
||||
|
||||
this.pathInfo = WebUtility.UrlDecode(pathInfo);
|
||||
this.pathInfo = System.Net.WebUtility.UrlDecode(pathInfo);
|
||||
this.pathInfo = NormalizePathInfo(pathInfo, mode);
|
||||
}
|
||||
return this.pathInfo;
|
||||
@@ -440,7 +439,7 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
cookies = new Dictionary<string, System.Net.Cookie>();
|
||||
foreach (var cookie in this.request.Cookies)
|
||||
{
|
||||
var httpCookie = (Cookie) cookie;
|
||||
var httpCookie = (System.Net.Cookie) cookie;
|
||||
cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,15 +114,9 @@ namespace Emby.Server.Implementations.HttpServer.SocketSharp
|
||||
var outputStream = response.OutputStream;
|
||||
|
||||
// This is needed with compression
|
||||
if (outputStream is ResponseStream)
|
||||
{
|
||||
//if (!string.IsNullOrWhiteSpace(GetHeader("Content-Encoding")))
|
||||
{
|
||||
outputStream.Flush();
|
||||
}
|
||||
outputStream.Flush();
|
||||
outputStream.Dispose();
|
||||
|
||||
outputStream.Dispose();
|
||||
}
|
||||
response.Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
459
Emby.Server.Implementations/IO/AsyncStreamCopier.cs
Normal file
459
Emby.Server.Implementations/IO/AsyncStreamCopier.cs
Normal file
@@ -0,0 +1,459 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.IO
|
||||
{
|
||||
public class AsyncStreamCopier : IDisposable
|
||||
{
|
||||
// size in bytes of the buffers in the buffer pool
|
||||
private const int DefaultBufferSize = 81920;
|
||||
private readonly int _bufferSize;
|
||||
// number of buffers in the pool
|
||||
private const int DefaultBufferCount = 4;
|
||||
private readonly int _bufferCount;
|
||||
|
||||
// indexes of the next buffer to read into/write from
|
||||
private int _nextReadBuffer = -1;
|
||||
private int _nextWriteBuffer = -1;
|
||||
|
||||
// the buffer pool, implemented as an array, and used in a cyclic way
|
||||
private readonly byte[][] _buffers;
|
||||
// sizes in bytes of the available (read) data in the buffers
|
||||
private readonly int[] _sizes;
|
||||
// the streams...
|
||||
private Stream _source;
|
||||
private Stream _target;
|
||||
private readonly bool _closeStreamsOnEnd;
|
||||
|
||||
// number of buffers that are ready to be written
|
||||
private int _buffersToWrite;
|
||||
// flag indicating that there is still a read operation to be scheduled
|
||||
// (source end of stream not reached)
|
||||
private volatile bool _moreDataToRead;
|
||||
// the result of the whole operation, returned by BeginCopy()
|
||||
private AsyncResult _asyncResult;
|
||||
// any exception that occurs during an async operation
|
||||
// stored here for rethrow
|
||||
private Exception _exception;
|
||||
|
||||
public TaskCompletionSource<long> TaskCompletionSource;
|
||||
private long _bytesToRead;
|
||||
private long _totalBytesWritten;
|
||||
private CancellationToken _cancellationToken;
|
||||
public int IndividualReadOffset = 0;
|
||||
|
||||
public AsyncStreamCopier(Stream source,
|
||||
Stream target,
|
||||
long bytesToRead,
|
||||
CancellationToken cancellationToken,
|
||||
bool closeStreamsOnEnd = false,
|
||||
int bufferSize = DefaultBufferSize,
|
||||
int bufferCount = DefaultBufferCount)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException("source");
|
||||
if (target == null)
|
||||
throw new ArgumentNullException("target");
|
||||
if (!source.CanRead)
|
||||
throw new ArgumentException("Cannot copy from a non-readable stream.");
|
||||
if (!target.CanWrite)
|
||||
throw new ArgumentException("Cannot copy to a non-writable stream.");
|
||||
_source = source;
|
||||
_target = target;
|
||||
_moreDataToRead = true;
|
||||
_closeStreamsOnEnd = closeStreamsOnEnd;
|
||||
_bufferSize = bufferSize;
|
||||
_bufferCount = bufferCount;
|
||||
_buffers = new byte[_bufferCount][];
|
||||
_sizes = new int[_bufferCount];
|
||||
_bytesToRead = bytesToRead;
|
||||
_cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
~AsyncStreamCopier()
|
||||
{
|
||||
// ensure any exception cannot be ignored
|
||||
ThrowExceptionIfNeeded();
|
||||
}
|
||||
|
||||
public static Task<long> CopyStream(Stream source, Stream target, int bufferSize, int bufferCount, CancellationToken cancellationToken)
|
||||
{
|
||||
return CopyStream(source, target, 0, bufferSize, bufferCount, cancellationToken);
|
||||
}
|
||||
|
||||
public static Task<long> CopyStream(Stream source, Stream target, long size, int bufferSize, int bufferCount, CancellationToken cancellationToken)
|
||||
{
|
||||
var copier = new AsyncStreamCopier(source, target, size, cancellationToken, false, bufferSize, bufferCount);
|
||||
var taskCompletion = new TaskCompletionSource<long>();
|
||||
|
||||
copier.TaskCompletionSource = taskCompletion;
|
||||
|
||||
var result = copier.BeginCopy(StreamCopyCallback, copier);
|
||||
|
||||
if (result.CompletedSynchronously)
|
||||
{
|
||||
StreamCopyCallback(result);
|
||||
}
|
||||
|
||||
cancellationToken.Register(() => taskCompletion.TrySetCanceled());
|
||||
|
||||
return taskCompletion.Task;
|
||||
}
|
||||
|
||||
private static void StreamCopyCallback(IAsyncResult result)
|
||||
{
|
||||
var copier = (AsyncStreamCopier)result.AsyncState;
|
||||
var taskCompletion = copier.TaskCompletionSource;
|
||||
|
||||
try
|
||||
{
|
||||
copier.EndCopy(result);
|
||||
taskCompletion.TrySetResult(copier._totalBytesWritten);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_asyncResult != null)
|
||||
_asyncResult.Dispose();
|
||||
if (_closeStreamsOnEnd)
|
||||
{
|
||||
if (_source != null)
|
||||
{
|
||||
_source.Dispose();
|
||||
_source = null;
|
||||
}
|
||||
if (_target != null)
|
||||
{
|
||||
_target.Dispose();
|
||||
_target = null;
|
||||
}
|
||||
}
|
||||
GC.SuppressFinalize(this);
|
||||
ThrowExceptionIfNeeded();
|
||||
}
|
||||
|
||||
public IAsyncResult BeginCopy(AsyncCallback callback, object state)
|
||||
{
|
||||
// avoid concurrent start of the copy on separate threads
|
||||
if (Interlocked.CompareExchange(ref _asyncResult, new AsyncResult(callback, state), null) != null)
|
||||
throw new InvalidOperationException("A copy operation has already been started on this object.");
|
||||
// allocate buffers
|
||||
for (int i = 0; i < _bufferCount; i++)
|
||||
_buffers[i] = new byte[_bufferSize];
|
||||
|
||||
// we pass false to BeginRead() to avoid completing the async result
|
||||
// immediately which would result in invoking the callback
|
||||
// when the method fails synchronously
|
||||
BeginRead(false);
|
||||
// throw exception synchronously if there is one
|
||||
ThrowExceptionIfNeeded();
|
||||
return _asyncResult;
|
||||
}
|
||||
|
||||
public void EndCopy(IAsyncResult ar)
|
||||
{
|
||||
if (ar != _asyncResult)
|
||||
throw new InvalidOperationException("Invalid IAsyncResult object.");
|
||||
|
||||
if (!_asyncResult.IsCompleted)
|
||||
_asyncResult.AsyncWaitHandle.WaitOne();
|
||||
|
||||
if (_closeStreamsOnEnd)
|
||||
{
|
||||
_source.Close();
|
||||
_source = null;
|
||||
_target.Close();
|
||||
_target = null;
|
||||
}
|
||||
|
||||
//_logger.Info("AsyncStreamCopier {0} bytes requested. {1} bytes transferred", _bytesToRead, _totalBytesWritten);
|
||||
ThrowExceptionIfNeeded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Here we'll throw a pending exception if there is one,
|
||||
/// and remove it from our instance, so we know it has been consumed.
|
||||
/// </summary>
|
||||
private void ThrowExceptionIfNeeded()
|
||||
{
|
||||
if (_exception != null)
|
||||
{
|
||||
var exception = _exception;
|
||||
_exception = null;
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginRead(bool completeOnError = true)
|
||||
{
|
||||
if (!_moreDataToRead)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (_asyncResult.IsCompleted)
|
||||
return;
|
||||
int bufferIndex = Interlocked.Increment(ref _nextReadBuffer) % _bufferCount;
|
||||
|
||||
try
|
||||
{
|
||||
_source.BeginRead(_buffers[bufferIndex], 0, _bufferSize, EndRead, bufferIndex);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_exception = exception;
|
||||
if (completeOnError)
|
||||
_asyncResult.Complete(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void BeginWrite()
|
||||
{
|
||||
if (_asyncResult.IsCompleted)
|
||||
return;
|
||||
// this method can actually be called concurrently!!
|
||||
// indeed, let's say we call a BeginWrite, and the thread gets interrupted
|
||||
// just after making the IO request.
|
||||
// At that moment, the thread is still in the method. And then the IO request
|
||||
// ends (extremely fast io, or caching...), EndWrite gets called
|
||||
// on another thread, and calls BeginWrite again! There we have it!
|
||||
// That is the reason why an Interlocked is needed here.
|
||||
int bufferIndex = Interlocked.Increment(ref _nextWriteBuffer) % _bufferCount;
|
||||
|
||||
try
|
||||
{
|
||||
int bytesToWrite;
|
||||
if (_bytesToRead > 0)
|
||||
{
|
||||
var bytesLeftToWrite = _bytesToRead - _totalBytesWritten;
|
||||
bytesToWrite = Convert.ToInt32(Math.Min(_sizes[bufferIndex], bytesLeftToWrite));
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesToWrite = _sizes[bufferIndex];
|
||||
}
|
||||
|
||||
_target.BeginWrite(_buffers[bufferIndex], IndividualReadOffset, bytesToWrite - IndividualReadOffset, EndWrite, null);
|
||||
|
||||
_totalBytesWritten += bytesToWrite;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_exception = exception;
|
||||
_asyncResult.Complete(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void EndRead(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
int read = _source.EndRead(ar);
|
||||
_moreDataToRead = read > 0;
|
||||
var bufferIndex = (int)ar.AsyncState;
|
||||
_sizes[bufferIndex] = read;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_exception = exception;
|
||||
_asyncResult.Complete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_moreDataToRead && !_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
int usedBuffers = Interlocked.Increment(ref _buffersToWrite);
|
||||
// if we incremented from zero to one, then it means we just
|
||||
// added the single buffer to write, so a writer could not
|
||||
// be busy, and we have to schedule one.
|
||||
if (usedBuffers == 1)
|
||||
BeginWrite();
|
||||
// test if there is at least a free buffer, and schedule
|
||||
// a read, as we have read some data
|
||||
if (usedBuffers < _bufferCount)
|
||||
BeginRead();
|
||||
}
|
||||
else
|
||||
{
|
||||
// we did not add a buffer, because no data was read, and
|
||||
// there is no buffer left to write so this is the end...
|
||||
if (Thread.VolatileRead(ref _buffersToWrite) == 0)
|
||||
{
|
||||
_asyncResult.Complete(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EndWrite(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
_target.EndWrite(ar);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
_exception = exception;
|
||||
_asyncResult.Complete(false);
|
||||
return;
|
||||
}
|
||||
|
||||
int buffersLeftToWrite = Interlocked.Decrement(ref _buffersToWrite);
|
||||
// no reader could be active if all buffers were full of data waiting to be written
|
||||
bool noReaderIsBusy = buffersLeftToWrite == _bufferCount - 1;
|
||||
// note that it is possible that both a reader and
|
||||
// a writer see the end of the copy and call Complete
|
||||
// on the _asyncResult object. That race condition is handled by
|
||||
// Complete that ensures it is only executed fully once.
|
||||
|
||||
long bytesLeftToWrite;
|
||||
if (_bytesToRead > 0)
|
||||
{
|
||||
bytesLeftToWrite = _bytesToRead - _totalBytesWritten;
|
||||
}
|
||||
else
|
||||
{
|
||||
bytesLeftToWrite = 1;
|
||||
}
|
||||
|
||||
if (!_moreDataToRead || bytesLeftToWrite <= 0 || _cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// at this point we know no reader can schedule a read or write
|
||||
if (Thread.VolatileRead(ref _buffersToWrite) == 0)
|
||||
{
|
||||
// nothing left to write, so it is the end
|
||||
_asyncResult.Complete(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
// here, we know we have something left to read,
|
||||
// so schedule a read if no read is busy
|
||||
if (noReaderIsBusy)
|
||||
BeginRead();
|
||||
|
||||
// also schedule a write if we are sure we did not write the last buffer
|
||||
// note that if buffersLeftToWrite is zero and a reader has put another
|
||||
// buffer to write between the time we decremented _buffersToWrite
|
||||
// and now, that reader will also schedule another write,
|
||||
// as it will increment _buffersToWrite from zero to one
|
||||
if (buffersLeftToWrite > 0)
|
||||
BeginWrite();
|
||||
}
|
||||
}
|
||||
|
||||
internal class AsyncResult : IAsyncResult, IDisposable
|
||||
{
|
||||
// Fields set at construction which never change while
|
||||
// operation is pending
|
||||
private readonly AsyncCallback _asyncCallback;
|
||||
private readonly object _asyncState;
|
||||
|
||||
// Fields set at construction which do change after
|
||||
// operation completes
|
||||
private const int StatePending = 0;
|
||||
private const int StateCompletedSynchronously = 1;
|
||||
private const int StateCompletedAsynchronously = 2;
|
||||
private int _completedState = StatePending;
|
||||
|
||||
// Field that may or may not get set depending on usage
|
||||
private ManualResetEvent _waitHandle;
|
||||
|
||||
internal AsyncResult(
|
||||
AsyncCallback asyncCallback,
|
||||
object state)
|
||||
{
|
||||
_asyncCallback = asyncCallback;
|
||||
_asyncState = state;
|
||||
}
|
||||
|
||||
internal bool Complete(bool completedSynchronously)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
// The _completedState field MUST be set prior calling the callback
|
||||
int prevState = Interlocked.CompareExchange(ref _completedState,
|
||||
completedSynchronously ? StateCompletedSynchronously :
|
||||
StateCompletedAsynchronously, StatePending);
|
||||
if (prevState == StatePending)
|
||||
{
|
||||
// If the event exists, set it
|
||||
if (_waitHandle != null)
|
||||
_waitHandle.Set();
|
||||
|
||||
if (_asyncCallback != null)
|
||||
_asyncCallback(this);
|
||||
|
||||
result = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Implementation of IAsyncResult
|
||||
|
||||
public Object AsyncState { get { return _asyncState; } }
|
||||
|
||||
public bool CompletedSynchronously
|
||||
{
|
||||
get
|
||||
{
|
||||
return Thread.VolatileRead(ref _completedState) ==
|
||||
StateCompletedSynchronously;
|
||||
}
|
||||
}
|
||||
|
||||
public WaitHandle AsyncWaitHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_waitHandle == null)
|
||||
{
|
||||
bool done = IsCompleted;
|
||||
var mre = new ManualResetEvent(done);
|
||||
if (Interlocked.CompareExchange(ref _waitHandle,
|
||||
mre, null) != null)
|
||||
{
|
||||
// Another thread created this object's event; dispose
|
||||
// the event we just created
|
||||
mre.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!done && IsCompleted)
|
||||
{
|
||||
// If the operation wasn't done when we created
|
||||
// the event but now it is done, set the event
|
||||
_waitHandle.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
return _waitHandle;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
return Thread.VolatileRead(ref _completedState) !=
|
||||
StatePending;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_waitHandle != null)
|
||||
{
|
||||
_waitHandle.Dispose();
|
||||
_waitHandle = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.IO;
|
||||
@@ -180,7 +180,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
try
|
||||
{
|
||||
await item.ChangedExternally().ConfigureAwait(false);
|
||||
item.ChangedExternally();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
@@ -231,7 +231,7 @@ namespace Emby.Server.Implementations.IO
|
||||
|
||||
private bool IsFileLocked(string path)
|
||||
{
|
||||
if (_environmentInfo.OperatingSystem != OperatingSystem.Windows)
|
||||
if (_environmentInfo.OperatingSystem != MediaBrowser.Model.System.OperatingSystem.Windows)
|
||||
{
|
||||
// Causing lockups on linux
|
||||
return false;
|
||||
@@ -282,11 +282,11 @@ namespace Emby.Server.Implementations.IO
|
||||
return false;
|
||||
}
|
||||
}
|
||||
//catch (DirectoryNotFoundException)
|
||||
//{
|
||||
// // File may have been deleted
|
||||
// return false;
|
||||
//}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
// File may have been deleted
|
||||
return false;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// File may have been deleted
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.IO;
|
||||
@@ -59,33 +59,6 @@ namespace Emby.Server.Implementations.Images
|
||||
//return GetSupportedImages(item).Where(i => IsEnabled(options, i, item)).ToList();
|
||||
}
|
||||
|
||||
private bool IsEnabled(MetadataOptions options, ImageType type, IHasImages item)
|
||||
{
|
||||
if (type == ImageType.Backdrop)
|
||||
{
|
||||
if (item.LockedFields.Contains(MetadataFields.Backdrops))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (type == ImageType.Screenshot)
|
||||
{
|
||||
if (item.LockedFields.Contains(MetadataFields.Screenshots))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.LockedFields.Contains(MetadataFields.Images))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return options.IsEnabled(type);
|
||||
}
|
||||
|
||||
public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!Supports(item))
|
||||
@@ -128,7 +101,7 @@ namespace Emby.Server.Implementations.Images
|
||||
}
|
||||
}
|
||||
|
||||
var items = await GetItemsWithImages(item).ConfigureAwait(false);
|
||||
var items = GetItemsWithImages(item);
|
||||
|
||||
return await FetchToFileInternal(item, items, imageType, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -140,7 +113,7 @@ namespace Emby.Server.Implementations.Images
|
||||
{
|
||||
var outputPathWithoutExtension = Path.Combine(ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPathWithoutExtension));
|
||||
string outputPath = await CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0).ConfigureAwait(false);
|
||||
string outputPath = CreateImage(item, itemsWithImages, outputPathWithoutExtension, imageType, 0);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(outputPath))
|
||||
{
|
||||
@@ -159,9 +132,9 @@ namespace Emby.Server.Implementations.Images
|
||||
return ItemUpdateType.ImageUpdate;
|
||||
}
|
||||
|
||||
protected abstract Task<List<BaseItem>> GetItemsWithImages(IHasImages item);
|
||||
protected abstract List<BaseItem> GetItemsWithImages(IHasImages item);
|
||||
|
||||
protected Task<string> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
|
||||
protected string CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
|
||||
{
|
||||
return CreateCollage(primaryItem, items, outputPath, 640, 360);
|
||||
}
|
||||
@@ -188,22 +161,22 @@ namespace Emby.Server.Implementations.Images
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i));
|
||||
}
|
||||
|
||||
protected Task<string> CreatePosterCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
|
||||
protected string CreatePosterCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
|
||||
{
|
||||
return CreateCollage(primaryItem, items, outputPath, 400, 600);
|
||||
}
|
||||
|
||||
protected Task<string> CreateSquareCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
|
||||
protected string CreateSquareCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath)
|
||||
{
|
||||
return CreateCollage(primaryItem, items, outputPath, 600, 600);
|
||||
}
|
||||
|
||||
protected Task<string> CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
|
||||
protected string CreateThumbCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
|
||||
{
|
||||
return CreateCollage(primaryItem, items, outputPath, width, height);
|
||||
}
|
||||
|
||||
private async Task<string> CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
|
||||
private string CreateCollage(IHasImages primaryItem, List<BaseItem> items, string outputPath, int width, int height)
|
||||
{
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(outputPath));
|
||||
|
||||
@@ -225,7 +198,7 @@ namespace Emby.Server.Implementations.Images
|
||||
return null;
|
||||
}
|
||||
|
||||
await ImageProcessor.CreateImageCollage(options).ConfigureAwait(false);
|
||||
ImageProcessor.CreateImageCollage(options);
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
@@ -234,7 +207,7 @@ namespace Emby.Server.Implementations.Images
|
||||
get { return "Dynamic Image Provider"; }
|
||||
}
|
||||
|
||||
protected virtual async Task<string> CreateImage(IHasImages item,
|
||||
protected virtual string CreateImage(IHasImages item,
|
||||
List<BaseItem> itemsWithImages,
|
||||
string outputPathWithoutExtension,
|
||||
ImageType imageType,
|
||||
@@ -249,20 +222,20 @@ namespace Emby.Server.Implementations.Images
|
||||
|
||||
if (imageType == ImageType.Thumb)
|
||||
{
|
||||
return await CreateThumbCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
|
||||
return CreateThumbCollage(item, itemsWithImages, outputPath);
|
||||
}
|
||||
|
||||
if (imageType == ImageType.Primary)
|
||||
{
|
||||
if (item is UserView)
|
||||
{
|
||||
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
|
||||
return CreateSquareCollage(item, itemsWithImages, outputPath);
|
||||
}
|
||||
if (item is Playlist || item is MusicGenre || item is Genre || item is GameGenre || item is PhotoAlbum)
|
||||
{
|
||||
return await CreateSquareCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
|
||||
return CreateSquareCollage(item, itemsWithImages, outputPath);
|
||||
}
|
||||
return await CreatePosterCollage(item, itemsWithImages, outputPath).ConfigureAwait(false);
|
||||
return CreatePosterCollage(item, itemsWithImages, outputPath);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Unexpected image type");
|
||||
@@ -346,7 +319,7 @@ namespace Emby.Server.Implementations.Images
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<string> CreateSingleImage(List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType)
|
||||
protected string CreateSingleImage(List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType)
|
||||
{
|
||||
var image = itemsWithImages
|
||||
.Where(i => i.HasImage(imageType) && i.GetImageInfo(imageType, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(imageType)))
|
||||
|
||||
@@ -6,7 +6,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
||||
@@ -40,7 +40,8 @@ using MediaBrowser.Model.Net;
|
||||
using SortOrder = MediaBrowser.Model.Entities.SortOrder;
|
||||
using VideoResolver = MediaBrowser.Naming.Video.VideoResolver;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
@@ -313,7 +314,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Season).Name },
|
||||
Recursive = true,
|
||||
IndexNumber = 0
|
||||
IndexNumber = 0,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).Cast<Season>()
|
||||
.Where(i => !string.Equals(i.Name, newName, StringComparison.Ordinal))
|
||||
@@ -342,7 +344,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
if (item is IItemByName)
|
||||
{
|
||||
if (!(item is MusicArtist) && !(item is Studio))
|
||||
if (!(item is MusicArtist))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -862,13 +864,19 @@ namespace Emby.Server.Implementations.Library
|
||||
// If this returns multiple items it could be tricky figuring out which one is correct.
|
||||
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
throw new ArgumentNullException("path");
|
||||
}
|
||||
|
||||
var query = new InternalItemsQuery
|
||||
{
|
||||
Path = path,
|
||||
IsFolder = isFolder,
|
||||
SortBy = new[] { ItemSortBy.DateCreated },
|
||||
SortOrder = SortOrder.Descending,
|
||||
Limit = 1
|
||||
Limit = 1,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
};
|
||||
|
||||
return GetItemList(query)
|
||||
@@ -882,7 +890,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <returns>Task{Person}.</returns>
|
||||
public Person GetPerson(string name)
|
||||
{
|
||||
return CreateItemByName<Person>(Person.GetPath, name);
|
||||
return CreateItemByName<Person>(Person.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -892,7 +900,27 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <returns>Task{Studio}.</returns>
|
||||
public Studio GetStudio(string name)
|
||||
{
|
||||
return CreateItemByName<Studio>(Studio.GetPath, name);
|
||||
return CreateItemByName<Studio>(Studio.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
public Guid GetStudioId(string name)
|
||||
{
|
||||
return GetItemByNameId<Studio>(Studio.GetPath, name);
|
||||
}
|
||||
|
||||
public Guid GetGenreId(string name)
|
||||
{
|
||||
return GetItemByNameId<Genre>(Genre.GetPath, name);
|
||||
}
|
||||
|
||||
public Guid GetMusicGenreId(string name)
|
||||
{
|
||||
return GetItemByNameId<MusicGenre>(MusicGenre.GetPath, name);
|
||||
}
|
||||
|
||||
public Guid GetGameGenreId(string name)
|
||||
{
|
||||
return GetItemByNameId<GameGenre>(GameGenre.GetPath, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -902,7 +930,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <returns>Task{Genre}.</returns>
|
||||
public Genre GetGenre(string name)
|
||||
{
|
||||
return CreateItemByName<Genre>(Genre.GetPath, name);
|
||||
return CreateItemByName<Genre>(Genre.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -912,7 +940,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <returns>Task{MusicGenre}.</returns>
|
||||
public MusicGenre GetMusicGenre(string name)
|
||||
{
|
||||
return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name);
|
||||
return CreateItemByName<MusicGenre>(MusicGenre.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -922,7 +950,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <returns>Task{GameGenre}.</returns>
|
||||
public GameGenre GetGameGenre(string name)
|
||||
{
|
||||
return CreateItemByName<GameGenre>(GameGenre.GetPath, name);
|
||||
return CreateItemByName<GameGenre>(GameGenre.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -940,7 +968,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var name = value.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
return CreateItemByName<Year>(Year.GetPath, name);
|
||||
return CreateItemByName<Year>(Year.GetPath, name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -950,10 +978,15 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <returns>Task{Genre}.</returns>
|
||||
public MusicArtist GetArtist(string name)
|
||||
{
|
||||
return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name);
|
||||
return GetArtist(name, new DtoOptions(true));
|
||||
}
|
||||
|
||||
private T CreateItemByName<T>(Func<string, string> getPathFn, string name)
|
||||
public MusicArtist GetArtist(string name, DtoOptions options)
|
||||
{
|
||||
return CreateItemByName<MusicArtist>(MusicArtist.GetPath, name, options);
|
||||
}
|
||||
|
||||
private T CreateItemByName<T>(Func<string, string> getPathFn, string name, DtoOptions options)
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
if (typeof(T) == typeof(MusicArtist))
|
||||
@@ -961,7 +994,8 @@ namespace Emby.Server.Implementations.Library
|
||||
var existing = GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(T).Name },
|
||||
Name = name
|
||||
Name = name,
|
||||
DtoOptions = options
|
||||
|
||||
}).Cast<MusicArtist>()
|
||||
.OrderBy(i => i.IsAccessedByName ? 1 : 0)
|
||||
@@ -974,14 +1008,13 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
var path = getPathFn(name);
|
||||
var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
|
||||
var id = GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
|
||||
var id = GetItemByNameId<T>(getPathFn, name);
|
||||
|
||||
var item = GetItemById(id) as T;
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
var path = getPathFn(name);
|
||||
item = new T
|
||||
{
|
||||
Name = name,
|
||||
@@ -998,52 +1031,12 @@ namespace Emby.Server.Implementations.Library
|
||||
return item;
|
||||
}
|
||||
|
||||
public IEnumerable<MusicArtist> GetAlbumArtists(IEnumerable<IHasAlbumArtist> items)
|
||||
private Guid GetItemByNameId<T>(Func<string, string> getPathFn, string name)
|
||||
where T : BaseItem, new()
|
||||
{
|
||||
var names = items
|
||||
.SelectMany(i => i.AlbumArtists)
|
||||
.DistinctNames()
|
||||
.Select(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var artist = GetArtist(i);
|
||||
|
||||
return artist;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Already logged at lower levels
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
public IEnumerable<MusicArtist> GetArtists(IEnumerable<IHasArtist> items)
|
||||
{
|
||||
var names = items
|
||||
.SelectMany(i => i.AllArtists)
|
||||
.DistinctNames()
|
||||
.Select(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var artist = GetArtist(i);
|
||||
|
||||
return artist;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Already logged at lower levels
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null);
|
||||
|
||||
return names;
|
||||
var path = getPathFn(name);
|
||||
var forceCaseInsensitiveId = ConfigurationManager.Configuration.EnableNormalizedItemByNameIds;
|
||||
return GetNewItemIdInternal(path, typeof(T), forceCaseInsensitiveId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1098,12 +1091,6 @@ namespace Emby.Server.Implementations.Library
|
||||
try
|
||||
{
|
||||
await PerformLibraryValidation(progress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey)
|
||||
{
|
||||
ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey = true;
|
||||
ConfigurationManager.SaveConfiguration();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1558,7 +1545,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
}
|
||||
|
||||
query.ParentId = null;
|
||||
query.Parent = null;
|
||||
}
|
||||
|
||||
private void AddUserToQuery(InternalItemsQuery query, User user)
|
||||
|
||||
@@ -6,6 +6,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
|
||||
@@ -27,7 +28,8 @@ namespace Emby.Server.Implementations.Library
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(BoxSet).Name, typeof(Game).Name, typeof(Movie).Name, typeof(Series).Name },
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).OfType<IHasTrailers>().ToList();
|
||||
|
||||
@@ -40,7 +42,8 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Trailer).Name },
|
||||
TrailerTypes = trailerTypes,
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
|
||||
}).ToArray();
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Playlists;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Model.Querying;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
@@ -18,47 +19,48 @@ namespace Emby.Server.Implementations.Library
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var list = new List<Audio>
|
||||
{
|
||||
item
|
||||
};
|
||||
|
||||
return list.Concat(GetInstantMixFromGenres(item.Genres, user));
|
||||
return list.Concat(GetInstantMixFromGenres(item.Genres, user, dtoOptions));
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist item, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return GetInstantMixFromGenres(item.Genres, user);
|
||||
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return GetInstantMixFromGenres(item.Genres, user);
|
||||
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var genres = item
|
||||
.GetRecursiveChildren(user, new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name }
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
||||
DtoOptions = dtoOptions
|
||||
})
|
||||
.Cast<Audio>()
|
||||
.SelectMany(i => i.Genres)
|
||||
.Concat(item.Genres)
|
||||
.DistinctNames();
|
||||
|
||||
return GetInstantMixFromGenres(genres, user);
|
||||
return GetInstantMixFromGenres(genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return GetInstantMixFromGenres(item.Genres, user);
|
||||
return GetInstantMixFromGenres(item.Genres, user, dtoOptions);
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var genreIds = genres.DistinctNames().Select(i =>
|
||||
{
|
||||
@@ -73,10 +75,10 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
}).Where(i => i != null);
|
||||
|
||||
return GetInstantMixFromGenreIds(genreIds, user);
|
||||
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromGenreIds(IEnumerable<string> genreIds, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
@@ -86,47 +88,49 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
Limit = 200,
|
||||
|
||||
SortBy = new[] { ItemSortBy.Random }
|
||||
SortBy = new[] { ItemSortBy.Random },
|
||||
|
||||
DtoOptions = dtoOptions
|
||||
|
||||
}).Cast<Audio>();
|
||||
}
|
||||
|
||||
public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user)
|
||||
public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var genre = item as MusicGenre;
|
||||
if (genre != null)
|
||||
{
|
||||
return GetInstantMixFromGenreIds(new[] { item.Id.ToString("N") }, user);
|
||||
return GetInstantMixFromGenreIds(new[] { item.Id.ToString("N") }, user, dtoOptions);
|
||||
}
|
||||
|
||||
var playlist = item as Playlist;
|
||||
if (playlist != null)
|
||||
{
|
||||
return GetInstantMixFromPlaylist(playlist, user);
|
||||
return GetInstantMixFromPlaylist(playlist, user, dtoOptions);
|
||||
}
|
||||
|
||||
var album = item as MusicAlbum;
|
||||
if (album != null)
|
||||
{
|
||||
return GetInstantMixFromAlbum(album, user);
|
||||
return GetInstantMixFromAlbum(album, user, dtoOptions);
|
||||
}
|
||||
|
||||
var artist = item as MusicArtist;
|
||||
if (artist != null)
|
||||
{
|
||||
return GetInstantMixFromArtist(artist, user);
|
||||
return GetInstantMixFromArtist(artist, user, dtoOptions);
|
||||
}
|
||||
|
||||
var song = item as Audio;
|
||||
if (song != null)
|
||||
{
|
||||
return GetInstantMixFromSong(song, user);
|
||||
return GetInstantMixFromSong(song, user, dtoOptions);
|
||||
}
|
||||
|
||||
var folder = item as Folder;
|
||||
if (folder != null)
|
||||
{
|
||||
return GetInstantMixFromFolder(folder, user);
|
||||
return GetInstantMixFromFolder(folder, user, dtoOptions);
|
||||
}
|
||||
|
||||
return new Audio[] { };
|
||||
|
||||
@@ -5,8 +5,6 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
|
||||
@@ -8,7 +8,7 @@ using MediaBrowser.Naming.Audio;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
|
||||
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
|
||||
@@ -11,7 +11,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
||||
@@ -5,7 +5,7 @@ using MediaBrowser.Controller.Resolvers;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
|
||||
@@ -8,6 +8,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
|
||||
namespace Emby.Server.Implementations.Library
|
||||
@@ -175,7 +176,17 @@ namespace Emby.Server.Implementations.Library
|
||||
IsNews = query.IsNews,
|
||||
IsSeries = query.IsSeries,
|
||||
IsSports = query.IsSports,
|
||||
MediaTypes = query.MediaTypes
|
||||
MediaTypes = query.MediaTypes,
|
||||
|
||||
DtoOptions = new DtoOptions
|
||||
{
|
||||
Fields = new List<ItemFields>
|
||||
{
|
||||
ItemFields.AirTime,
|
||||
ItemFields.DateCreated,
|
||||
ItemFields.ChannelInfo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add search hints based on item name
|
||||
|
||||
@@ -182,21 +182,21 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetUserData(userId, item.Id, item.GetUserDataKeys());
|
||||
}
|
||||
|
||||
public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, User user)
|
||||
public UserItemDataDto GetUserDataDto(IHasUserData item, User user)
|
||||
{
|
||||
var userData = GetUserData(user.Id, item);
|
||||
var dto = GetUserItemDataDto(userData);
|
||||
|
||||
await item.FillUserDataDtoValues(dto, userData, null, user, new List<ItemFields>()).ConfigureAwait(false);
|
||||
item.FillUserDataDtoValues(dto, userData, null, user, new List<ItemFields>());
|
||||
return dto;
|
||||
}
|
||||
|
||||
public async Task<UserItemDataDto> GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields)
|
||||
public UserItemDataDto GetUserDataDto(IHasUserData item, BaseItemDto itemDto, User user, List<ItemFields> fields)
|
||||
{
|
||||
var userData = GetUserData(user.Id, item);
|
||||
var dto = GetUserItemDataDto(userData);
|
||||
|
||||
await item.FillUserDataDtoValues(dto, userData, itemDto, user, fields).ConfigureAwait(false);
|
||||
item.FillUserDataDtoValues(dto, userData, itemDto, user, fields);
|
||||
return dto;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
|
||||
@@ -190,11 +191,11 @@ namespace Emby.Server.Implementations.Library
|
||||
return _libraryManager.GetShadowView(parent, viewType, sortName, cancellationToken);
|
||||
}
|
||||
|
||||
public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)
|
||||
public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request, DtoOptions options)
|
||||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
|
||||
var libraryItems = GetItemsForLatestItems(user, request);
|
||||
var libraryItems = GetItemsForLatestItems(user, request, options);
|
||||
|
||||
var list = new List<Tuple<BaseItem, List<BaseItem>>>();
|
||||
|
||||
@@ -230,7 +231,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return list;
|
||||
}
|
||||
|
||||
private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request)
|
||||
private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request, DtoOptions options)
|
||||
{
|
||||
var parentId = request.ParentId;
|
||||
|
||||
@@ -289,7 +290,8 @@ namespace Emby.Server.Implementations.Library
|
||||
IsVirtualItem = false,
|
||||
Limit = limit * 5,
|
||||
SourceTypes = parents.Count == 0 ? new[] { SourceType.Library } : new SourceType[] { },
|
||||
IsPlayed = isPlayed
|
||||
IsPlayed = isPlayed,
|
||||
DtoOptions = options
|
||||
|
||||
}, parents);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
|
||||
@@ -3,9 +3,10 @@ using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
||||
@@ -29,7 +30,35 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
public Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
if (directStreamProvider != null)
|
||||
{
|
||||
return RecordFromDirectStreamProvider(directStreamProvider, targetFile, duration, onStarted, cancellationToken);
|
||||
}
|
||||
|
||||
return RecordFromMediaSource(mediaSource, targetFile, duration, onStarted, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task RecordFromDirectStreamProvider(IDirectStreamProvider directStreamProvider, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
{
|
||||
onStarted();
|
||||
|
||||
_logger.Info("Copying recording stream to file {0}", targetFile);
|
||||
|
||||
// The media source if infinite so we need to handle stopping ourselves
|
||||
var durationToken = new CancellationTokenSource(duration);
|
||||
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
||||
|
||||
await directStreamProvider.CopyToAsync(output, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.Info("Recording completed to file {0}", targetFile);
|
||||
}
|
||||
|
||||
private async Task RecordFromMediaSource(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
var httpRequestOptions = new HttpRequestOptions
|
||||
{
|
||||
|
||||
@@ -28,8 +28,9 @@ using System.Xml;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.IO;
|
||||
@@ -1232,7 +1233,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
RequiresClosing = false,
|
||||
Protocol = MediaBrowser.Model.MediaInfo.MediaProtocol.Http,
|
||||
BufferMs = 0,
|
||||
IgnoreDts = true
|
||||
IgnoreDts = true,
|
||||
IgnoreIndex = true
|
||||
};
|
||||
|
||||
var isAudio = false;
|
||||
@@ -1517,7 +1519,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
EnforceKeepUpTo(timer, seriesPath);
|
||||
};
|
||||
|
||||
await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
|
||||
await recorder.Record(liveStreamInfo.Item1 as IDirectStreamProvider, mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
recordingStatus = RecordingStatus.Completed;
|
||||
_logger.Info("Recording completed: {0}", recordPath);
|
||||
@@ -1634,15 +1636,16 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return;
|
||||
}
|
||||
|
||||
var episodesToDelete = (await librarySeries.GetItems(new InternalItemsQuery
|
||||
var episodesToDelete = (librarySeries.GetItems(new InternalItemsQuery
|
||||
{
|
||||
SortBy = new[] { ItemSortBy.DateCreated },
|
||||
SortOrder = SortOrder.Descending,
|
||||
IsVirtualItem = false,
|
||||
IsFolder = false,
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
}))
|
||||
.Items
|
||||
.Where(i => i.LocationType == LocationType.FileSystem && _fileSystem.FileExists(i.Path))
|
||||
.Skip(seriesTimer.KeepUpTo - 1)
|
||||
@@ -1759,20 +1762,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
var config = GetConfiguration();
|
||||
|
||||
if (config.EnableRecordingEncoding)
|
||||
{
|
||||
var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false);
|
||||
var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false);
|
||||
|
||||
if (regInfo.IsValid)
|
||||
if (regInfo.IsValid)
|
||||
{
|
||||
if (config.EnableRecordingEncoding)
|
||||
{
|
||||
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, config, _httpClient, _processFactory, _config);
|
||||
}
|
||||
|
||||
return new DirectRecorder(_logger, _httpClient, _fileSystem);
|
||||
|
||||
//var options = new LiveTvOptions
|
||||
//{
|
||||
// EnableOriginalAudioWithEncodedRecordings = true,
|
||||
// RecordedVideoCodec = "copy",
|
||||
// RecordingEncodingFormat = "ts"
|
||||
//};
|
||||
//return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, options, _httpClient, _processFactory, _config);
|
||||
}
|
||||
|
||||
return new DirectRecorder(_logger, _httpClient, _fileSystem);
|
||||
throw new InvalidOperationException("Emby DVR Requires an active Emby Premiere subscription.");
|
||||
}
|
||||
|
||||
private async void OnSuccessfulRecording(TimerInfo timer, string path)
|
||||
private void OnSuccessfulRecording(TimerInfo timer, string path)
|
||||
{
|
||||
//if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize)
|
||||
//{
|
||||
@@ -1967,7 +1980,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
|
||||
Limit = 1,
|
||||
ExternalId = timer.ProgramId
|
||||
ExternalId = timer.ProgramId,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).FirstOrDefault() as LiveTvProgram;
|
||||
|
||||
@@ -2501,16 +2515,17 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
if (program.EpisodeNumber.HasValue && program.SeasonNumber.HasValue)
|
||||
{
|
||||
var result = _libraryManager.GetItemsResult(new InternalItemsQuery
|
||||
var result = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
ParentIndexNumber = program.SeasonNumber.Value,
|
||||
IndexNumber = program.EpisodeNumber.Value,
|
||||
AncestorIds = seriesIds,
|
||||
IsVirtualItem = false
|
||||
IsVirtualItem = false,
|
||||
Limit = 1
|
||||
});
|
||||
|
||||
if (result.TotalRecordCount > 0)
|
||||
if (result.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@@ -21,6 +21,7 @@ using MediaBrowser.Model.LiveTv;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
@@ -64,6 +65,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
return "mkv";
|
||||
}
|
||||
if (string.Equals(format, "ts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "ts";
|
||||
}
|
||||
|
||||
return "mp4";
|
||||
}
|
||||
@@ -90,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
return Path.ChangeExtension(targetFile, "." + extension);
|
||||
}
|
||||
|
||||
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
public async Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
//var durationToken = new CancellationTokenSource(duration);
|
||||
//cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
|
||||
@@ -177,6 +182,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
videoArgs = "-codec:v:0 copy";
|
||||
}
|
||||
|
||||
videoArgs += " -fflags +genpts";
|
||||
|
||||
var durationParam = " -t " + _mediaEncoder.GetTimeParameter(duration.Ticks);
|
||||
|
||||
var flags = new List<string>();
|
||||
@@ -188,28 +195,37 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
flags.Add("+ignidx");
|
||||
}
|
||||
if (mediaSource.GenPtsInput)
|
||||
{
|
||||
flags.Add("+genpts");
|
||||
}
|
||||
|
||||
var inputModifiers = "-async 1 -vsync -1";
|
||||
var inputModifier = "-async 1 -vsync -1";
|
||||
|
||||
if (flags.Count > 0)
|
||||
{
|
||||
inputModifiers += " -fflags " + string.Join("", flags.ToArray());
|
||||
inputModifier += " -fflags " + string.Join("", flags.ToArray());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(GetEncodingOptions().HardwareAccelerationType))
|
||||
{
|
||||
inputModifiers += " -hwaccel auto";
|
||||
inputModifier += " -hwaccel auto";
|
||||
}
|
||||
|
||||
if (mediaSource.ReadAtNativeFramerate)
|
||||
{
|
||||
inputModifiers += " -re";
|
||||
inputModifier += " -re";
|
||||
}
|
||||
|
||||
if (mediaSource.RequiresLooping)
|
||||
{
|
||||
inputModifier += " -stream_loop -1";
|
||||
}
|
||||
|
||||
var analyzeDurationSeconds = 5;
|
||||
var analyzeDuration = " -analyzeduration " +
|
||||
(analyzeDurationSeconds * 1000000).ToString(CultureInfo.InvariantCulture);
|
||||
inputModifiers += analyzeDuration;
|
||||
inputModifier += analyzeDuration;
|
||||
|
||||
var subtitleArgs = CopySubtitles ? " -codec:s copy" : " -sn";
|
||||
|
||||
@@ -228,7 +244,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
durationParam,
|
||||
outputParam);
|
||||
|
||||
return inputModifiers + " " + commandLineArgs;
|
||||
return inputModifier + " " + commandLineArgs;
|
||||
}
|
||||
|
||||
private string GetAudioArgs(MediaSourceInfo mediaSource)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
@@ -10,13 +11,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
/// <summary>
|
||||
/// Records the specified media source.
|
||||
/// </summary>
|
||||
/// <param name="mediaSource">The media source.</param>
|
||||
/// <param name="targetFile">The target file.</param>
|
||||
/// <param name="duration">The duration.</param>
|
||||
/// <param name="onStarted">The on started.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
|
||||
Task Record(IDirectStreamProvider directStreamProvider, MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
|
||||
|
||||
string GetOutputPath(MediaSourceInfo mediaSource, string targetFile);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
}
|
||||
else
|
||||
{
|
||||
name += " " + info.StartDate.ToString("yyyy-MM-dd") + " " + info.Id;
|
||||
name += " " + info.StartDate.ToString("yyyy-MM-dd");
|
||||
}
|
||||
|
||||
return name;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
@@ -167,10 +167,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||
{
|
||||
var programEntry = programDict[schedule.programID];
|
||||
|
||||
var data = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
|
||||
data = data.OrderByDescending(GetSizeOrder).ToList();
|
||||
var allImages = (images[imageIndex].data ?? new List<ScheduleDirect.ImageData>()).OrderByDescending(GetSizeOrder).ToList();
|
||||
var imagesWithText = allImages.Where(i => string.Equals(i.text, "yes", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
|
||||
programEntry.primaryImage = GetProgramImage(ApiUrl, imagesWithText, "Logo", true, 600) ??
|
||||
GetProgramImage(ApiUrl, allImages, "Logo", true, 600);
|
||||
|
||||
programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true, 600);
|
||||
//programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false);
|
||||
//programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
|
||||
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
|
||||
|
||||
@@ -149,7 +149,11 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
IncludeItemTypes = new string[] { typeof(Series).Name },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb }
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||
DtoOptions = new DtoOptions
|
||||
{
|
||||
Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
|
||||
}
|
||||
|
||||
}).FirstOrDefault();
|
||||
|
||||
@@ -191,7 +195,11 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
ExternalSeriesId = programSeriesId,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary }
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
DtoOptions = new DtoOptions
|
||||
{
|
||||
Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
|
||||
}
|
||||
|
||||
}).FirstOrDefault();
|
||||
|
||||
@@ -239,7 +247,11 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
IncludeItemTypes = new string[] { typeof(Series).Name },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb }
|
||||
ImageTypes = new ImageType[] { ImageType.Thumb },
|
||||
DtoOptions = new DtoOptions
|
||||
{
|
||||
Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
|
||||
}
|
||||
|
||||
}).FirstOrDefault();
|
||||
|
||||
@@ -281,14 +293,22 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
IncludeItemTypes = new string[] { typeof(Series).Name },
|
||||
Name = seriesName,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary }
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
DtoOptions = new DtoOptions
|
||||
{
|
||||
Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
|
||||
}
|
||||
|
||||
}).FirstOrDefault() ?? _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
ExternalSeriesId = programSeriesId,
|
||||
Limit = 1,
|
||||
ImageTypes = new ImageType[] { ImageType.Primary }
|
||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||
DtoOptions = new DtoOptions
|
||||
{
|
||||
Fields = new List<MediaBrowser.Model.Querying.ItemFields>()
|
||||
}
|
||||
|
||||
}).FirstOrDefault();
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Common.Security;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
@@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken)
|
||||
public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, DtoOptions dtoOptions, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
|
||||
|
||||
@@ -192,7 +192,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
IsFavorite = query.IsFavorite,
|
||||
IsLiked = query.IsLiked,
|
||||
StartIndex = query.StartIndex,
|
||||
Limit = query.Limit
|
||||
Limit = query.Limit,
|
||||
DtoOptions = dtoOptions
|
||||
};
|
||||
|
||||
internalQuery.OrderBy.AddRange(query.SortBy.Select(i => new Tuple<string, SortOrder>(i, query.SortOrder ?? SortOrder.Ascending)));
|
||||
@@ -249,7 +250,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
Id = id
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}, new DtoOptions(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result.Items.FirstOrDefault();
|
||||
}
|
||||
@@ -477,7 +478,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (!(service is EmbyTV.EmbyTV))
|
||||
{
|
||||
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
|
||||
// We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
|
||||
//mediaSource.SupportsDirectPlay = false;
|
||||
mediaSource.SupportsDirectStream = false;
|
||||
mediaSource.SupportsTranscoding = true;
|
||||
foreach (var stream in mediaSource.MediaStreams)
|
||||
@@ -864,13 +866,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
return item.Id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private string GetExternalSeriesIdLegacy(BaseItem item)
|
||||
{
|
||||
return item.GetProviderId("ProviderExternalSeriesId");
|
||||
}
|
||||
|
||||
public async Task<BaseItemDto> GetProgram(string id, CancellationToken cancellationToken, User user = null)
|
||||
{
|
||||
var program = GetInternalProgram(id);
|
||||
@@ -881,11 +876,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var externalSeriesId = program.ExternalSeriesId;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(externalSeriesId))
|
||||
{
|
||||
externalSeriesId = GetExternalSeriesIdLegacy(program);
|
||||
}
|
||||
|
||||
list.Add(new Tuple<BaseItemDto, string, string, string>(dto, program.ServiceName, GetItemExternalId(program), externalSeriesId));
|
||||
|
||||
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
|
||||
@@ -905,6 +895,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
query.SortBy = new[] { ItemSortBy.StartDate };
|
||||
}
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
var internalQuery = new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
|
||||
@@ -964,8 +956,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var queryResult = _libraryManager.QueryItems(internalQuery);
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
var returnArray = (await _dtoService.GetBaseItemDtos(queryResult.Items, options, user).ConfigureAwait(false)).ToArray();
|
||||
|
||||
var result = new QueryResult<BaseItemDto>
|
||||
@@ -1044,12 +1034,12 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
public async Task<QueryResult<BaseItemDto>> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
RemoveFields(options);
|
||||
|
||||
var internalResult = await GetRecommendedProgramsInternal(query, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var user = _userManager.GetUserById(query.UserId);
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
|
||||
|
||||
var result = new QueryResult<BaseItemDto>
|
||||
@@ -1332,7 +1322,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
|
||||
IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
|
||||
ChannelIds = new string[] { currentChannel.Id.ToString("N") }
|
||||
ChannelIds = new string[] { currentChannel.Id.ToString("N") },
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).Cast<LiveTvProgram>().ToDictionary(i => i.Id);
|
||||
|
||||
@@ -1435,7 +1426,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
var list = _itemRepo.GetItemIdsList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = validTypes
|
||||
IncludeItemTypes = validTypes,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
|
||||
}).ToList();
|
||||
|
||||
@@ -1662,6 +1654,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
includeItemTypes.Add(typeof(Series).Name);
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true,
|
||||
@@ -1671,11 +1665,10 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
SortOrder = SortOrder.Descending,
|
||||
EnableTotalRecordCount = query.EnableTotalRecordCount,
|
||||
IncludeItemTypes = includeItemTypes.ToArray(),
|
||||
ExcludeItemTypes = excludeItemTypes.ToArray()
|
||||
ExcludeItemTypes = excludeItemTypes.ToArray(),
|
||||
DtoOptions = options
|
||||
});
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
|
||||
|
||||
return new QueryResult<BaseItemDto>
|
||||
@@ -1685,7 +1678,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
|
||||
public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
|
||||
if (user != null && !IsLiveTvEnabled(user))
|
||||
@@ -1695,14 +1688,15 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
if (_services.Count == 1 && !(query.IsInProgress ?? false) && (!query.IsLibraryItem.HasValue || query.IsLibraryItem.Value))
|
||||
{
|
||||
return GetEmbyRecordings(query, new DtoOptions(), user);
|
||||
return GetEmbyRecordings(query, options, user);
|
||||
}
|
||||
|
||||
await RefreshRecordings(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var internalQuery = new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }
|
||||
IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name },
|
||||
DtoOptions = options
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(query.ChannelId))
|
||||
@@ -1871,11 +1865,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
|
||||
var externalSeriesId = program.ExternalSeriesId;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(externalSeriesId))
|
||||
{
|
||||
externalSeriesId = GetExternalSeriesIdLegacy(program);
|
||||
}
|
||||
|
||||
programTuples.Add(new Tuple<BaseItemDto, string, string, string>(dto, serviceName, GetItemExternalId(program), externalSeriesId));
|
||||
}
|
||||
|
||||
@@ -1952,10 +1941,10 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
|
||||
|
||||
var internalResult = await GetInternalRecordings(query, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
RemoveFields(options);
|
||||
|
||||
var internalResult = await GetInternalRecordings(query, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
|
||||
|
||||
return new QueryResult<BaseItemDto>
|
||||
@@ -2298,7 +2287,8 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
MinEndDate = now,
|
||||
Limit = channelIds.Length,
|
||||
SortBy = new[] { "StartDate" },
|
||||
TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") }
|
||||
TopParentIds = new[] { GetInternalLiveTvFolder(CancellationToken.None).Result.Id.ToString("N") },
|
||||
DtoOptions = options
|
||||
|
||||
}).ToList() : new List<BaseItem>();
|
||||
|
||||
@@ -2600,7 +2590,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
{
|
||||
UserId = query.UserId
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}, new DtoOptions(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var recordings = recordingResult.Items.OfType<ILiveTvRecording>().ToList();
|
||||
|
||||
|
||||
@@ -421,7 +421,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
SupportsDirectStream = true,
|
||||
SupportsTranscoding = true,
|
||||
IsInfiniteStream = true,
|
||||
IgnoreDts = true
|
||||
IgnoreDts = true,
|
||||
//IgnoreIndex = true,
|
||||
ReadAtNativeFramerate = true
|
||||
};
|
||||
|
||||
mediaSource.InferTotalBitrate();
|
||||
@@ -505,12 +507,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
if (hdhomerunChannel != null && hdhomerunChannel.IsLegacyTuner)
|
||||
{
|
||||
return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
|
||||
return new HdHomerunUdpStream(mediaSource, streamId, new LegacyHdHomerunChannelCommands(hdhomerunChannel.Url), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
|
||||
}
|
||||
|
||||
// The UDP method is not working reliably on OSX, and on BSD it hasn't been tested yet
|
||||
var enableHttpStream = _environment.OperatingSystem == OperatingSystem.OSX ||
|
||||
_environment.OperatingSystem == OperatingSystem.BSD;
|
||||
var enableHttpStream = _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX
|
||||
|| _environment.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.BSD;
|
||||
enableHttpStream = true;
|
||||
if (enableHttpStream)
|
||||
{
|
||||
@@ -519,17 +521,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
var httpUrl = GetApiUrl(info, true) + "/auto/v" + hdhrId;
|
||||
|
||||
// If raw was used, the tuner doesn't support params
|
||||
if (!string.IsNullOrWhiteSpace(profile)
|
||||
&& !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.IsNullOrWhiteSpace(profile) && !string.Equals(profile, "native", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
httpUrl += "?transcode=" + profile;
|
||||
}
|
||||
mediaSource.Path = httpUrl;
|
||||
|
||||
return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost);
|
||||
return new HdHomerunHttpStream(mediaSource, streamId, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _environment);
|
||||
}
|
||||
|
||||
return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager);
|
||||
return new HdHomerunUdpStream(mediaSource, streamId, new HdHomerunChannelCommands(hdhomerunChannel.Number, profile), modelInfo.TunerCount, _fileSystem, _httpClient, Logger, Config.ApplicationPaths, _appHost, _socketFactory, _networkManager, _environment);
|
||||
}
|
||||
|
||||
public async Task Validate(TunerHostInfo info)
|
||||
@@ -596,10 +597,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
// Need a way to set the Receive timeout on the socket otherwise this might never timeout?
|
||||
try
|
||||
{
|
||||
await udpClient.SendAsync(discBytes, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
|
||||
await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IpEndPointInfo(new IpAddressInfo("255.255.255.255", IpAddressFamily.InterNetwork), 65001), cancellationToken);
|
||||
var receiveBuffer = new byte[8192];
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var response = await udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
var response = await udpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
var deviceIp = response.RemoteEndPoint.IpAddress.Address;
|
||||
|
||||
// check to make sure we have enough bytes received to be a valid message and make sure the 2nd byte is the discover reply byte
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -10,6 +11,7 @@ using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
@@ -17,24 +19,22 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly MulticastStream _multicastStream;
|
||||
|
||||
public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost)
|
||||
: base(mediaSource)
|
||||
private readonly string _tempFilePath;
|
||||
|
||||
public HdHomerunHttpStream(MediaSourceInfo mediaSource, string originalStreamId, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, IEnvironmentInfo environment)
|
||||
: base(mediaSource, environment, fileSystem)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
_appHost = appHost;
|
||||
OriginalStreamId = originalStreamId;
|
||||
_multicastStream = new MulticastStream(_logger);
|
||||
|
||||
_tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts");
|
||||
}
|
||||
|
||||
protected override async Task OpenInternal(CancellationToken openCancellationToken)
|
||||
@@ -57,9 +57,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||
OpenedMediaSource.Protocol = MediaProtocol.Http;
|
||||
OpenedMediaSource.SupportsDirectPlay = false;
|
||||
OpenedMediaSource.SupportsDirectStream = true;
|
||||
OpenedMediaSource.SupportsTranscoding = true;
|
||||
//OpenedMediaSource.SupportsDirectPlay = false;
|
||||
//OpenedMediaSource.SupportsDirectStream = true;
|
||||
//OpenedMediaSource.SupportsTranscoding = true;
|
||||
|
||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
|
||||
@@ -74,9 +74,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
return _liveStreamTaskCompletionSource.Task;
|
||||
}
|
||||
|
||||
private async Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
private Task StartStreaming(string url, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
var isFirstAttempt = true;
|
||||
|
||||
@@ -101,13 +101,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
_logger.Info("Beginning multicastStream.CopyUntilCancelled");
|
||||
|
||||
Action onStarted = null;
|
||||
if (isFirstAttempt)
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
|
||||
using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
|
||||
{
|
||||
onStarted = () => openTaskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
ResolveAfterDelay(3000, openTaskCompletionSource);
|
||||
|
||||
await _multicastStream.CopyUntilCancelled(response.Content, onStarted, cancellationToken).ConfigureAwait(false);
|
||||
//await response.Content.CopyToAsync(fileStream, 81920, cancellationToken).ConfigureAwait(false);
|
||||
await AsyncStreamCopier.CopyStream(response.Content, fileStream, 81920, 4, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,13 +132,60 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
|
||||
_liveStreamTaskCompletionSource.TrySetResult(true);
|
||||
await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
private void ResolveAfterDelay(int delayMs, TaskCompletionSource<bool> openTaskCompletionSource)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(delayMs).ConfigureAwait(false);
|
||||
openTaskCompletionSource.TrySetResult(true);
|
||||
});
|
||||
}
|
||||
|
||||
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
return _multicastStream.CopyToAsync(stream);
|
||||
return CopyFileTo(_tempFilePath, false, stream, cancellationToken);
|
||||
}
|
||||
|
||||
protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken)
|
||||
{
|
||||
var eofCount = 0;
|
||||
|
||||
long startPosition = -25000;
|
||||
if (startPosition < 0)
|
||||
{
|
||||
var length = FileSystem.GetFileInfo(path).Length;
|
||||
startPosition = Math.Max(length - startPosition, 0);
|
||||
}
|
||||
|
||||
using (var inputStream = GetInputStream(path, startPosition, true))
|
||||
{
|
||||
if (startPosition > 0)
|
||||
{
|
||||
inputStream.Position = startPosition;
|
||||
}
|
||||
|
||||
while (eofCount < 20 || !allowEndOfFile)
|
||||
{
|
||||
var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//var position = fs.Position;
|
||||
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
eofCount++;
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
eofCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
public class HdHomerunChannelCommands : IHdHomerunChannelCommands
|
||||
{
|
||||
private string _channel;
|
||||
private string _profile;
|
||||
|
||||
public HdHomerunChannelCommands(string channel)
|
||||
public HdHomerunChannelCommands(string channel, string profile)
|
||||
{
|
||||
_channel = channel;
|
||||
_profile = profile;
|
||||
}
|
||||
|
||||
public IEnumerable<Tuple<string, string>> GetCommands()
|
||||
@@ -57,7 +59,16 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
var commands = new List<Tuple<string, string>>();
|
||||
|
||||
if (!String.IsNullOrEmpty(_channel))
|
||||
commands.Add(Tuple.Create("vchannel", _channel));
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(_profile) && !string.Equals(_profile, "native", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
commands.Add(Tuple.Create("vchannel", String.Format("{0} transcode={1}", _channel, _profile)));
|
||||
}
|
||||
else
|
||||
{
|
||||
commands.Add(Tuple.Create("vchannel", _channel));
|
||||
}
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
@@ -103,8 +114,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
var ipEndPoint = new IpEndPointInfo(remoteIp, HdHomeRunPort);
|
||||
|
||||
var lockkeyMsg = CreateGetMessage(tuner, "lockkey");
|
||||
await socket.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken);
|
||||
var response = await socket.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
await socket.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken);
|
||||
|
||||
var receiveBuffer = new byte[8192];
|
||||
var response = await socket.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
string returnVal;
|
||||
ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal);
|
||||
|
||||
@@ -117,6 +131,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
|
||||
{
|
||||
var receiveBuffer = new byte[8192];
|
||||
|
||||
if (!_lockkey.HasValue)
|
||||
{
|
||||
var rand = new Random();
|
||||
@@ -133,8 +149,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
_activeTuner = i;
|
||||
var lockKeyString = String.Format("{0:d}", _lockkey.Value);
|
||||
var lockkeyMsg = CreateSetMessage(i, "lockkey", lockKeyString, null);
|
||||
await tcpClient.SendAsync(lockkeyMsg, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
await tcpClient.SendToAsync(lockkeyMsg, 0, lockkeyMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
string returnVal;
|
||||
// parse response to make sure it worked
|
||||
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||
@@ -144,8 +160,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
foreach(Tuple<string,string> command in commandList)
|
||||
{
|
||||
var channelMsg = CreateSetMessage(i, command.Item1, command.Item2, _lockkey.Value);
|
||||
await tcpClient.SendAsync(channelMsg, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
// parse response to make sure it worked
|
||||
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||
{
|
||||
@@ -158,8 +174,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
var targetValue = String.Format("rtp://{0}:{1}", localIp, localPort);
|
||||
var targetMsg = CreateSetMessage(i, "target", targetValue, _lockkey.Value);
|
||||
|
||||
await tcpClient.SendAsync(targetMsg, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
await tcpClient.SendToAsync(targetMsg, 0, targetMsg.Length, ipEndPoint, cancellationToken).ConfigureAwait(false);
|
||||
response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
// parse response to make sure it worked
|
||||
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||
{
|
||||
@@ -180,11 +196,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
using (var tcpClient = _socketFactory.CreateTcpSocket(_remoteIp, HdHomeRunPort))
|
||||
{
|
||||
var commandList = commands.GetCommands();
|
||||
var receiveBuffer = new byte[8192];
|
||||
|
||||
foreach (Tuple<string, string> command in commandList)
|
||||
{
|
||||
var channelMsg = CreateSetMessage(_activeTuner, command.Item1, command.Item2, _lockkey.Value);
|
||||
await tcpClient.SendAsync(channelMsg, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
|
||||
var response = await tcpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
await tcpClient.SendToAsync(channelMsg, 0, channelMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), cancellationToken).ConfigureAwait(false);
|
||||
var response = await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
// parse response to make sure it worked
|
||||
string returnVal;
|
||||
if (!ParseReturnMessage(response.Buffer, response.ReceivedBytes, out returnVal))
|
||||
@@ -209,12 +227,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
private async Task ReleaseLockkey(ISocket tcpClient)
|
||||
{
|
||||
var releaseTarget = CreateSetMessage(_activeTuner, "target", "none", _lockkey);
|
||||
await tcpClient.SendAsync(releaseTarget, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
|
||||
await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
await tcpClient.SendToAsync(releaseTarget, 0, releaseTarget.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var receiveBuffer = new byte[8192];
|
||||
|
||||
await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
|
||||
var releaseKeyMsg = CreateSetMessage(_activeTuner, "lockkey", "none", _lockkey);
|
||||
_lockkey = null;
|
||||
await tcpClient.SendAsync(releaseKeyMsg, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
|
||||
await tcpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
await tcpClient.SendToAsync(releaseKeyMsg, 0, releaseKeyMsg.Length, new IpEndPointInfo(_remoteIp, HdHomeRunPort), CancellationToken.None).ConfigureAwait(false);
|
||||
await tcpClient.ReceiveAsync(receiveBuffer, 0, receiveBuffer.Length, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static byte[] CreateGetMessage(int tuner, string name)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -14,39 +15,35 @@ using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Net;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
{
|
||||
public class HdHomerunUdpStream : LiveStream, IDirectStreamProvider
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ISocketFactory _socketFactory;
|
||||
|
||||
private readonly CancellationTokenSource _liveStreamCancellationTokenSource = new CancellationTokenSource();
|
||||
private readonly TaskCompletionSource<bool> _liveStreamTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly MulticastStream _multicastStream;
|
||||
private readonly IHdHomerunChannelCommands _channelCommands;
|
||||
private readonly int _numTuners;
|
||||
private readonly INetworkManager _networkManager;
|
||||
|
||||
public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager)
|
||||
: base(mediaSource)
|
||||
private readonly string _tempFilePath;
|
||||
|
||||
public HdHomerunUdpStream(MediaSourceInfo mediaSource, string originalStreamId, IHdHomerunChannelCommands channelCommands, int numTuners, IFileSystem fileSystem, IHttpClient httpClient, ILogger logger, IServerApplicationPaths appPaths, IServerApplicationHost appHost, ISocketFactory socketFactory, INetworkManager networkManager, IEnvironmentInfo environment)
|
||||
: base(mediaSource, environment, fileSystem)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_httpClient = httpClient;
|
||||
_logger = logger;
|
||||
_appPaths = appPaths;
|
||||
_appHost = appHost;
|
||||
_socketFactory = socketFactory;
|
||||
_networkManager = networkManager;
|
||||
OriginalStreamId = originalStreamId;
|
||||
_multicastStream = new MulticastStream(_logger);
|
||||
_channelCommands = channelCommands;
|
||||
_numTuners = numTuners;
|
||||
_tempFilePath = Path.Combine(appPaths.TranscodingTempPath, UniqueId + ".ts");
|
||||
}
|
||||
|
||||
protected override async Task OpenInternal(CancellationToken openCancellationToken)
|
||||
@@ -70,9 +67,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
OpenedMediaSource.Path = _appHost.GetLocalApiUrl("127.0.0.1") + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
|
||||
OpenedMediaSource.Protocol = MediaProtocol.Http;
|
||||
OpenedMediaSource.SupportsDirectPlay = false;
|
||||
OpenedMediaSource.SupportsDirectStream = true;
|
||||
OpenedMediaSource.SupportsTranscoding = true;
|
||||
//OpenedMediaSource.SupportsDirectPlay = false;
|
||||
//OpenedMediaSource.SupportsDirectStream = true;
|
||||
//OpenedMediaSource.SupportsTranscoding = true;
|
||||
|
||||
await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
|
||||
@@ -87,9 +84,9 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
return _liveStreamTaskCompletionSource.Task;
|
||||
}
|
||||
|
||||
private async Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
private Task StartStreaming(string remoteIp, int localPort, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
return Task.Run(async () =>
|
||||
{
|
||||
var isFirstAttempt = true;
|
||||
using (var udpClient = _socketFactory.CreateUdpSocket(localPort))
|
||||
@@ -124,13 +121,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
|
||||
if (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Action onStarted = null;
|
||||
if (isFirstAttempt)
|
||||
FileSystem.CreateDirectory(FileSystem.GetDirectoryName(_tempFilePath));
|
||||
using (var fileStream = FileSystem.GetFileStream(_tempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.Asynchronous))
|
||||
{
|
||||
onStarted = () => openTaskCompletionSource.TrySetResult(true);
|
||||
await CopyTo(udpClient, fileStream, openTaskCompletionSource, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await _multicastStream.CopyUntilCancelled(new UdpClientStream(udpClient), onStarted, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
@@ -159,135 +154,109 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
}
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
await DeleteTempFile(_tempFilePath).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
openTaskCompletionSource.TrySetResult(true);
|
||||
});
|
||||
}
|
||||
|
||||
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
return _multicastStream.CopyToAsync(stream);
|
||||
return CopyFileTo(_tempFilePath, false, stream, cancellationToken);
|
||||
}
|
||||
|
||||
protected async Task CopyFileTo(string path, bool allowEndOfFile, Stream outputStream, CancellationToken cancellationToken)
|
||||
{
|
||||
var eofCount = 0;
|
||||
|
||||
long startPosition = -25000;
|
||||
if (startPosition < 0)
|
||||
{
|
||||
var length = FileSystem.GetFileInfo(path).Length;
|
||||
startPosition = Math.Max(length - startPosition, 0);
|
||||
}
|
||||
|
||||
using (var inputStream = GetInputStream(path, startPosition, true))
|
||||
{
|
||||
if (startPosition > 0)
|
||||
{
|
||||
inputStream.Position = startPosition;
|
||||
}
|
||||
|
||||
while (eofCount < 20 || !allowEndOfFile)
|
||||
{
|
||||
var bytesRead = await AsyncStreamCopier.CopyStream(inputStream, outputStream, 81920, 4, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
//var position = fs.Position;
|
||||
//_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
eofCount++;
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
eofCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This handles the ReadAsync function only of a Stream object
|
||||
// This is used to wrap a UDP socket into a stream for MulticastStream which only uses ReadAsync
|
||||
public class UdpClientStream : Stream
|
||||
{
|
||||
private static int RtpHeaderBytes = 12;
|
||||
private static int PacketSize = 1316;
|
||||
private readonly ISocket _udpClient;
|
||||
bool disposed;
|
||||
|
||||
public UdpClientStream(ISocket udpClient) : base()
|
||||
private Task CopyTo(ISocket udpClient, Stream outputStream, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
{
|
||||
_udpClient = udpClient;
|
||||
return CopyStream(_socketFactory.CreateNetworkStream(udpClient, false), outputStream, 81920, 4, openTaskCompletionSource, cancellationToken);
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
private Task CopyStream(Stream source, Stream target, int bufferSize, int bufferCount, TaskCompletionSource<bool> openTaskCompletionSource, CancellationToken cancellationToken)
|
||||
{
|
||||
if (buffer == null)
|
||||
throw new ArgumentNullException("buffer");
|
||||
var copier = new AsyncStreamCopier(source, target, 0, cancellationToken, false, bufferSize, bufferCount);
|
||||
copier.IndividualReadOffset = RtpHeaderBytes;
|
||||
|
||||
if (offset + count < 0)
|
||||
throw new ArgumentOutOfRangeException("offset + count must not be negative", "offset+count");
|
||||
var taskCompletion = new TaskCompletionSource<long>();
|
||||
|
||||
if (offset + count > buffer.Length)
|
||||
throw new ArgumentException("offset + count must not be greater than the length of buffer", "offset+count");
|
||||
copier.TaskCompletionSource = taskCompletion;
|
||||
|
||||
if (disposed)
|
||||
throw new ObjectDisposedException(typeof(UdpClientStream).ToString());
|
||||
var result = copier.BeginCopy(StreamCopyCallback, copier);
|
||||
|
||||
// This will always receive a 1328 packet size (PacketSize + RtpHeaderSize)
|
||||
// The RTP header will be stripped so see how many reads we need to make to fill the buffer.
|
||||
int numReads = count / PacketSize;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
for (int i = 0; i < numReads; ++i)
|
||||
if (openTaskCompletionSource != null)
|
||||
{
|
||||
var data = await _udpClient.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
|
||||
|
||||
// remove rtp header
|
||||
Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, buffer, offset, bytesRead);
|
||||
offset += bytesRead;
|
||||
totalBytesRead += bytesRead;
|
||||
Resolve(openTaskCompletionSource);
|
||||
openTaskCompletionSource = null;
|
||||
}
|
||||
return totalBytesRead;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
public override bool CanRead
|
||||
{
|
||||
get
|
||||
if (result.CompletedSynchronously)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
StreamCopyCallback(result);
|
||||
}
|
||||
|
||||
cancellationToken.Register(() => taskCompletion.TrySetCanceled());
|
||||
|
||||
return taskCompletion.Task;
|
||||
}
|
||||
|
||||
private void StreamCopyCallback(IAsyncResult result)
|
||||
{
|
||||
var copier = (AsyncStreamCopier)result.AsyncState;
|
||||
var taskCompletion = copier.TaskCompletionSource;
|
||||
|
||||
try
|
||||
{
|
||||
copier.EndCopy(result);
|
||||
taskCompletion.TrySetResult(0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskCompletion.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanSeek
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanWrite
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -19,6 +19,7 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.System;
|
||||
|
||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
@@ -27,13 +28,15 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IEnvironmentInfo _environment;
|
||||
|
||||
public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost)
|
||||
public M3UTunerHost(IServerConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient, IServerApplicationHost appHost, IEnvironmentInfo environment)
|
||||
: base(config, logger, jsonSerializer, mediaEncoder)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
_httpClient = httpClient;
|
||||
_appHost = appHost;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public override string Type
|
||||
@@ -73,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
var sources = await GetChannelStreamMediaSources(info, channelId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var liveStream = new LiveStream(sources.First());
|
||||
var liveStream = new LiveStream(sources.First(), _environment, _fileSystem);
|
||||
return liveStream;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.IO;
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
{
|
||||
private readonly ConcurrentDictionary<Guid,QueueStream> _outputStreams = new ConcurrentDictionary<Guid, QueueStream>();
|
||||
private const int BufferSize = 81920;
|
||||
private CancellationToken _cancellationToken;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public MulticastStream(ILogger logger)
|
||||
@@ -25,8 +24,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
public async Task CopyUntilCancelled(Stream source, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
_cancellationToken = cancellationToken;
|
||||
|
||||
byte[] buffer = new byte[BufferSize];
|
||||
|
||||
if (source == null)
|
||||
@@ -72,59 +69,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
}
|
||||
}
|
||||
|
||||
private static int RtpHeaderBytes = 12;
|
||||
public async Task CopyUntilCancelled(ISocket udpClient, Action onStarted, CancellationToken cancellationToken)
|
||||
{
|
||||
_cancellationToken = cancellationToken;
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var receiveToken = cancellationToken;
|
||||
|
||||
// On the first connection attempt, put a timeout to avoid being stuck indefinitely in the event of failure
|
||||
if (onStarted != null)
|
||||
{
|
||||
receiveToken = CancellationTokenSource.CreateLinkedTokenSource(new CancellationTokenSource(5000).Token, cancellationToken).Token;
|
||||
}
|
||||
|
||||
var data = await udpClient.ReceiveAsync(receiveToken).ConfigureAwait(false);
|
||||
var bytesRead = data.ReceivedBytes - RtpHeaderBytes;
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
var allStreams = _outputStreams.ToList();
|
||||
|
||||
if (allStreams.Count == 1)
|
||||
{
|
||||
await allStreams[0].Value.WriteAsync(data.Buffer, 0, bytesRead).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
byte[] copy = new byte[bytesRead];
|
||||
Buffer.BlockCopy(data.Buffer, RtpHeaderBytes, copy, 0, bytesRead);
|
||||
|
||||
foreach (var stream in allStreams)
|
||||
{
|
||||
stream.Value.Queue(copy, 0, copy.Length);
|
||||
}
|
||||
}
|
||||
|
||||
if (onStarted != null)
|
||||
{
|
||||
var onStartedCopy = onStarted;
|
||||
onStarted = null;
|
||||
Task.Run(onStartedCopy);
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task CopyToAsync(Stream stream)
|
||||
public Task CopyToAsync(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new QueueStream(stream, _logger)
|
||||
{
|
||||
@@ -133,7 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
_outputStreams.TryAdd(result.Id, result);
|
||||
|
||||
result.Start(_cancellationToken);
|
||||
result.Start(cancellationToken);
|
||||
|
||||
return result.TaskCompletion.Task;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Migrations
|
||||
_taskManager = taskManager;
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
public Task Run()
|
||||
{
|
||||
var name = "GuideRefresh3";
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace Emby.Server.Implementations.Migrations
|
||||
_config.Configuration.Migrations = list.ToArray();
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Migrations
|
||||
_taskManager = taskManager;
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
public Task Run()
|
||||
{
|
||||
var name = "LibraryScan6";
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace Emby.Server.Implementations.Migrations
|
||||
_config.Configuration.Migrations = list.ToArray();
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
|
||||
namespace Emby.Server.Implementations.Migrations
|
||||
{
|
||||
public class UpdateLevelMigration : IVersionMigration
|
||||
{
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly string _releaseAssetFilename;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UpdateLevelMigration(IServerConfigurationManager config, IServerApplicationHost appHost, IHttpClient httpClient, IJsonSerializer jsonSerializer, string releaseAssetFilename, ILogger logger)
|
||||
{
|
||||
_config = config;
|
||||
_appHost = appHost;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_releaseAssetFilename = releaseAssetFilename;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Run()
|
||||
{
|
||||
var lastVersion = _config.Configuration.LastVersion;
|
||||
var currentVersion = _appHost.ApplicationVersion;
|
||||
|
||||
if (string.Equals(lastVersion, currentVersion.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var updateLevel = _config.Configuration.SystemUpdateLevel;
|
||||
|
||||
await CheckVersion(currentVersion, updateLevel, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error in update migration", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckVersion(Version currentVersion, PackageVersionClass currentUpdateLevel, CancellationToken cancellationToken)
|
||||
{
|
||||
var releases = await new GithubUpdater(_httpClient, _jsonSerializer)
|
||||
.GetLatestReleases("MediaBrowser", "Emby", _releaseAssetFilename, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var newUpdateLevel = GetNewUpdateLevel(currentVersion, currentUpdateLevel, releases);
|
||||
|
||||
if (newUpdateLevel != currentUpdateLevel)
|
||||
{
|
||||
_config.Configuration.SystemUpdateLevel = newUpdateLevel;
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
private PackageVersionClass GetNewUpdateLevel(Version currentVersion, PackageVersionClass currentUpdateLevel, List<GithubUpdater.RootObject> releases)
|
||||
{
|
||||
var newUpdateLevel = currentUpdateLevel;
|
||||
|
||||
// If the current version is later than current stable, set the update level to beta
|
||||
if (releases.Count >= 1)
|
||||
{
|
||||
var release = releases[0];
|
||||
var version = ParseVersion(release.tag_name);
|
||||
if (version != null)
|
||||
{
|
||||
if (currentVersion > version)
|
||||
{
|
||||
newUpdateLevel = PackageVersionClass.Beta;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PackageVersionClass.Release;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the current version is later than current beta, set the update level to dev
|
||||
if (releases.Count >= 2)
|
||||
{
|
||||
var release = releases[1];
|
||||
var version = ParseVersion(release.tag_name);
|
||||
if (version != null)
|
||||
{
|
||||
if (currentVersion > version)
|
||||
{
|
||||
newUpdateLevel = PackageVersionClass.Dev;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PackageVersionClass.Beta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newUpdateLevel;
|
||||
}
|
||||
|
||||
private Version ParseVersion(string versionString)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(versionString))
|
||||
{
|
||||
var parts = versionString.Split('.');
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
versionString += ".0";
|
||||
}
|
||||
}
|
||||
|
||||
Version version;
|
||||
Version.TryParse(versionString, out version);
|
||||
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,15 @@ namespace Emby.Server.Implementations.Photos
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var photoAlbum = (PhotoAlbum)item;
|
||||
var items = GetFinalItems(photoAlbum.Children.ToList());
|
||||
|
||||
return Task.FromResult(items);
|
||||
return items;
|
||||
}
|
||||
|
||||
protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
protected string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
{
|
||||
return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var playlist = (Playlist)item;
|
||||
|
||||
@@ -67,7 +68,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
.DistinctBy(i => i.Id)
|
||||
.ToList();
|
||||
|
||||
return Task.FromResult(GetFinalItems(items));
|
||||
return GetFinalItems(items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
@@ -89,11 +90,12 @@ namespace Emby.Server.Implementations.Playlists
|
||||
SortBy = new[] { ItemSortBy.Random },
|
||||
Limit = 4,
|
||||
Recursive = true,
|
||||
ImageTypes = new[] { ImageType.Primary }
|
||||
ImageTypes = new[] { ImageType.Primary },
|
||||
DtoOptions = new DtoOptions(false)
|
||||
|
||||
}).ToList();
|
||||
|
||||
return Task.FromResult(GetFinalItems(items));
|
||||
return GetFinalItems(items);
|
||||
}
|
||||
|
||||
//protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
@@ -111,7 +113,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
protected override Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var items = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
@@ -120,11 +122,12 @@ namespace Emby.Server.Implementations.Playlists
|
||||
SortBy = new[] { ItemSortBy.Random },
|
||||
Limit = 4,
|
||||
Recursive = true,
|
||||
ImageTypes = new[] { ImageType.Primary }
|
||||
ImageTypes = new[] { ImageType.Primary },
|
||||
DtoOptions = new DtoOptions(false)
|
||||
|
||||
}).ToList();
|
||||
|
||||
return Task.FromResult(GetFinalItems(items));
|
||||
return GetFinalItems(items);
|
||||
}
|
||||
|
||||
//protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
|
||||
@@ -12,7 +12,8 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
@@ -135,7 +136,10 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
if (options.ItemIdList.Count > 0)
|
||||
{
|
||||
await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user);
|
||||
await AddToPlaylistInternal(playlist.Id.ToString("N"), options.ItemIdList, user, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = true
|
||||
});
|
||||
}
|
||||
|
||||
return new PlaylistCreationResult
|
||||
@@ -160,21 +164,24 @@ namespace Emby.Server.Implementations.Playlists
|
||||
return path;
|
||||
}
|
||||
|
||||
private Task<IEnumerable<BaseItem>> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user)
|
||||
private IEnumerable<BaseItem> GetPlaylistItems(IEnumerable<string> itemIds, string playlistMediaType, User user, DtoOptions options)
|
||||
{
|
||||
var items = itemIds.Select(i => _libraryManager.GetItemById(i)).Where(i => i != null);
|
||||
|
||||
return Playlist.GetPlaylistItems(playlistMediaType, items, user);
|
||||
return Playlist.GetPlaylistItems(playlistMediaType, items, user, options);
|
||||
}
|
||||
|
||||
public Task AddToPlaylist(string playlistId, IEnumerable<string> itemIds, string userId)
|
||||
{
|
||||
var user = string.IsNullOrWhiteSpace(userId) ? null : _userManager.GetUserById(userId);
|
||||
|
||||
return AddToPlaylistInternal(playlistId, itemIds, user);
|
||||
return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = true
|
||||
});
|
||||
}
|
||||
|
||||
private async Task AddToPlaylistInternal(string playlistId, IEnumerable<string> itemIds, User user)
|
||||
private async Task AddToPlaylistInternal(string playlistId, IEnumerable<string> itemIds, User user, DtoOptions options)
|
||||
{
|
||||
var playlist = _libraryManager.GetItemById(playlistId) as Playlist;
|
||||
|
||||
@@ -185,12 +192,17 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
var list = new List<LinkedChild>();
|
||||
|
||||
var items = (await GetPlaylistItems(itemIds, playlist.MediaType, user).ConfigureAwait(false))
|
||||
var items = (GetPlaylistItems(itemIds, playlist.MediaType, user, options))
|
||||
.Where(i => i.SupportsAddingToPlaylist)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.Path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add(LinkedChild.Create(item));
|
||||
}
|
||||
|
||||
@@ -271,7 +283,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
{
|
||||
var typeName = "PlaylistsFolder";
|
||||
|
||||
return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ??
|
||||
return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal)) ??
|
||||
_libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, typeName, StringComparison.Ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Entities;
|
||||
@@ -85,7 +85,9 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
MediaTypes = new[] { MediaType.Video },
|
||||
IsFolder = false,
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
|
||||
})
|
||||
.OfType<Video>()
|
||||
.ToList();
|
||||
|
||||
@@ -4,7 +4,7 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.Text;
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Services;
|
||||
using MediaBrowser.Model.Text;
|
||||
|
||||
@@ -12,43 +12,28 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
public static class ResponseHelper
|
||||
{
|
||||
public static Task WriteToResponse(IResponse httpRes, IRequest httpReq, object result)
|
||||
public static async Task WriteToResponse(IResponse response, IRequest request, object result, CancellationToken cancellationToken)
|
||||
{
|
||||
if (result == null)
|
||||
{
|
||||
if (httpRes.StatusCode == (int)HttpStatusCode.OK)
|
||||
if (response.StatusCode == (int)HttpStatusCode.OK)
|
||||
{
|
||||
httpRes.StatusCode = (int)HttpStatusCode.NoContent;
|
||||
response.StatusCode = (int)HttpStatusCode.NoContent;
|
||||
}
|
||||
|
||||
httpRes.SetContentLength(0);
|
||||
return Task.FromResult(true);
|
||||
response.SetContentLength(0);
|
||||
return;
|
||||
}
|
||||
|
||||
var httpResult = result as IHttpResult;
|
||||
if (httpResult != null)
|
||||
{
|
||||
httpResult.RequestContext = httpReq;
|
||||
httpReq.ResponseContentType = httpResult.ContentType ?? httpReq.ResponseContentType;
|
||||
return WriteToResponseInternal(httpRes, httpResult, httpReq);
|
||||
httpResult.RequestContext = request;
|
||||
request.ResponseContentType = httpResult.ContentType ?? request.ResponseContentType;
|
||||
}
|
||||
|
||||
return WriteToResponseInternal(httpRes, result, httpReq);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes to response.
|
||||
/// Response headers are customizable by implementing IHasHeaders an returning Dictionary of Http headers.
|
||||
/// </summary>
|
||||
/// <param name="response">The response.</param>
|
||||
/// <param name="result">Whether or not it was implicity handled by ServiceStack's built-in handlers.</param>
|
||||
/// <param name="request">The serialization context.</param>
|
||||
/// <returns></returns>
|
||||
private static async Task WriteToResponseInternal(IResponse response, object result, IRequest request)
|
||||
{
|
||||
var defaultContentType = request.ResponseContentType;
|
||||
|
||||
var httpResult = result as IHttpResult;
|
||||
if (httpResult != null)
|
||||
{
|
||||
if (httpResult.RequestContext == null)
|
||||
@@ -105,7 +90,7 @@ namespace Emby.Server.Implementations.Services
|
||||
var asyncStreamWriter = result as IAsyncStreamWriter;
|
||||
if (asyncStreamWriter != null)
|
||||
{
|
||||
await asyncStreamWriter.WriteToAsync(response.OutputStream, CancellationToken.None).ConfigureAwait(false);
|
||||
await asyncStreamWriter.WriteToAsync(response.OutputStream, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,7 +104,7 @@ namespace Emby.Server.Implementations.Services
|
||||
var fileWriter = result as FileWriter;
|
||||
if (fileWriter != null)
|
||||
{
|
||||
await fileWriter.WriteToAsync(response, CancellationToken.None).ConfigureAwait(false);
|
||||
await fileWriter.WriteToAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,7 +124,7 @@ namespace Emby.Server.Implementations.Services
|
||||
response.ContentType = "application/octet-stream";
|
||||
response.SetContentLength(bytes.Length);
|
||||
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,7 +133,7 @@ namespace Emby.Server.Implementations.Services
|
||||
{
|
||||
bytes = Encoding.UTF8.GetBytes(responseText);
|
||||
response.SetContentLength(bytes.Length);
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||
await response.OutputStream.WriteAsync(bytes, 0, bytes.Length, cancellationToken).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Emby.Server.Implementations.Services
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<object> Execute(HttpListenerHost appHost, object requestDto, IRequest req)
|
||||
public Task<object> Execute(HttpListenerHost appHost, object requestDto, IRequest req)
|
||||
{
|
||||
req.Dto = requestDto;
|
||||
var requestType = requestDto.GetType();
|
||||
@@ -209,9 +209,7 @@ namespace Emby.Server.Implementations.Services
|
||||
req.Dto = requestDto;
|
||||
|
||||
//Executes the service and returns the result
|
||||
var response = await ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName()).ConfigureAwait(false);
|
||||
|
||||
return response;
|
||||
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using MediaBrowser.Model.Logging;
|
||||
@@ -123,7 +124,7 @@ namespace Emby.Server.Implementations.Services
|
||||
// Set from SSHHF.GetHandlerForPathInfo()
|
||||
public string ResponseContentType { get; set; }
|
||||
|
||||
public async Task ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, ILogger logger, string operationName)
|
||||
public async Task ProcessRequestAsync(HttpListenerHost appHost, IRequest httpReq, IResponse httpRes, ILogger logger, string operationName, CancellationToken cancellationToken)
|
||||
{
|
||||
var restPath = GetRestPath(httpReq.Verb, httpReq.PathInfo);
|
||||
if (restPath == null)
|
||||
@@ -142,7 +143,8 @@ namespace Emby.Server.Implementations.Services
|
||||
|
||||
var rawResponse = await appHost.ServiceController.Execute(appHost, request, httpReq).ConfigureAwait(false);
|
||||
|
||||
var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false);
|
||||
//var response = await HandleResponseAsync(rawResponse).ConfigureAwait(false);
|
||||
var response = rawResponse;
|
||||
|
||||
// Apply response filters
|
||||
foreach (var responseFilter in appHost.ResponseFilters)
|
||||
@@ -150,7 +152,7 @@ namespace Emby.Server.Implementations.Services
|
||||
responseFilter(httpReq, httpRes, response);
|
||||
}
|
||||
|
||||
await ResponseHelper.WriteToResponse(httpRes, httpReq, response).ConfigureAwait(false);
|
||||
await ResponseHelper.WriteToResponse(httpRes, httpReq, response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static object CreateRequest(HttpListenerHost host, IRequest httpReq, RestPath restPath, ILogger logger)
|
||||
|
||||
@@ -66,19 +66,19 @@ namespace Emby.Server.Implementations.Session
|
||||
return SendMessage(name, new Dictionary<string, string>(), cancellationToken);
|
||||
}
|
||||
|
||||
private async Task SendMessage(string name,
|
||||
private Task SendMessage(string name,
|
||||
Dictionary<string, string> args,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var url = PostUrl + "/" + name + ToQueryString(args);
|
||||
|
||||
await _httpClient.Post(new HttpRequestOptions
|
||||
return _httpClient.Post(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = false
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
|
||||
@@ -159,8 +159,24 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
public Task SendMessage<T>(string name, T data, CancellationToken cancellationToken)
|
||||
{
|
||||
// Not supported or needed right now
|
||||
return Task.FromResult(true);
|
||||
var url = PostUrl + "/" + name;
|
||||
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = false
|
||||
};
|
||||
|
||||
options.RequestContent = _json.SerializeToString(data);
|
||||
options.RequestContentType = "application/json";
|
||||
|
||||
return _httpClient.Post(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
CancellationToken = cancellationToken,
|
||||
BufferContent = false
|
||||
});
|
||||
}
|
||||
|
||||
private string ToQueryString(Dictionary<string, string> nvc)
|
||||
|
||||
@@ -30,6 +30,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Threading;
|
||||
|
||||
namespace Emby.Server.Implementations.Session
|
||||
@@ -984,7 +985,7 @@ namespace Emby.Server.Implementations.Session
|
||||
var list = new List<BaseItem>();
|
||||
foreach (var itemId in command.ItemIds)
|
||||
{
|
||||
var subItems = await TranslateItemForPlayback(itemId, user).ConfigureAwait(false);
|
||||
var subItems = TranslateItemForPlayback(itemId, user);
|
||||
list.AddRange(subItems);
|
||||
}
|
||||
|
||||
@@ -1022,7 +1023,10 @@ namespace Emby.Server.Implementations.Session
|
||||
var series = episode.Series;
|
||||
if (series != null)
|
||||
{
|
||||
var episodes = series.GetEpisodes(user)
|
||||
var episodes = series.GetEpisodes(user, new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false
|
||||
})
|
||||
.Where(i => !i.IsVirtualItem)
|
||||
.SkipWhile(i => i.Id != episode.Id)
|
||||
.ToList();
|
||||
@@ -1048,7 +1052,7 @@ namespace Emby.Server.Implementations.Session
|
||||
await session.SessionController.SendPlayCommand(command, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<List<BaseItem>> TranslateItemForPlayback(string id, User user)
|
||||
private List<BaseItem> TranslateItemForPlayback(string id, User user)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
@@ -1065,7 +1069,15 @@ namespace Emby.Server.Implementations.Session
|
||||
var items = byName.GetTaggedItems(new InternalItemsQuery(user)
|
||||
{
|
||||
IsFolder = false,
|
||||
Recursive = true
|
||||
Recursive = true,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false,
|
||||
Fields = new List<ItemFields>
|
||||
{
|
||||
ItemFields.SortName
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return FilterToSingleMediaType(items)
|
||||
@@ -1077,12 +1089,20 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var folder = (Folder)item;
|
||||
|
||||
var itemsResult = await folder.GetItems(new InternalItemsQuery(user)
|
||||
var itemsResult = folder.GetItems(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true,
|
||||
IsFolder = false
|
||||
IsFolder = false,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
{
|
||||
EnableImages = false,
|
||||
Fields = new List<ItemFields>
|
||||
{
|
||||
ItemFields.SortName
|
||||
}
|
||||
}
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
return FilterToSingleMediaType(itemsResult.Items)
|
||||
.OrderBy(i => i.SortName)
|
||||
@@ -1111,7 +1131,7 @@ namespace Emby.Server.Implementations.Session
|
||||
return new List<BaseItem>();
|
||||
}
|
||||
|
||||
return _musicManager.GetInstantMixFromItem(item, user);
|
||||
return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false });
|
||||
}
|
||||
|
||||
public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken)
|
||||
|
||||
@@ -57,7 +57,6 @@ namespace Emby.Server.Implementations.Session
|
||||
_json = json;
|
||||
_httpServer = httpServer;
|
||||
_serverManager = serverManager;
|
||||
httpServer.WebSocketConnecting += _httpServer_WebSocketConnecting;
|
||||
serverManager.WebSocketConnected += _serverManager_WebSocketConnected;
|
||||
}
|
||||
|
||||
@@ -84,27 +83,6 @@ namespace Emby.Server.Implementations.Session
|
||||
}
|
||||
}
|
||||
|
||||
async void _httpServer_WebSocketConnecting(object sender, WebSocketConnectingEventArgs e)
|
||||
{
|
||||
//var token = e.QueryString["api_key"];
|
||||
//if (!string.IsNullOrWhiteSpace(token))
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// var session = await GetSession(e.QueryString, e.Endpoint).ConfigureAwait(false);
|
||||
|
||||
// if (session == null)
|
||||
// {
|
||||
// e.AllowConnection = false;
|
||||
// }
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// _logger.ErrorException("Error getting session info", ex);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
private Task<SessionInfo> GetSession(QueryParamCollection queryString, string remoteEndpoint)
|
||||
{
|
||||
if (queryString == null)
|
||||
@@ -123,7 +101,6 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_httpServer.WebSocketConnecting -= _httpServer_WebSocketConnecting;
|
||||
_serverManager.WebSocketConnected -= _serverManager_WebSocketConnected;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -58,7 +59,8 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
Recursive = true,
|
||||
GroupByPresentationUniqueKey = false
|
||||
GroupByPresentationUniqueKey = false,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).Cast<Series>().ToList();
|
||||
|
||||
@@ -188,7 +190,8 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
Recursive = true,
|
||||
GroupByPresentationUniqueKey = false
|
||||
GroupByPresentationUniqueKey = false,
|
||||
DtoOptions = new DtoOptions(true)
|
||||
|
||||
}).Cast<Series>().ToList();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
|
||||
namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
@@ -26,7 +27,7 @@ namespace Emby.Server.Implementations.TV
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public QueryResult<BaseItem> GetNextUp(NextUpQuery request)
|
||||
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, DtoOptions dtoOptions)
|
||||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
|
||||
@@ -68,19 +69,19 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
Fields = new List<ItemFields>
|
||||
{
|
||||
|
||||
ItemFields.PresentationUniqueKey
|
||||
}
|
||||
}
|
||||
|
||||
}).Cast<Series>().Select(GetUniqueSeriesKey);
|
||||
|
||||
// Avoid implicitly captured closure
|
||||
var episodes = GetNextUpEpisodes(request, user, items);
|
||||
var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
|
||||
|
||||
return GetResult(episodes, request);
|
||||
}
|
||||
|
||||
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, List<Folder> parentsFolders)
|
||||
public QueryResult<BaseItem> GetNextUp(NextUpQuery request, List<Folder> parentsFolders, DtoOptions dtoOptions)
|
||||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
|
||||
@@ -118,7 +119,7 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
Fields = new List<ItemFields>
|
||||
{
|
||||
|
||||
ItemFields.PresentationUniqueKey
|
||||
},
|
||||
EnableImages = false
|
||||
}
|
||||
@@ -126,18 +127,18 @@ namespace Emby.Server.Implementations.TV
|
||||
}, parentsFolders.Cast<BaseItem>().ToList()).Cast<Series>().Select(GetUniqueSeriesKey);
|
||||
|
||||
// Avoid implicitly captured closure
|
||||
var episodes = GetNextUpEpisodes(request, user, items);
|
||||
var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
|
||||
|
||||
return GetResult(episodes, request);
|
||||
}
|
||||
|
||||
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys)
|
||||
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<string> seriesKeys, DtoOptions dtoOptions)
|
||||
{
|
||||
// Avoid implicitly captured closure
|
||||
var currentUser = user;
|
||||
|
||||
var allNextUp = seriesKeys
|
||||
.Select(i => GetNextUp(i, currentUser));
|
||||
.Select(i => GetNextUp(i, currentUser, dtoOptions));
|
||||
|
||||
//allNextUp = allNextUp.OrderByDescending(i => i.Item1);
|
||||
|
||||
@@ -175,14 +176,12 @@ namespace Emby.Server.Implementations.TV
|
||||
/// Gets the next up.
|
||||
/// </summary>
|
||||
/// <returns>Task{Episode}.</returns>
|
||||
private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user)
|
||||
private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions)
|
||||
{
|
||||
var enableSeriesPresentationKey = _config.Configuration.EnableSeriesPresentationUniqueKey;
|
||||
|
||||
var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey,
|
||||
SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null,
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
SortBy = new[] { ItemSortBy.SortName },
|
||||
SortOrder = SortOrder.Descending,
|
||||
@@ -193,7 +192,7 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
Fields = new List<ItemFields>
|
||||
{
|
||||
|
||||
ItemFields.SortName
|
||||
},
|
||||
EnableImages = false
|
||||
}
|
||||
@@ -204,8 +203,8 @@ namespace Emby.Server.Implementations.TV
|
||||
{
|
||||
return _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey,
|
||||
SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null,
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
SortBy = new[] { ItemSortBy.SortName },
|
||||
SortOrder = SortOrder.Ascending,
|
||||
@@ -213,7 +212,8 @@ namespace Emby.Server.Implementations.TV
|
||||
IsPlayed = false,
|
||||
IsVirtualItem = false,
|
||||
ParentIndexNumberNotEquals = 0,
|
||||
MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName
|
||||
MinSortName = lastWatchedEpisode == null ? null : lastWatchedEpisode.SortName,
|
||||
DtoOptions = dtoOptions
|
||||
|
||||
}).Cast<Episode>().FirstOrDefault();
|
||||
};
|
||||
|
||||
@@ -139,30 +139,58 @@ namespace Emby.Server.Implementations.Udp
|
||||
{
|
||||
_udpClient = _socketFactory.CreateUdpSocket(port);
|
||||
|
||||
Task.Run(() => StartListening());
|
||||
Task.Run(() => BeginReceive());
|
||||
}
|
||||
|
||||
private async void StartListening()
|
||||
{
|
||||
while (!_isDisposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _udpClient.ReceiveAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
private readonly byte[] _receiveBuffer = new byte[8192];
|
||||
|
||||
OnMessageReceived(result);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
private void BeginReceive()
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = _udpClient.BeginReceive(_receiveBuffer, 0, _receiveBuffer.Length, OnReceiveResult);
|
||||
|
||||
if (result.CompletedSynchronously)
|
||||
{
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error receiving udp message", ex);
|
||||
OnReceiveResult(result);
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error receiving udp message", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReceiveResult(IAsyncResult result)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var socketResult = _udpClient.EndReceive(result);
|
||||
|
||||
OnMessageReceived(socketResult);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error receiving udp message", ex);
|
||||
}
|
||||
|
||||
BeginReceive();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -239,13 +267,13 @@ namespace Emby.Server.Implementations.Udp
|
||||
|
||||
try
|
||||
{
|
||||
await _udpClient.SendWithLockAsync(bytes, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
|
||||
await _udpClient.SendToAsync(bytes, 0, bytes.Length, remoteEndPoint, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
_logger.Info("Udp message sent to {0}", remoteEndPoint);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -11,9 +11,9 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@@ -36,19 +36,20 @@ namespace Emby.Server.Implementations.UserViews
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var view = (CollectionFolder)item;
|
||||
|
||||
var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var result = await view.GetItems(new InternalItemsQuery
|
||||
var result = view.GetItems(new InternalItemsQuery
|
||||
{
|
||||
CollapseBoxSetItems = false,
|
||||
Recursive = recursive,
|
||||
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Playlist" }
|
||||
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Playlist" },
|
||||
DtoOptions = new DtoOptions(false)
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
var items = result.Items.Select(i =>
|
||||
{
|
||||
@@ -98,7 +99,7 @@ namespace Emby.Server.Implementations.UserViews
|
||||
return item is CollectionFolder;
|
||||
}
|
||||
|
||||
protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
{
|
||||
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
|
||||
|
||||
@@ -109,10 +110,10 @@ namespace Emby.Server.Implementations.UserViews
|
||||
return null;
|
||||
}
|
||||
|
||||
return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false);
|
||||
return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540);
|
||||
}
|
||||
|
||||
return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false);
|
||||
return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +134,7 @@ namespace Emby.Server.Implementations.UserViews
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var view = (ManualCollectionsFolder)item;
|
||||
|
||||
@@ -144,7 +145,8 @@ namespace Emby.Server.Implementations.UserViews
|
||||
Recursive = recursive,
|
||||
IncludeItemTypes = new[] { typeof(BoxSet).Name },
|
||||
Limit = 20,
|
||||
SortBy = new[] { ItemSortBy.Random }
|
||||
SortBy = new[] { ItemSortBy.Random },
|
||||
DtoOptions = new DtoOptions(false)
|
||||
});
|
||||
|
||||
return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8);
|
||||
@@ -155,7 +157,7 @@ namespace Emby.Server.Implementations.UserViews
|
||||
return item is ManualCollectionsFolder;
|
||||
}
|
||||
|
||||
protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
{
|
||||
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
|
||||
|
||||
@@ -166,10 +168,10 @@ namespace Emby.Server.Implementations.UserViews
|
||||
return null;
|
||||
}
|
||||
|
||||
return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false);
|
||||
return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540);
|
||||
}
|
||||
|
||||
return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false);
|
||||
return base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Images;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
@@ -47,7 +48,7 @@ namespace Emby.Server.Implementations.UserViews
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
|
||||
protected override List<BaseItem> GetItemsWithImages(IHasImages item)
|
||||
{
|
||||
var view = (UserView)item;
|
||||
|
||||
@@ -58,7 +59,9 @@ namespace Emby.Server.Implementations.UserViews
|
||||
IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
|
||||
ImageTypes = new[] { ImageType.Primary },
|
||||
Limit = 30,
|
||||
IsMovie = true
|
||||
IsMovie = true,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
|
||||
}).ToList();
|
||||
|
||||
return GetFinalItems(programs).ToList();
|
||||
@@ -67,9 +70,10 @@ namespace Emby.Server.Implementations.UserViews
|
||||
if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(view.ViewType, SpecialFolder.TvGenre, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var userItemsResult = await view.GetItems(new InternalItemsQuery
|
||||
var userItemsResult = view.GetItems(new InternalItemsQuery
|
||||
{
|
||||
CollapseBoxSetItems = false
|
||||
CollapseBoxSetItems = false,
|
||||
DtoOptions = new DtoOptions(false)
|
||||
});
|
||||
|
||||
return userItemsResult.Items.ToList();
|
||||
@@ -78,14 +82,14 @@ namespace Emby.Server.Implementations.UserViews
|
||||
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
|
||||
var recursive = isUsingCollectionStrip && !new[] { CollectionType.Channels, CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var result = await view.GetItems(new InternalItemsQuery
|
||||
var result = view.GetItems(new InternalItemsQuery
|
||||
{
|
||||
User = view.UserId.HasValue ? _userManager.GetUserById(view.UserId.Value) : null,
|
||||
CollapseBoxSetItems = false,
|
||||
Recursive = recursive,
|
||||
ExcludeItemTypes = new[] { "UserView", "CollectionFolder", "Person" },
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
DtoOptions = new DtoOptions(false)
|
||||
});
|
||||
|
||||
var items = result.Items.Select(i =>
|
||||
{
|
||||
@@ -159,7 +163,7 @@ namespace Emby.Server.Implementations.UserViews
|
||||
return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
|
||||
}
|
||||
|
||||
protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
protected override string CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||
{
|
||||
if (itemsWithImages.Count == 0)
|
||||
{
|
||||
@@ -168,7 +172,7 @@ namespace Emby.Server.Implementations.UserViews
|
||||
|
||||
var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
|
||||
|
||||
return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false);
|
||||
return CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user