-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3ed81cb
Showing
8 changed files
with
835 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"version": "2.0.0", | ||
"tasks": [ | ||
{ | ||
"label": "publish", | ||
"command": "dotnet", | ||
"type": "process", | ||
"args": [ | ||
"publish", | ||
"-c", | ||
"Release", | ||
"-o", | ||
"publish", | ||
"-r", | ||
"win-x64", | ||
"/p:NativeLib=Shared", | ||
"/p:SelfContained=true" | ||
], | ||
"problemMatcher": "$msCompile", | ||
"group": { | ||
"kind": "build", | ||
"isDefault": true | ||
} | ||
}, | ||
{ | ||
"label": "test", | ||
"type": "shell", | ||
"command": ".\\test.local.cmd" | ||
}, | ||
{ | ||
"label": "publish-and-test", | ||
"type": "shell", | ||
"command": ".\\test.local.cmd", | ||
"dependsOrder": "sequence", | ||
"dependsOn": ["publish"], | ||
"problemMatcher": "$msCompile" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using ObsInterop; | ||
namespace ObsCSharpExample; | ||
|
||
public class BrowserFilter | ||
{ | ||
public unsafe struct Context | ||
{ | ||
public obs_source* Source; | ||
public obs_data* Settings; | ||
public bool Active; | ||
public float SecondsWaited; | ||
public int RefreshIntervalSeconds; | ||
} | ||
|
||
#region Helper methods | ||
public static unsafe void Register() | ||
{ | ||
var sourceInfo = new obs_source_info(); | ||
fixed (byte* id = Encoding.UTF8.GetBytes(Module.ModuleName + " Browser Filter")) | ||
{ | ||
sourceInfo.id = (sbyte*)id; | ||
sourceInfo.type = obs_source_type.OBS_SOURCE_TYPE_FILTER; | ||
sourceInfo.output_flags = ObsSource.OBS_SOURCE_VIDEO; | ||
sourceInfo.get_name = &filter_get_name; | ||
sourceInfo.create = &filter_create; | ||
sourceInfo.show = &filter_show; | ||
sourceInfo.hide = &filter_hide; | ||
sourceInfo.destroy = &filter_destroy; | ||
sourceInfo.get_defaults = &filter_get_defaults; | ||
sourceInfo.get_properties = &filter_get_properties; | ||
sourceInfo.save = &filter_save; | ||
sourceInfo.video_tick = &filter_tick; | ||
ObsSource.obs_register_source_s(&sourceInfo, (nuint)Marshal.SizeOf(sourceInfo)); | ||
} | ||
} | ||
|
||
public static unsafe void RefreshBrowserSource(Context* context) | ||
{ | ||
//TODO: feature: freeze the source display for a configurable amount of time during refresh to prevent flickering | ||
Module.Log("RefreshBrowserSource called", ObsLogLevel.Debug); | ||
var browserSource = Obs.obs_filter_get_parent(context->Source); | ||
if ((browserSource == null) || !Convert.ToBoolean(Obs.obs_source_active(browserSource))) | ||
return; | ||
|
||
new Thread(() => | ||
{ | ||
var sourceProperties = Obs.obs_source_properties(browserSource); | ||
fixed (byte* refreshButtonId = Encoding.UTF8.GetBytes("refreshnocache")) | ||
{ | ||
var property = ObsProperties.obs_properties_get(sourceProperties, (sbyte*)refreshButtonId); | ||
if (property != null) // could be null if this filter is applied on something that is not a browser source | ||
{ | ||
Module.Log("Refreshing browser...", ObsLogLevel.Debug); | ||
ObsProperties.obs_property_button_clicked(property, browserSource); | ||
} | ||
else | ||
{ | ||
string sourceDisplayName = Marshal.PtrToStringUTF8((IntPtr)Obs.obs_source_get_name(browserSource))!; | ||
Module.Log("Failed to refresh source \"" + sourceDisplayName + "\", is this filter really applied to a browser source?", ObsLogLevel.Error); | ||
} | ||
ObsProperties.obs_properties_destroy(sourceProperties); | ||
} | ||
}).Start(); | ||
} | ||
#endregion Helper methods | ||
|
||
#region Filter API methods | ||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe sbyte* filter_get_name(void* data) | ||
{ | ||
Module.Log("filter_get_name called", ObsLogLevel.Debug); | ||
fixed (byte* logMessagePtr = Encoding.UTF8.GetBytes("Browser Auto-refresh")) | ||
return (sbyte*)logMessagePtr; | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void* filter_create(obs_data* settings, obs_source* source) | ||
{ | ||
Module.Log("filter_create called", ObsLogLevel.Debug); | ||
|
||
Context* context = (Context*)Marshal.AllocCoTaskMem(sizeof(Context)); | ||
context->Source = source; | ||
context->Settings = settings; | ||
fixed (byte* intervalId = Encoding.UTF8.GetBytes("interval")) | ||
context->RefreshIntervalSeconds = (int)ObsData.obs_data_get_int(settings, (sbyte*)intervalId); | ||
return (void*)context; | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void filter_show(void* data) | ||
{ | ||
Module.Log("filter_show called", ObsLogLevel.Debug); | ||
|
||
var context = (Context*)data; | ||
context->SecondsWaited = 0; // if a browser refresh on show is wanted this can be configured in the browser source, the assumption here is that the interval starts from the time the browser source is being shown | ||
context->Active = true; | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void filter_hide(void* data) | ||
{ | ||
Module.Log("filter_hide called", ObsLogLevel.Debug); | ||
((Context*)data)->Active = false; | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void filter_destroy(void* data) | ||
{ | ||
Module.Log("filter_destroy called", ObsLogLevel.Debug); | ||
Marshal.FreeCoTaskMem((IntPtr)data); | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe obs_properties* filter_get_properties(void* data) | ||
{ | ||
Module.Log("filter_get_properties called", ObsLogLevel.Debug); | ||
|
||
var properties = ObsProperties.obs_properties_create(); | ||
fixed (byte* | ||
intervalId = Encoding.UTF8.GetBytes("interval"), | ||
intervalCaption = Module.ObsText("IntervalCaption"), | ||
intervalText = Module.ObsText("IntervalText") | ||
) | ||
{ | ||
var prop = ObsProperties.obs_properties_add_int(properties, (sbyte*)intervalId, (sbyte*)intervalCaption, 1, int.MaxValue, 1); | ||
ObsProperties.obs_property_set_long_description(prop, (sbyte*)intervalText); | ||
} | ||
return properties; | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void filter_get_defaults(obs_data* settings) | ||
{ | ||
Module.Log("filter_get_defaults called", ObsLogLevel.Debug); | ||
fixed (byte* intervalId = Encoding.UTF8.GetBytes("interval")) | ||
ObsData.obs_data_set_default_int(settings, (sbyte*)intervalId, 60); | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void filter_save(void* data, obs_data* settings) | ||
{ | ||
var context = (Context*)data; | ||
Module.Log("filter_save called", ObsLogLevel.Debug); | ||
fixed (byte* intervalId = Encoding.UTF8.GetBytes("interval")) | ||
context->RefreshIntervalSeconds = (int)ObsData.obs_data_get_int(settings, (sbyte*)intervalId); | ||
Module.Log("Browser auto refresh interval was set to " + context->RefreshIntervalSeconds + " second(s)", ObsLogLevel.Debug); | ||
} | ||
|
||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void filter_tick(void* data, float seconds) | ||
{ | ||
var context = (Context*)data; | ||
if (context->Active && ((context->SecondsWaited += seconds) >= context->RefreshIntervalSeconds)) | ||
{ | ||
context->SecondsWaited -= context->RefreshIntervalSeconds; | ||
RefreshBrowserSource(context); | ||
} | ||
} | ||
#endregion Filter API methods | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
using ObsInterop; | ||
namespace ObsCSharpExample; | ||
|
||
public enum ObsLogLevel : int | ||
{ | ||
Error = ObsBase.LOG_ERROR, | ||
Warning = ObsBase.LOG_WARNING, | ||
Info = ObsBase.LOG_INFO, | ||
Debug = ObsBase.LOG_DEBUG | ||
} | ||
|
||
public static class Module | ||
{ | ||
|
||
const bool DebugLog = false; // set this to true and recompile to get debug messages from this plug-in only (unlike getting the full log spam when enabling debug log globally in OBS) | ||
const string DefaultLocale = "en-US"; | ||
public static string ModuleName = "xObsBrowserAutoRefresh"; | ||
static string _locale = DefaultLocale; | ||
static unsafe obs_module* _obsModule = null; | ||
public static unsafe obs_module* ObsModule { get => _obsModule; } | ||
static unsafe text_lookup* _textLookupModule = null; | ||
|
||
#region Helper methods | ||
public static unsafe void Log(string text, ObsLogLevel logLevel) | ||
{ | ||
if (DebugLog && (logLevel == ObsLogLevel.Debug)) | ||
logLevel = ObsLogLevel.Info; | ||
// need to escape %, otherwise they are treated as format items, but since we provide null as arguments list this crashes OBS | ||
fixed (byte* logMessagePtr = Encoding.UTF8.GetBytes("[" + ModuleName + "] " + text.Replace("%", "%%"))) | ||
ObsBase.blogva((int)logLevel, (sbyte*)logMessagePtr, null); | ||
} | ||
|
||
public static unsafe byte[] ObsText(string identifier, params object[] args) | ||
{ | ||
return Encoding.UTF8.GetBytes(string.Format(ObsTextString(identifier), args)); | ||
} | ||
|
||
public static unsafe byte[] ObsText(string identifier) | ||
{ | ||
return Encoding.UTF8.GetBytes(ObsTextString(identifier)); | ||
} | ||
|
||
public static unsafe string ObsTextString(string identifier, params object[] args) | ||
{ | ||
return string.Format(ObsTextString(identifier), args); | ||
} | ||
|
||
public static unsafe string ObsTextString(string identifier) | ||
{ | ||
fixed (byte* lookupVal = Encoding.UTF8.GetBytes(identifier)) | ||
{ | ||
sbyte* lookupResult = null; | ||
ObsTextLookup.text_lookup_getstr(_textLookupModule, (sbyte*)lookupVal, &lookupResult); | ||
var resultString = Marshal.PtrToStringUTF8((IntPtr)lookupResult); | ||
if (string.IsNullOrEmpty(resultString)) | ||
return "<MissingLocale:" + _locale + ":" + identifier + ">"; | ||
else | ||
return resultString; | ||
} | ||
} | ||
#endregion Helper methods | ||
|
||
#region OBS module API methods | ||
[UnmanagedCallersOnly(EntryPoint = "obs_module_set_pointer", CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void obs_module_set_pointer(obs_module* obsModulePointer) | ||
{ | ||
Log("obs_module_set_pointer called", ObsLogLevel.Debug); | ||
ModuleName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name!; | ||
_obsModule = obsModulePointer; | ||
} | ||
|
||
[UnmanagedCallersOnly(EntryPoint = "obs_module_ver", CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static uint obs_module_ver() | ||
{ | ||
var major = (uint)Obs.Version.Major; | ||
var minor = (uint)Obs.Version.Minor; | ||
var patch = (uint)Obs.Version.Build; | ||
var version = (major << 24) | (minor << 16) | patch; | ||
return version; | ||
} | ||
|
||
[UnmanagedCallersOnly(EntryPoint = "obs_module_load", CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe bool obs_module_load() | ||
{ | ||
Log("Loading...", ObsLogLevel.Debug); | ||
|
||
BrowserFilter.Register(); | ||
var assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName(); | ||
Version version = assemblyName.Version!; | ||
Log("Version " + version.Major + "." + version.Minor + " loaded.", ObsLogLevel.Info); | ||
return true; | ||
} | ||
|
||
[UnmanagedCallersOnly(EntryPoint = "obs_module_unload", CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void obs_module_unload() | ||
{ | ||
Log("obs_module_unload called", ObsLogLevel.Debug); | ||
} | ||
|
||
[UnmanagedCallersOnly(EntryPoint = "obs_module_set_locale", CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void obs_module_set_locale(char* locale) | ||
{ | ||
Log("obs_module_set_locale called", ObsLogLevel.Debug); | ||
var localeString = Marshal.PtrToStringUTF8((IntPtr)locale); | ||
if (!string.IsNullOrEmpty(localeString)) | ||
{ | ||
_locale = localeString; | ||
Log("Locale is set to: " + _locale, ObsLogLevel.Debug); | ||
} | ||
if (_textLookupModule != null) | ||
ObsTextLookup.text_lookup_destroy(_textLookupModule); | ||
fixed (byte* defaultLocale = Encoding.UTF8.GetBytes(DefaultLocale), currentLocale = Encoding.UTF8.GetBytes(_locale)) | ||
_textLookupModule = Obs.obs_module_load_locale(_obsModule, (sbyte*)defaultLocale, (sbyte*)currentLocale); | ||
} | ||
|
||
[UnmanagedCallersOnly(EntryPoint = "obs_module_free_locale", CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })] | ||
public static unsafe void obs_module_free_locale() | ||
{ | ||
Log("obs_module_free_locale called", ObsLogLevel.Debug); | ||
if (_textLookupModule != null) | ||
ObsTextLookup.text_lookup_destroy(_textLookupModule); | ||
_textLookupModule = null; | ||
} | ||
#endregion OBS module API methods | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dotnet publish -c Release -o publish -r win-x64 /p:NativeLib=Shared /p:SelfContained=true | ||
pause |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
IntervalCaption="⏱️ Aktualisierungsintervall (Sekunden)" | ||
IntervalText="Das Intervall in Sekunden, in dem die Browser-Quelle automatisch aktualisiert werden soll." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
IntervalCaption="⏱️ Refresh interval (seconds)" | ||
IntervalText="The interval in seconds in which the browser source should be auto-refreshed." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net7.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | ||
<PublishAot>true</PublishAot> | ||
<Authors>YorVeX (yorvex@yorvex.tv, @YorVeX)</Authors> | ||
<AssemblyVersion>0.1.0.0</AssemblyVersion> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="NetObsBindings" Version="0.0.1.29-alpha" /> | ||
</ItemGroup> | ||
|
||
</Project> |