vdb86 5 Posted 4 hours ago Posted 4 hours ago Hi Emby team, First, thanks for Emby. I run it as the media hub for my living-room HTPC and it has been rock solid. ## What I am trying to solve On a dedicated HTPC, I want the Emby client to get out of the way when I am not actively watching, and come back when I am: - Start with windows minimized to tray - so that I can use my phone to select the htpc as play on target. - Pause playback -> hide the client to the system tray. - Resume -> bring it back to the foreground. - Playback starts -> show it; playback stops/ends -> hide it. This is a really nice "appliance" experience on a TV-connected PC: the client is there when you need it and invisible when you do not, without ever landing on the desktop or taskbar. ## What I built as a stopgap Because I could not do this from within Emby, I wrote a small standalone Windows tray agent, "Emby Tray": https://github.com/vdb86/Emby-Tray It polls the server's `GET /Sessions` endpoint, filters to this machine's own session (by local network address / client name / device name), reads `PlayState.IsPaused` and the now-playing item, and then uses Win32 (`EnumWindows` + `ShowWindow`) to hide/restore the client window. It runs entirely outside the client install so client updates never touch it. It works well, but it is fundamentally a workaround, and I would much rather this behavior lived in Emby itself so nobody needs a side-car process. ## Why this cannot be done as a normal plugin today I looked hard at doing this "properly" first, and hit a wall that I think only you can remove: 1. A server-side catalog plugin can subscribe to `ISessionManager` playback events and read `PlayState.IsPaused`, so it can perfectly DETECT pause/resume. But it runs on the server and has no way to touch a client's OS window or tray. 2. A client-side (Theater web/JS) plugin knows the play/pause state directly via the web `playbackManager` events, but it runs in the web sandbox and cannot reach the OS tray or minimize the native window. 3. The server-to-client `GeneralCommand` path is the natural bridge, but `GeneralCommandType` has no command for this. It has `ToggleFullscreen`, `SetVolume`, `Mute`, `GoHome`, `DisplayMessage`, `SendKey`, `SendString`, and so on, but nothing like `MinimizeToTray` / `RestoreWindow` / `Minimize`. So detection is fully supported, but the "hide/restore the native window" half is not exposed to any plugin surface. That gap is why an external agent is currently the only option. ## What would be needed from your end Any one of these would let this be done natively (roughly in order of how self-contained they are): 1. Best: build it into the desktop client directly. Add a tray icon plus a "minimize to tray on pause / restore on resume" option (and a "start minimized to tray" option) in the client's settings. Nothing external needed, and it would be a great HTPC feature out of the box. 2. Or: add `GeneralCommandType` values such as `MinimizeToTray` / `RestoreWindow` (and ideally `Minimize` / `Restore`) that the desktop client honors. Then even a small server plugin could drive the behavior on pause/resume. 3. Or: expose a minimal NativeShell API to Theater client plugins (for example `nativeShell.window.hideToTray()` / `show()`), so a client plugin reacting to `playbackManager` pause/unpause events could do the window/tray control itself. Option 1 is what I would love most, since it removes the need for any plugin at all. Options 2 or 3 would let the community build it cleanly. ## Offer The full working implementation, including the session-matching logic and the Win32 hide/restore handling, is open source at the link above under GPLv3, so please feel free to reference it for the detection/behavior model. Happy to answer questions, test builds, or help however is useful. Thanks for considering it. P.s. I'm also open to trying to help with optimizing the windows app code (start faster, etc.)
softworkz 5336 Posted 2 hours ago Posted 2 hours ago @vdb86 Thanks for your request. This is an interesting use case, but - to be honest - it's also pretty special. Likely too special to become a built-in feature. A GeneralCommandType which allow to remotely control the window state of an app - phhm - I'm not sure. Regarding "native shell": The web ui code that is shipped with apps is not meant to be touched or manipulated. You might have seen this in the Windows app already. And what's sttill working today, might no longer in the near future. So, what then? First of all, the way you have done it is the best you can get at the moment. I would do something similar if I had to. Future Now - there is a way how this can be achieved in a way that is crazily simple: Client Plugins (.net) The Windows app belongs to a new range of client apps which are based on .net and x-plat - named Emby.Client. There is a plugin model, and some other concepts (like JS- <> net interop, service model, x-plat feature among other things). A plugin with this code would already do what you want (roughtly): DISCLAIMER: Just for Illustratration - custom plugins are not supported atm public class AutoHidePlugin : HostPluginBase { private ICurrentPlayer currentPlayer; private IFeatureSetWindowState featureWindowSTate; public AutoHidePlugin(IServiceRoot serviceRoot) : base(serviceRoot) { } public override string Id => "autohideplugin"; public override string Name => "AutiHide Plugin"; public override PluginTypes Type => PluginTypes.Unknown; public override async Task OnAppReady() { this.currentPlayer = await this.Require<ICurrentPlayer>().ConfigureAwait(false); this.featureWindowSTate = this.GetService<IFeatureSetWindowState>(); //// TODO: Handle currentplayer/feauture unavailable this.currentPlayer.ModuleEvent += this.CurrentPlayer_OnModuleEvent; this.currentPlayer.RegisterModuleEvent(@"playbackstart"); this.currentPlayer.RegisterModuleEvent(@"playbackstop"); this.currentPlayer.RegisterModuleEvent(@"pause"); this.currentPlayer.RegisterModuleEvent(@"unpause"); this.currentPlayer.RegisterModuleEvent(@"statechange"); } private void CurrentPlayer_OnModuleEvent(object sender, JsModuleEventArgs e) { var playState = e.GetArgument<PlayStateInfo>(); if (playState != null) { if (playState.PlayState?.IsPaused != null && !playState.PlayState.IsPaused.Value) { this.featureWindowSTate.SetWindowState(WindowStates.Maximized); } else { this.featureWindowSTate.SetWindowState(WindowStates.Minimized); } } } } While that plugin model exists and is actually being used, it is not open for 3rtd party plugins at the moment. No decision has been made so far about whether we might open-up those apps to 3td party developers. There's a large amount of issues, consequences and caveats to consider and unfortunately we haven't come to the right point for this.. Clearly, it has a lot of appeal - the code above Eorks on all Emby.Client app (whch have the SetWindowState feature..
vdb86 5 Posted 47 minutes ago Author Posted 47 minutes ago Thank you very much for your time and detailed response, I really appreciate it. I'll keep using my app and we'll see if you guys open up the plugin model for 3rd party plugins. It might be worth while considering a specific case: it's not open for complex 3rd party plugins, only the ones that are simple and don't need to be updated much. That way reviewing the plugins becomes relatively easy and inexpensive while not having to review a bunch of future updates. That reduces he risk etc, and while I'm writing this I have a feeling you guys already considered that as well. Thank you again, and thank you for Emby. 1
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now