Skip to content

Commit

Permalink
Push initial version
Browse files Browse the repository at this point in the history
  • Loading branch information
lucapolesel committed Oct 5, 2022
1 parent db8c897 commit ba7c1f1
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 151 deletions.
4 changes: 2 additions & 2 deletions Jellyfin.Plugin.Template.sln → BlockTranscoding.sln
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Plugin.Template", "Jellyfin.Plugin.Template\Jellyfin.Plugin.Template.csproj", "{D921B930-CF91-406F-ACBC-08914DCD0D34}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlockTranscoding", "BlockTranscoding\BlockTranscoding.csproj", "{D921B930-CF91-406F-ACBC-08914DCD0D34}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -12,4 +12,4 @@ Global
{D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D921B930-CF91-406F-ACBC-08914DCD0D34}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
EndGlobal
223 changes: 223 additions & 0 deletions BlockTranscoding/BlockTranscoding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using BlockTranscoding.Utilities;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Session;
using Microsoft.Extensions.Logging;

namespace BlockTranscoding;

/// <summary>
/// Automatically block content from transcoding.
/// </summary>
public class BlockTranscoding : IServerEntryPoint, IDisposable
{
private readonly object _playbackStoppedCommandLock = new();

private readonly IUserDataManager _userDataManager;
private readonly ISessionManager _sessionManager;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger<BlockTranscoding> _logger;

private readonly System.Timers.Timer _playbackTimer = new(1000);

private readonly Dictionary<string, bool> _playbackStoppedCommand;

/// <summary>
/// Initializes a new instance of the <see cref="BlockTranscoding"/> class.
/// </summary>
/// <param name="userDataManager">User data manager.</param>
/// <param name="sessionManager">Session manager.</param>
/// <param name="loggerFactory">Logger factory.</param>
public BlockTranscoding(
IUserDataManager userDataManager,
ISessionManager sessionManager,
ILoggerFactory loggerFactory)
{
_userDataManager = userDataManager;
_sessionManager = sessionManager;
_loggerFactory = loggerFactory;
_logger = loggerFactory.CreateLogger<BlockTranscoding>();
_playbackStoppedCommand = new Dictionary<string, bool>();
}

/// <summary>
/// Subscribe to the PlaybackStart callback.
/// </summary>
/// <returns>Task completion.</returns>
public Task RunAsync()
{
_logger.LogInformation("Setting up BlockTranscoding");

_userDataManager.UserDataSaved += UserDataManager_UserDataSaved;
Plugin.Instance!.BlockTranscodingChanged += BlockTranscodingChanged;

_playbackTimer.AutoReset = true;
_playbackTimer.Elapsed += PlaybackTimer_Elapsed;

BlockTranscodingChanged(null, EventArgs.Empty);

return Task.CompletedTask;
}

private void BlockTranscodingChanged(object? sender, EventArgs e)
{
var newState = Plugin.Instance!.Configuration.BlockTranscoding;

_logger.LogDebug("Setting playback timer enabled to {NewState}.", newState);

_playbackTimer.Enabled = newState;
}

private void UserDataManager_UserDataSaved(object? sender, UserDataSaveEventArgs e)
{
var itemId = e.Item.Id;

if (e.SaveReason != UserDataSaveReason.PlaybackStart && e.SaveReason != UserDataSaveReason.PlaybackFinished)
{
return;
}

// Lookup the session for this item.
SessionInfo? session = null;

try
{
foreach (var needle in _sessionManager.Sessions)
{
if (needle.UserId == e.UserId && needle.NowPlayingItem?.Id == itemId)
{
session = needle;
break;
}
}

if (session == null)
{
_logger.LogInformation("Unable to find session for {Item}", itemId);
return;
}
}
catch (Exception ex) when (ex is NullReferenceException || ex is ResourceNotFoundException)
{
return;
}

// Reset the stop command state for this device.
lock (_playbackStoppedCommandLock)
{
var device = session.DeviceId;

_logger.LogDebug("Resetting seek command state for session {Session}", device);
_playbackStoppedCommand[device] = false;
}
}

private void PlaybackTimer_Elapsed(object? sender, ElapsedEventArgs e)
{
foreach (var session in _sessionManager.Sessions)
{
var deviceId = session.DeviceId;
var playingItem = session.NowPlayingItem;

lock (_playbackStoppedCommandLock)
{
if (_playbackStoppedCommand.TryGetValue(deviceId, out var stopped) && stopped)
{
_logger.LogTrace("Already sent stop command for session {Session}", deviceId);
continue;
}
}

// Check if it is actually a video
if (playingItem?.MediaType == "Video")
{
// Check if it is transcoding
if (session.PlayState.PlayMethod == PlayMethod.Transcode)
{
// Ignore if the video is not being transcoded
if (session.TranscodingInfo.IsVideoDirect)
{
continue;
}

// Check if the video is being transcoded because it was over the max resolution
var maxRes = Plugin.Instance!.Configuration.MaxResolution;
var maxResSize = ResolutionUtility.GetSize(maxRes);

if (playingItem.Width > maxResSize.Width || playingItem.Height > maxResSize.Height)
{
_sessionManager.SendPlaystateCommand(
session.Id,
session.Id,
new PlaystateRequest
{
Command = PlaystateCommand.Stop,
ControllingUserId = session.UserId.ToString("N"),
},
CancellationToken.None);

lock (_playbackStoppedCommandLock)
{
_logger.LogTrace("Setting stop command state for session {Session}", deviceId);
_playbackStoppedCommand[deviceId] = true;
}

var customMessage = Plugin.Instance!.Configuration.CustomMessage;

if (string.IsNullOrEmpty(customMessage))
{
continue;
}

// TODO: Maybe allow the admin to tell the user which resolution has been blocked.

_sessionManager.SendMessageCommand(
session.Id,
session.Id,
new MessageCommand()
{
Header = string.Empty,
Text = customMessage,
TimeoutMs = 2000,
},
CancellationToken.None);
}
}
}
}
}

/// <summary>
/// Dispose.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
/// Protected dispose.
/// </summary>
/// <param name="disposing">Dispose.</param>
protected virtual void Dispose(bool disposing)
{
if (!disposing)
{
return;
}

_userDataManager.UserDataSaved -= UserDataManager_UserDataSaved;

_playbackTimer?.Stop();
_playbackTimer?.Dispose();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>Jellyfin.Plugin.Template</RootNamespace>
<RootNamespace>BlockTranscoding</RootNamespace>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
Expand Down
68 changes: 68 additions & 0 deletions BlockTranscoding/Configuration/PluginConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.ComponentModel;
using MediaBrowser.Model.Plugins;

namespace BlockTranscoding.Configuration;

/// <summary>
/// Resolutions.
/// </summary>
public enum Resolutions
{
/// <summary>
/// 480p.
/// </summary>
[Description("480p")]
StandardDefinition,

/// <summary>
/// 720p.
/// </summary>
[Description("720p")]
HighDefinition,

/// <summary>
/// Full HD.
/// </summary>
[Description("1080p")]
FullHD,

/// <summary>
/// Quad HD.
/// </summary>
[Description("1440p")]
QuadHD,

/// <summary>
/// 4K.
/// </summary>
[Description("2160p")]
UltraHD,
}

/// <summary>
/// Plugin configuration.
/// </summary>
public class PluginConfiguration : BasePluginConfiguration
{
/// <summary>
/// Initializes a new instance of the <see cref="PluginConfiguration"/> class.
/// </summary>
public PluginConfiguration()
{
}

/// <summary>
/// Gets or sets a value indicating whether the plugin should start blocking the playback.
/// </summary>
public bool BlockTranscoding { get; set; } = false;

/// <summary>
/// Gets or sets a custom message when the playback gets stopped.
/// </summary>
public string CustomMessage { get; set; } = "4k trasconding is disabled.";

/// <summary>
/// Gets or sets the max allowed playback resolution.
/// </summary>
public Resolutions MaxResolution { get; set; } = Resolutions.FullHD;
}
74 changes: 74 additions & 0 deletions BlockTranscoding/Configuration/configPage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Block transcoding</title>
</head>
<body>
<div id="TemplateConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
<div data-role="content">
<div class="content-primary">
<form id="TemplateConfigForm">
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="BlockTranscoding" name="BlockTranscoding" type="checkbox" is="emby-checkbox" />
<span>Block transcoding</span>
</label>
</div>
<div class="selectContainer">
<label class="selectLabel" for="MaxResolution">Block trasconding above:</label>
<select is="emby-select" id="MaxResolution" name="MaxResolution" class="emby-select-withcolor emby-select">
<option id="optStandardDefinition" value="StandardDefinition">480p</option>
<option id="optHighDefinition" value="HighDefinition">720p</option>
<option id="optFullHD" value="FullHD">1080p</option>
<option id="optQuadHD" value="QuadHD">1440p</option>
<option id="optUltraHD" value="UltraHD">2160p</option>
</select>
</div>
<div class="inputContainer">
<label class="inputeLabel inputLabelUnfocused" for="CustomMessage">Custom message:</label>
<input id="CustomMessage" name="CustomMessage" type="text" is="emby-input" />
</div>
<div>
<button is="emby-button" type="submit" class="raised button-submit block emby-button">
<span>Save</span>
</button>
</div>
</form>
</div>
</div>
<script type="text/javascript">
var TemplateConfig = {
pluginUniqueId: '55330139-1f8b-4e5d-a207-2afece96e7a6'
};

document.querySelector('#TemplateConfigPage')
.addEventListener('pageshow', function() {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) {
document.querySelector('#BlockTranscoding').checked = config.BlockTranscoding;
document.querySelector('#MaxResolution').value = config.MaxResolution;
document.querySelector('#CustomMessage').value = config.CustomMessage;
Dashboard.hideLoadingMsg();
});
});

document.querySelector('#TemplateConfigForm')
.addEventListener('submit', function(e) {
Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(TemplateConfig.pluginUniqueId).then(function (config) {
config.BlockTranscoding = document.querySelector('#BlockTranscoding').checked;
config.MaxResolution = document.querySelector('#MaxResolution').value;
config.CustomMessage = document.querySelector('#CustomMessage').value;
ApiClient.updatePluginConfiguration(TemplateConfig.pluginUniqueId, config).then(function (result) {
Dashboard.processPluginConfigurationUpdateResult(result);
});
});

e.preventDefault();
return false;
});
</script>
</div>
</body>
</html>
Loading

0 comments on commit ba7c1f1

Please sign in to comment.