Compare commits

...

24 Commits

Author SHA1 Message Date
Joshua M. Boniface 0ac18a50f5 Bump version to 10.8.9 2023-01-22 14:09:40 -05:00
Joshua M. Boniface 4ebae248df Merge pull request #9130 from Shadowghost/livetv-security 2023-01-22 14:02:34 -05:00
Joshua M. Boniface fbb9acf58b Merge pull request #9145 from nyanmisaka/fix-pgs-8602 2023-01-21 15:48:54 -05:00
nyanmisaka 87f081c8ac Fix PGS position issue in sw decoding #8602
Partially revert #7736

Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-01-21 22:34:46 +08:00
Shadowghost 44077b4f5c Apply review suggestions 2023-01-20 01:27:39 +01:00
Shadowghost 060a80bef7 Enforce download permission on download policy 2023-01-20 00:58:47 +01:00
Shadowghost 885a1b02c1 Secure endpoints in LiveTvController 2023-01-20 00:58:41 +01:00
Joshua M. Boniface 1dea309ae4 Merge pull request #9051 from dmitrylyzo/fix-transcode-reasons 2023-01-19 15:32:40 -05:00
Joshua M. Boniface 32227c76b7 Merge pull request #9112 from nyanmisaka/win-ffmpeg-link 2023-01-17 12:56:06 -05:00
Shadowghost a7c43643a4 Fix Windows FFmpeg download link 2023-01-18 01:47:52 +08:00
Dmitry Lyzo 2a5efeb3bb Don't add additional entries if HEVC encoding is disabled (#9092) 2023-01-14 14:37:11 -07:00
Bond-009 464136cfc9 Merge pull request #9050 from nyanmisaka/update-wa-i915-hang 2023-01-10 20:54:47 +01:00
Bill Thornton 31673cc27d Disable splash screen image by default (#9060) 2023-01-10 09:15:21 -07:00
Dmitry Lyzo 6a909f956e cleanup: remove redundant condition 2023-01-09 23:21:57 +03:00
Dmitry Lyzo 20e9db8308 fix transcode reasons 2023-01-09 23:21:57 +03:00
nyanmisaka f8b8fdace6 Update workaround for the i915 hang
The issue has been fixed in linux 6.2:
https://github.com/torvalds/linux/commit/3f882f2

And the fix was cherry picked into 6.0.18 and
6.1.4 (may be used by debian bookworm).

Signed-off-by: nyanmisaka <nst799610810@gmail.com>
2023-01-10 03:41:20 +08:00
Cody Robibero 2a6e292153 Merge pull request #9049 from Shadowghost/codec-fix
Fixes https://github.com/jellyfin/jellyfin-androidtv/issues/2396
2023-01-09 12:18:09 -07:00
Shadowghost c9f3d9bdde Add truehd to list of audio codecs which require strict -2 2023-01-09 20:07:17 +01:00
David Fairbrother 8d49e0099c Add DavidFair to contributors
Add myself to the list of contributors, as per the development
guidelines found on the JF website.
2023-01-09 19:50:46 +01:00
David Fairbrother 76e3da6a40 Add dts to list of audio codecs which require strict -2
Adds dts to the list of audio codecs where ffmpeg will throw asking us
to opt into experimental support. This is seen when the original content
is based on dts and we don't acopy using ffmpeg.
2023-01-09 19:50:37 +01:00
Bond-009 f0faddcc44 Backport 8726: Fix incorrect starting offset of buffer span in CheckTunerAvailability. (#9020)
Co-authored-by: Michael Powers <swedishborgie@gmail.com>
2023-01-07 11:30:34 -07:00
Bond-009 e6606d41ce Merge pull request #9009 from dmitrylyzo/fix-secondary-audio 2023-01-05 22:57:49 +01:00
Dmitry Lyzo 7c8aea7859 fix tests 2023-01-05 01:15:58 +03:00
Dmitry Lyzo c4c5af40a1 fix secondary audio
Browsers (Chrome, Firefox) can only play the first track,
even if the second track is the default.

Ignore default flag when testing on secondary audio.

External audio tracks are not secondary.
2023-01-05 01:15:58 +03:00
30 changed files with 293 additions and 150 deletions
+1
View File
@@ -27,6 +27,7 @@
- [cvium](https://github.com/cvium) - [cvium](https://github.com/cvium)
- [dannymichel](https://github.com/dannymichel) - [dannymichel](https://github.com/dannymichel)
- [DaveChild](https://github.com/DaveChild) - [DaveChild](https://github.com/DaveChild)
- [DavidFair](https://github.com/DavidFair)
- [Delgan](https://github.com/Delgan) - [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev) - [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung) - [dhartung](https://github.com/dhartung)
+1 -1
View File
@@ -36,7 +36,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId> <PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.8.8</VersionPrefix> <VersionPrefix>10.8.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -67,7 +67,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
return VerifyReturnValueOfGetSet(buffer.AsSpan(receivedBytes), "none"); return VerifyReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), "none");
} }
finally finally
{ {
+19 -1
View File
@@ -43,12 +43,16 @@ namespace Jellyfin.Api.Auth
/// <param name="ignoreSchedule">Whether to ignore parental control.</param> /// <param name="ignoreSchedule">Whether to ignore parental control.</param>
/// <param name="localAccessOnly">Whether access is to be allowed locally only.</param> /// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
/// <param name="requiredDownloadPermission">Whether validation requires download permission.</param> /// <param name="requiredDownloadPermission">Whether validation requires download permission.</param>
/// <param name="requireLiveTvManagementPermission">Whether validation requires LiveTV management permission.</param>
/// <param name="requireLiveTvAccessPermission">Whether validation requires LiveTV management permission.</param>
/// <returns>Validated claim status.</returns> /// <returns>Validated claim status.</returns>
protected bool ValidateClaims( protected bool ValidateClaims(
ClaimsPrincipal claimsPrincipal, ClaimsPrincipal claimsPrincipal,
bool ignoreSchedule = false, bool ignoreSchedule = false,
bool localAccessOnly = false, bool localAccessOnly = false,
bool requiredDownloadPermission = false) bool requiredDownloadPermission = false,
bool requireLiveTvManagementPermission = false,
bool requireLiveTvAccessPermission = false)
{ {
// ApiKey is currently global admin, always allow. // ApiKey is currently global admin, always allow.
var isApiKey = ClaimHelpers.GetIsApiKey(claimsPrincipal); var isApiKey = ClaimHelpers.GetIsApiKey(claimsPrincipal);
@@ -106,6 +110,20 @@ namespace Jellyfin.Api.Auth
return false; return false;
} }
// User attempting to access LiveTV without permission.
if (requireLiveTvAccessPermission
&& !user.HasPermission(PermissionKind.EnableLiveTvAccess))
{
return false;
}
// User attempting to manage LiveTV without permission.
if (requireLiveTvManagementPermission
&& !user.HasPermission(PermissionKind.EnableLiveTvManagement))
{
return false;
}
return true; return true;
} }
} }
@@ -28,7 +28,7 @@ namespace Jellyfin.Api.Auth.DownloadPolicy
/// <inheritdoc /> /// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadRequirement requirement) protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadRequirement requirement)
{ {
var validated = ValidateClaims(context.User); var validated = ValidateClaims(context.User, requiredDownloadPermission: true);
if (validated) if (validated)
{ {
context.Succeed(requirement); context.Succeed(requirement);
@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Jellyfin.Api.Auth.LiveTvAccessPolicy;
/// <summary>
/// Authorization handler for LiveTV access.
/// </summary>
public class LiveTvAccessHandler : BaseAuthorizationHandler<LiveTvAccessRequirement>
{
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvAccessHandler"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public LiveTvAccessHandler(
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
: base(userManager, networkManager, httpContextAccessor)
{
}
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LiveTvAccessRequirement requirement)
{
var validated = ValidateClaims(context.User, requireLiveTvAccessPermission: true);
if (validated)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.LiveTvAccessPolicy;
/// <summary>
/// The LiveTV access requirement.
/// </summary>
public class LiveTvAccessRequirement : IAuthorizationRequirement
{
}
@@ -0,0 +1,43 @@
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Jellyfin.Api.Auth.LiveTvManagementPolicy;
/// <summary>
/// Authorization handler for LiveTV management access.
/// </summary>
public class LiveTvManagementHandler : BaseAuthorizationHandler<LiveTvManagementRequirement>
{
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvManagementHandler"/> class.
/// </summary>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public LiveTvManagementHandler(
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
: base(userManager, networkManager, httpContextAccessor)
{
}
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LiveTvManagementRequirement requirement)
{
var validated = ValidateClaims(context.User, requireLiveTvManagementPermission: true);
if (validated)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
@@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.LiveTvManagementPolicy;
/// <summary>
/// The LiveTV management requirement.
/// </summary>
public class LiveTvManagementRequirement : IAuthorizationRequirement
{
}
+10
View File
@@ -74,5 +74,15 @@ namespace Jellyfin.Api.Constants
/// Policy name for accessing a SyncPlay group. /// Policy name for accessing a SyncPlay group.
/// </summary> /// </summary>
public const string SyncPlayIsInGroup = "SyncPlayIsInGroup"; public const string SyncPlayIsInGroup = "SyncPlayIsInGroup";
/// <summary>
/// Policy name for accessing LiveTV.
/// </summary>
public const string LiveTvAccess = "LiveTvAccess";
/// <summary>
/// Policy name for managing LiveTV.
/// </summary>
public const string LiveTvManagement = "LiveTvManagement";
} }
} }
@@ -1712,11 +1712,13 @@ namespace Jellyfin.Api.Controllers
return audioTranscodeParams; return audioTranscodeParams;
} }
// flac and opus are experimental in mp4 muxer // dts, flac, opus and truehd are experimental in mp4 muxer
var strictArgs = string.Empty; var strictArgs = string.Empty;
if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) || string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
{ {
strictArgs = " -strict -2"; strictArgs = " -strict -2";
} }
+39 -63
View File
@@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Info")] [HttpGet("Info")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<LiveTvInfo> GetLiveTvInfo() public ActionResult<LiveTvInfo> GetLiveTvInfo()
{ {
return _liveTvManager.GetLiveTvInfo(CancellationToken.None); return _liveTvManager.GetLiveTvInfo(CancellationToken.None);
@@ -129,7 +129,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Channels")] [HttpGet("Channels")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels( public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
[FromQuery] ChannelType? type, [FromQuery] ChannelType? type,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -208,7 +208,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
[HttpGet("Channels/{channelId}")] [HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{ {
var user = userId is null || userId.Value.Equals(default) var user = userId is null || userId.Value.Equals(default)
@@ -249,7 +249,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
[HttpGet("Recordings")] [HttpGet("Recordings")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetRecordings( public ActionResult<QueryResult<BaseItemDto>> GetRecordings(
[FromQuery] string? channelId, [FromQuery] string? channelId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -320,7 +320,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
[HttpGet("Recordings/Series")] [HttpGet("Recordings/Series")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")] [Obsolete("This endpoint is obsolete.")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
@@ -363,7 +363,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the recording groups.</returns> /// <returns>An <see cref="OkResult"/> containing the recording groups.</returns>
[HttpGet("Recordings/Groups")] [HttpGet("Recordings/Groups")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")] [Obsolete("This endpoint is obsolete.")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId) public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId)
@@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the recording folders.</returns> /// <returns>An <see cref="OkResult"/> containing the recording folders.</returns>
[HttpGet("Recordings/Folders")] [HttpGet("Recordings/Folders")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId) public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId)
{ {
var user = userId is null || userId.Value.Equals(default) var user = userId is null || userId.Value.Equals(default)
@@ -401,7 +401,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
[HttpGet("Recordings/{recordingId}")] [HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{ {
var user = userId is null || userId.Value.Equals(default) var user = userId is null || userId.Value.Equals(default)
@@ -423,10 +423,9 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Tuners/{tunerId}/Reset")] [HttpPost("Tuners/{tunerId}/Reset")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId) public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -441,7 +440,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Timers/{timerId}")] [HttpGet("Timers/{timerId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId) public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId)
{ {
return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false); return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false);
@@ -457,7 +456,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Timers/Defaults")] [HttpGet("Timers/Defaults")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId) public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId)
{ {
return string.IsNullOrEmpty(programId) return string.IsNullOrEmpty(programId)
@@ -477,7 +476,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Timers")] [HttpGet("Timers")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers( public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers(
[FromQuery] string? channelId, [FromQuery] string? channelId,
[FromQuery] string? seriesTimerId, [FromQuery] string? seriesTimerId,
@@ -531,7 +530,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpGet("Programs")] [HttpGet("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms( public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -614,7 +613,7 @@ namespace Jellyfin.Api.Controllers
/// </returns> /// </returns>
[HttpPost("Programs")] [HttpPost("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body) public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
{ {
var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId); var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId);
@@ -680,7 +679,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Recommended epgs returned.</response> /// <response code="200">Recommended epgs returned.</response>
/// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns> /// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns>
[HttpGet("Programs/Recommended")] [HttpGet("Programs/Recommended")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms( public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@@ -732,7 +731,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Program returned.</response> /// <response code="200">Program returned.</response>
/// <returns>An <see cref="OkResult"/> containing the livetv program.</returns> /// <returns>An <see cref="OkResult"/> containing the livetv program.</returns>
[HttpGet("Programs/{programId}")] [HttpGet("Programs/{programId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetProgram( public async Task<ActionResult<BaseItemDto>> GetProgram(
[FromRoute, Required] string programId, [FromRoute, Required] string programId,
@@ -753,13 +752,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="404">Item not found.</response> /// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpDelete("Recordings/{recordingId}")] [HttpDelete("Recordings/{recordingId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteRecording([FromRoute, Required] Guid recordingId) public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
var item = _libraryManager.GetItemById(recordingId); var item = _libraryManager.GetItemById(recordingId);
if (item == null) if (item == null)
{ {
@@ -781,11 +778,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer deleted.</response> /// <response code="204">Timer deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Timers/{timerId}")] [HttpDelete("Timers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId) public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false); await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -798,12 +794,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer updated.</response> /// <response code="204">Timer updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Timers/{timerId}")] [HttpPost("Timers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo) public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -815,11 +810,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer created.</response> /// <response code="204">Timer created.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Timers")] [HttpPost("Timers")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo) public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -832,7 +826,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="404">Series timer not found.</response> /// <response code="404">Series timer not found.</response>
/// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns> /// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns>
[HttpGet("SeriesTimers/{timerId}")] [HttpGet("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId) public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId)
@@ -854,7 +848,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Timers returned.</response> /// <response code="200">Timers returned.</response>
/// <returns>An <see cref="OkResult"/> of live tv series timers.</returns> /// <returns>An <see cref="OkResult"/> of live tv series timers.</returns>
[HttpGet("SeriesTimers")] [HttpGet("SeriesTimers")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder) public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder)
{ {
@@ -874,11 +868,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Timer cancelled.</response> /// <response code="204">Timer cancelled.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("SeriesTimers/{timerId}")] [HttpDelete("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId) public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false); await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -891,12 +884,11 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Series timer updated.</response> /// <response code="204">Series timer updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("SeriesTimers/{timerId}")] [HttpPost("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo) public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -908,11 +900,10 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Series timer info created.</response> /// <response code="204">Series timer info created.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("SeriesTimers")] [HttpPost("SeriesTimers")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo) public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo)
{ {
await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@@ -923,7 +914,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="groupId">Group id.</param> /// <param name="groupId">Group id.</param>
/// <returns>A <see cref="NotFoundResult"/>.</returns> /// <returns>A <see cref="NotFoundResult"/>.</returns>
[HttpGet("Recordings/Groups/{groupId}")] [HttpGet("Recordings/Groups/{groupId}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("This endpoint is obsolete.")] [Obsolete("This endpoint is obsolete.")]
public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid groupId) public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid groupId)
@@ -937,7 +928,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Guid info returned.</response> /// <response code="200">Guid info returned.</response>
/// <returns>An <see cref="OkResult"/> containing the guide info.</returns> /// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
[HttpGet("GuideInfo")] [HttpGet("GuideInfo")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<GuideInfo> GetGuideInfo() public ActionResult<GuideInfo> GetGuideInfo()
{ {
@@ -951,7 +942,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Created tuner host returned.</response> /// <response code="200">Created tuner host returned.</response>
/// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns> /// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns>
[HttpPost("TunerHosts")] [HttpPost("TunerHosts")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo) public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
{ {
@@ -965,7 +956,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Tuner host deleted.</response> /// <response code="204">Tuner host deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("TunerHosts")] [HttpDelete("TunerHosts")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteTunerHost([FromQuery] string? id) public ActionResult DeleteTunerHost([FromQuery] string? id)
{ {
@@ -981,7 +972,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Default listings provider info returned.</response> /// <response code="200">Default listings provider info returned.</response>
/// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns> /// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns>
[HttpGet("ListingProviders/Default")] [HttpGet("ListingProviders/Default")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<ListingsProviderInfo> GetDefaultListingProvider() public ActionResult<ListingsProviderInfo> GetDefaultListingProvider()
{ {
@@ -998,7 +989,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Created listings provider returned.</response> /// <response code="200">Created listings provider returned.</response>
/// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns> /// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
[HttpPost("ListingProviders")] [HttpPost("ListingProviders")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider( public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
@@ -1025,7 +1016,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="204">Listing provider deleted.</response> /// <response code="204">Listing provider deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("ListingProviders")] [HttpDelete("ListingProviders")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteListingProvider([FromQuery] string? id) public ActionResult DeleteListingProvider([FromQuery] string? id)
{ {
@@ -1043,7 +1034,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Available lineups returned.</response> /// <response code="200">Available lineups returned.</response>
/// <returns>A <see cref="OkResult"/> containing the available lineups.</returns> /// <returns>A <see cref="OkResult"/> containing the available lineups.</returns>
[HttpGet("ListingProviders/Lineups")] [HttpGet("ListingProviders/Lineups")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups( public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups(
[FromQuery] string? id, [FromQuery] string? id,
@@ -1060,7 +1051,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Available countries returned.</response> /// <response code="200">Available countries returned.</response>
/// <returns>A <see cref="FileResult"/> containing the available countries.</returns> /// <returns>A <see cref="FileResult"/> containing the available countries.</returns>
[HttpGet("ListingProviders/SchedulesDirect/Countries")] [HttpGet("ListingProviders/SchedulesDirect/Countries")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile(MediaTypeNames.Application.Json)] [ProducesFile(MediaTypeNames.Application.Json)]
public async Task<ActionResult> GetSchedulesDirectCountries() public async Task<ActionResult> GetSchedulesDirectCountries()
@@ -1081,7 +1072,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Channel mapping options returned.</response> /// <response code="200">Channel mapping options returned.</response>
/// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns> /// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns>
[HttpGet("ChannelMappingOptions")] [HttpGet("ChannelMappingOptions")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId) public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId)
{ {
@@ -1119,7 +1110,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Created channel mapping returned.</response> /// <response code="200">Created channel mapping returned.</response>
/// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns> /// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
[HttpPost("ChannelMappings")] [HttpPost("ChannelMappings")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto) public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto)
{ {
@@ -1132,7 +1123,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Tuner host types returned.</response> /// <response code="200">Tuner host types returned.</response>
/// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns> /// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns>
[HttpGet("TunerHosts/Types")] [HttpGet("TunerHosts/Types")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes() public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
{ {
@@ -1147,7 +1138,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the tuners.</returns> /// <returns>An <see cref="OkResult"/> containing the tuners.</returns>
[HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")] [HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
[HttpGet("Tuners/Discover")] [HttpGet("Tuners/Discover")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
{ {
@@ -1207,20 +1198,5 @@ namespace Jellyfin.Api.Controllers
var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream());
return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container));
} }
private async Task AssertUserCanManageLiveTv()
{
var user = await _sessionContext.GetUser(Request).ConfigureAwait(false);
if (user == null)
{
throw new SecurityException("Anonymous live tv management is not allowed.");
}
if (!user.HasPermission(PermissionKind.EnableLiveTvManagement))
{
throw new SecurityException("The current user does not have permission to manage live tv.");
}
}
} }
} }
+5 -1
View File
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@@ -202,8 +203,11 @@ namespace Jellyfin.Api.Helpers
if (state.VideoStream != null && state.VideoRequest != null) if (state.VideoStream != null && state.VideoRequest != null)
{ {
var encodingOptions = _serverConfigurationManager.GetEncodingOptions();
// Provide SDR HEVC entrance for backward compatibility. // Provide SDR HEVC entrance for backward compatibility.
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec) if (encodingOptions.AllowHevcEncoding
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
&& !string.IsNullOrEmpty(state.VideoStream.VideoRange) && !string.IsNullOrEmpty(state.VideoStream.VideoRange)
&& string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) && string.Equals(state.VideoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase)
&& string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) && string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
+1 -1
View File
@@ -18,7 +18,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId> <PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.8.8</VersionPrefix> <VersionPrefix>10.8.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -14,6 +14,8 @@ using Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy; using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
using Jellyfin.Api.Auth.LiveTvAccessPolicy;
using Jellyfin.Api.Auth.LiveTvManagementPolicy;
using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
using Jellyfin.Api.Auth.LocalAccessPolicy; using Jellyfin.Api.Auth.LocalAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy;
@@ -66,6 +68,8 @@ namespace Jellyfin.Server.Extensions
serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LiveTvAccessHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, LiveTvManagementHandler>();
serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>();
return serviceCollection.AddAuthorizationCore(options => return serviceCollection.AddAuthorizationCore(options =>
{ {
@@ -167,6 +171,20 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new AnonymousLanAccessRequirement()); policy.AddRequirements(new AnonymousLanAccessRequirement());
}); });
options.AddPolicy(
Policies.LiveTvAccess,
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new LiveTvAccessRequirement());
});
options.AddPolicy(
Policies.LiveTvManagement,
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new LiveTvManagementRequirement());
});
}); });
} }
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId> <PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.8.8</VersionPrefix> <VersionPrefix>10.8.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId> <PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.8.8</VersionPrefix> <VersionPrefix>10.8.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -35,7 +35,11 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder; private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config; private readonly IConfiguration _config;
private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
// i915 hang was fixed by linux 6.2 (3f882f2)
private readonly Version _minKerneli915Hang = new Version(5, 18);
private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
private static readonly string[] _videoProfilesH264 = new[] private static readonly string[] _videoProfilesH264 = new[]
{ {
@@ -1309,7 +1313,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping. // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
var intelLowPowerHwEncoding = false; var intelLowPowerHwEncoding = false;
// Workaround for linux 5.18+ i915 hang at cost of performance. // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
// https://github.com/intel/media-driver/issues/1456 // https://github.com/intel/media-driver/issues/1456
var enableWaFori915Hang = false; var enableWaFori915Hang = false;
@@ -1328,18 +1332,25 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{ {
if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang) if (OperatingSystem.IsLinux())
{ {
var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty; var ver = Environment.OSVersion.Version;
var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase) var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
|| vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase); var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
&& IsVaapiSupported(state)
&& IsOpenclFullSupported()
&& !IsVaapiVppTonemapAvailable(state, encodingOptions)
&& IsHwTonemapAvailable(state, encodingOptions);
enableWaFori915Hang = isIntelDecoder && doOclTonemap; if (!(isUnaffectedKernel || isFixedKernel60))
{
var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
|| vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
&& IsVaapiSupported(state)
&& IsOpenclFullSupported()
&& !IsVaapiVppTonemapAvailable(state, encodingOptions)
&& IsHwTonemapAvailable(state, encodingOptions);
enableWaFori915Hang = isIntelDecoder && doOclTonemap;
}
} }
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
@@ -2879,8 +2890,8 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
else if (hasGraphicalSubs) else if (hasGraphicalSubs)
{ {
// [0:s]scale=expr // [0:s]scale=s=1280x720
var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH); var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3066,9 +3077,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3268,9 +3277,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3522,9 +3529,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
} }
@@ -3733,9 +3738,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
} }
@@ -3982,9 +3985,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4159,9 +4160,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
if (hasGraphicalSubs) if (hasGraphicalSubs)
{ {
var subSwScaleFilter = isSwDecoder var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
: GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter); subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0"); overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -23,7 +23,7 @@ public class BrandingOptions
/// <summary> /// <summary>
/// Gets or sets a value indicating whether to enable the splashscreen. /// Gets or sets a value indicating whether to enable the splashscreen.
/// </summary> /// </summary>
public bool SplashscreenEnabled { get; set; } = true; public bool SplashscreenEnabled { get; set; } = false;
/// <summary> /// <summary>
/// Gets or sets the splashscreen location on disk. /// Gets or sets the splashscreen location on disk.
+14 -9
View File
@@ -1088,9 +1088,6 @@ namespace MediaBrowser.Model.Dlna
bool? isInterlaced = videoStream?.IsInterlaced; bool? isInterlaced = videoStream?.IsInterlaced;
string videoCodecTag = videoStream?.CodecTag; string videoCodecTag = videoStream?.CodecTag;
bool? isAvc = videoStream?.IsAVC; bool? isAvc = videoStream?.IsAVC;
// Audio
var defaultLanguage = audioStream?.Language ?? string.Empty;
var defaultMarked = audioStream?.IsDefault ?? false;
TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp; TransportStreamTimestamp? timestamp = videoStream == null ? TransportStreamTimestamp.None : mediaSource.Timestamp;
int? packetLength = videoStream?.PacketLength; int? packetLength = videoStream?.PacketLength;
@@ -1122,7 +1119,7 @@ namespace MediaBrowser.Model.Dlna
.SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions))); .SelectMany(codecProfile => checkVideoConditions(codecProfile.Conditions)));
// Check audiocandidates profile conditions // Check audiocandidates profile conditions
var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream, defaultLanguage, defaultMarked)); var audioStreamMatches = candidateAudioStreams.ToDictionary(s => s, audioStream => CheckVideoAudioStreamDirectPlay(options, mediaSource, container, audioStream));
TranscodeReason subtitleProfileReasons = 0; TranscodeReason subtitleProfileReasons = 0;
if (subtitleStream != null) if (subtitleStream != null)
@@ -1147,7 +1144,6 @@ namespace MediaBrowser.Model.Dlna
var reason = a & flag; var reason = a & flag;
if (reason != 0) if (reason != 0)
{ {
a = reason;
return index; return index;
} }
@@ -1157,6 +1153,8 @@ namespace MediaBrowser.Model.Dlna
return index; return index;
}; };
var containerSupported = false;
// Check DirectPlay profiles to see if it can be direct played // Check DirectPlay profiles to see if it can be direct played
var analyzedProfiles = profile.DirectPlayProfiles var analyzedProfiles = profile.DirectPlayProfiles
.Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video) .Where(directPlayProfile => directPlayProfile.Type == DlnaProfileType.Video)
@@ -1170,6 +1168,10 @@ namespace MediaBrowser.Model.Dlna
{ {
directPlayProfileReasons |= TranscodeReason.ContainerNotSupported; directPlayProfileReasons |= TranscodeReason.ContainerNotSupported;
} }
else
{
containerSupported = true;
}
// Check video codec // Check video codec
string videoCodec = videoStream?.Codec; string videoCodec = videoStream?.Codec;
@@ -1212,7 +1214,7 @@ namespace MediaBrowser.Model.Dlna
{ {
playMethod = PlayMethod.DirectPlay; playMethod = PlayMethod.DirectPlay;
} }
else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream && directPlayProfile != null) else if (directStreamFailureReasons == 0 && isEligibleForDirectStream && mediaSource.SupportsDirectStream)
{ {
playMethod = PlayMethod.DirectStream; playMethod = PlayMethod.DirectStream;
} }
@@ -1234,7 +1236,10 @@ namespace MediaBrowser.Model.Dlna
return profileMatch; return profileMatch;
} }
var failureReasons = analyzedProfiles[false].Select(analysis => analysis.Result).FirstOrDefault().TranscodeReason; var failureReasons = analyzedProfiles[false]
.Select(analysis => analysis.Result)
.Where(result => !containerSupported || (result.TranscodeReason & TranscodeReason.ContainerNotSupported) == 0)
.FirstOrDefault().TranscodeReason;
if (failureReasons == 0) if (failureReasons == 0)
{ {
failureReasons = TranscodeReason.DirectPlayError; failureReasons = TranscodeReason.DirectPlayError;
@@ -1243,10 +1248,10 @@ namespace MediaBrowser.Model.Dlna
return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons); return (Profile: null, PlayMethod: null, AudioStreamIndex: null, TranscodeReasons: failureReasons);
} }
private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream, string language, bool isDefault) private TranscodeReason CheckVideoAudioStreamDirectPlay(VideoOptions options, MediaSourceInfo mediaSource, string container, MediaStream audioStream)
{ {
var profile = options.Profile; var profile = options.Profile;
var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, !audioStream.IsDefault); var audioFailureConditions = GetProfileConditionsForVideoAudio(profile.CodecProfiles, container, audioStream.Codec, audioStream.Channels, audioStream.BitRate, audioStream.SampleRate, audioStream.BitDepth, audioStream.Profile, mediaSource.IsSecondaryAudio(audioStream));
var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions); var audioStreamFailureReasons = AggregateFailureConditions(mediaSource, profile, "VideoAudioCodecProfile", audioFailureConditions);
if (audioStream?.IsExternal == true) if (audioStream?.IsExternal == true)
+3 -7
View File
@@ -230,19 +230,15 @@ namespace MediaBrowser.Model.Dto
public bool? IsSecondaryAudio(MediaStream stream) public bool? IsSecondaryAudio(MediaStream stream)
{ {
// Look for the first audio track marked as default if (stream.IsExternal)
foreach (var currentStream in MediaStreams)
{ {
if (currentStream.Type == MediaStreamType.Audio && currentStream.IsDefault) return false;
{
return currentStream.Index != stream.Index;
}
} }
// Look for the first audio track // Look for the first audio track
foreach (var currentStream in MediaStreams) foreach (var currentStream in MediaStreams)
{ {
if (currentStream.Type == MediaStreamType.Audio) if (currentStream.Type == MediaStreamType.Audio && !currentStream.IsExternal)
{ {
return currentStream.Index != stream.Index; return currentStream.Index != stream.Index;
} }
+1 -1
View File
@@ -8,7 +8,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId> <PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.8.8</VersionPrefix> <VersionPrefix>10.8.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
+2 -2
View File
@@ -1,4 +1,4 @@
using System.Reflection; using System.Reflection;
[assembly: AssemblyVersion("10.8.8")] [assembly: AssemblyVersion("10.8.9")]
[assembly: AssemblyFileVersion("10.8.8")] [assembly: AssemblyFileVersion("10.8.9")]
+1 -1
View File
@@ -1,7 +1,7 @@
--- ---
# We just wrap `build` so this is really it # We just wrap `build` so this is really it
name: "jellyfin" name: "jellyfin"
version: "10.8.8" version: "10.8.9"
packages: packages:
- debian.amd64 - debian.amd64
- debian.arm64 - debian.arm64
+6
View File
@@ -1,3 +1,9 @@
jellyfin-server (10.8.9-1) unstable; urgency=medium
* New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.9
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sun, 22 Jan 2023 14:09:37 -0500
jellyfin-server (10.8.8-1) unstable; urgency=medium jellyfin-server (10.8.8-1) unstable; urgency=medium
* New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.8 * New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.8
+1 -1
View File
@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
Standards-Version: 3.9.2 Standards-Version: 3.9.2
Package: jellyfin Package: jellyfin
Version: 10.8.8 Version: 10.8.9
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org> Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
Depends: jellyfin-server, jellyfin-web Depends: jellyfin-server, jellyfin-web
Description: Provides the Jellyfin Free Software Media System Description: Provides the Jellyfin Free Software Media System
+1 -1
View File
@@ -8,7 +8,7 @@ set -o xtrace
# Version variables # Version variables
NSSM_VERSION="nssm-2.24-101-g897c7ad" NSSM_VERSION="nssm-2.24-101-g897c7ad"
NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip" NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
FFMPEG_URL="https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip"; FFMPEG_URL="https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg-portable_win64.zip";
# Move to source directory # Move to source directory
pushd ${SOURCE_DIR} pushd ${SOURCE_DIR}
+3 -1
View File
@@ -7,7 +7,7 @@
%endif %endif
Name: jellyfin Name: jellyfin
Version: 10.8.8 Version: 10.8.9
Release: 1%{?dist} Release: 1%{?dist}
Summary: The Free Software Media System Summary: The Free Software Media System
License: GPLv2 License: GPLv2
@@ -176,6 +176,8 @@ fi
%systemd_postun_with_restart jellyfin.service %systemd_postun_with_restart jellyfin.service
%changelog %changelog
* Sun Jan 22 2023 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.9; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.9
* Tue Nov 29 2022 Jellyfin Packaging Team <packaging@jellyfin.org> * Tue Nov 29 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
- New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.8 - New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.8
* Mon Oct 31 2022 Jellyfin Packaging Team <packaging@jellyfin.org> * Mon Oct 31 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
@@ -13,7 +13,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId> <PackageId>Jellyfin.Extensions</PackageId>
<VersionPrefix>10.8.8</VersionPrefix> <VersionPrefix>10.8.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>
@@ -21,23 +21,23 @@ namespace Jellyfin.Model.Tests
[Theory] [Theory]
// Chrome // Chrome
[InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Chrome", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
[InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
// Firefox // Firefox
[InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Firefox", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
[InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Firefox", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
@@ -59,11 +59,11 @@ namespace Jellyfin.Model.Tests
[InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")] [InlineData("AndroidPixel", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.ContainerBitrateExceedsLimit, "Transcode")]
// Yatse // Yatse
[InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Yatse", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Yatse", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] [InlineData("Yatse", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)]
[InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Yatse", "mp4-hevc-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
// RokuSSPlus // RokuSSPlus
[InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("RokuSSPlus", "mp4-h264-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 should be DirectPlay [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 should be DirectPlay
@@ -83,12 +83,12 @@ namespace Jellyfin.Model.Tests
[InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("JellyfinMediaPlayer", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay)] // #6450
// Chrome-NoHLS // Chrome-NoHLS
[InlineData("Chrome-NoHLS", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Chrome-NoHLS", "mp4-h264-aac-vtt-2600k", PlayMethod.DirectPlay)] // #6450
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 [InlineData("Chrome-NoHLS", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] [InlineData("Chrome-NoHLS", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode", "http")] [InlineData("Chrome-NoHLS", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode", "http")]
[InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Chrome-NoHLS", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
@@ -176,7 +176,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
[InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Chrome", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Chrome", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
@@ -186,7 +186,7 @@ namespace Jellyfin.Model.Tests
[InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-aacDef-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.AudioCodecNotSupported, "Transcode")]
[InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.ContainerNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450 [InlineData("Firefox", "mkv-vp9-ac3-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioCodecNotSupported)] // #6450
[InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Firefox", "mkv-vp9-vorbis-vtt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
@@ -273,15 +273,15 @@ namespace Jellyfin.Model.Tests
[Theory] [Theory]
// Chrome // Chrome
[InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450 [InlineData("Chrome", "mp4-h264-ac3-aacExt-srt-2600k", PlayMethod.DirectStream, TranscodeReason.AudioIsExternal)] // #6450
[InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Chrome", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")]
// Firefox // Firefox
[InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay)] // #6450 [InlineData("Firefox", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported, "Transcode")] [InlineData("Firefox", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.Transcode, TranscodeReason.VideoCodecNotSupported | TranscodeReason.SecondaryAudioNotSupported, "Transcode")]
// Yatse // Yatse
[InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Yatse", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
[InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("Yatse", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectStream, TranscodeReason.SecondaryAudioNotSupported, "Remux")] // #6450
// RokuSSPlus // RokuSSPlus
[InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("RokuSSPlus", "mp4-h264-ac3-aac-srt-2600k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450
[InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450 [InlineData("RokuSSPlus", "mp4-hevc-ac3-aac-srt-15200k", PlayMethod.DirectPlay, (TranscodeReason)0, "Remux")] // #6450