DimitarCC 20 Posted December 8, 2024 Posted December 8, 2024 Hello, I was struggling with plugin development recently but i hit the brick wall since i couldn't get the information i needed. Or at least i don't know where to read.... So, i have a few questions... 1. Where i can read about the SDK? What functions are in there, what interfaces can be used and so on? 2. What are the requirements for the plugin to work on Emby? 3. What standard .NET libraries are included in Emby server installation? 4. Can plugins that modify UI of the clients be done or not? 5. How can the data be scrapped with a particular scrapper (when there are multiple scrappers enabled)? Or at least how can priority be given to a particular scrapper? Maybe some other questions will come to my mind but for now that's all. Thanks in advance.
Solution softworkz 4423 Posted December 8, 2024 Solution Posted December 8, 2024 (edited) 1 hour ago, DimitarCC said: 1. Where i can read about the SDK? What functions are in there, what interfaces can be used and so on? All the documentation is here: https://dev.emby.media/ There's a separate beta site: https://betadev.emby.media/ At the right top, you can see the version of the server to which it corresponds. You can switch between the APIs versions by choosing different versions of the nuget package: https://www.nuget.org/packages/MediaBrowser.Server.Core/ Interfaces that you can implement in your plugin to provide certain functionality are here. https://dev.emby.media/doc/plugins/dev/Automatic-Type-Discovery.html (not fully complete, though) Plugins are very powerful, which means that they can use almost all the components of Emby Server, you just need to find the right interface in the Reference Documentation for plugins and then you can get an instance of it either via constructor injection (in your plugin class or in any class implementing one of the "interfaces you can implement". You can also call Emby SDK Reference: IApplicationHost.Resolve<T>() to get instances. If you need some specific functionality just ask, as it's probably a bit overwhelming. Essential interfaces are Emby SDK Reference: IServerApplicationHost, Emby SDK Reference: ILogManager, Emby SDK Reference: IJsonSerialize, Emby SDK Reference: IServerApplicationPaths, Emby SDK Reference: IHttpClient, Emby SDK Reference: ILibraryManager, Emby SDK Reference: IProcessManager - just from the top of my head. 1 hour ago, DimitarCC said: 2. What are the requirements for the plugin to work on Emby? 3. What standard .NET libraries are included in Emby server installation? Ideally, start with one of the templates or example plugins. You must target netstandard2.0 and it needs to be a single dll, so it can't have any dependencies other than what EmbyServer has included, which you can see from the assemblies in a server installation. Be careful about versions, lower than what Emby includes is fine, but not higher. And you need to look at the stable server for that, not the beta server. Yet I've created many plugins of different kinds and almost never needed to include any extra dependencies. There's a complicated way to have dependencies by including them as resource and loading them in a module initializer. I've done it once, but it should be gnerally avoided. If the dependency is open source, it's eeasier to include its source code directly in your plugin. 1 hour ago, DimitarCC said: 4. Can plugins that modify UI of the clients be done or not? No, this is not possible - for a number of reasons, but I'll name just two: For once, not all clients are html/js based, so that wouldn't work in all clients. Second is that even if we would allow this for html/js based clients, it would be no fun for anybody. Your UI extension would break every 6 months as the code is constantly changing, and not all clients are always at the same version, so you would need to accommodate for this and also know exactly in which version this and in which version another implementation would be required. Also, we do not know what your plugin does, so it would always break first and then you need to run behind and fix it, but it would always be broken for some time. In the end, the users would be hating the plugin developer and the plugin developers would be hating us and we would be hating ourselves for allowing this - so there's no happy end in this story. There have been ideas for allowing to do some UI extensions via plugin UI, but that would require a lot of work to make it possible and we haven't seen a compelling use case for that so far. 1 hour ago, DimitarCC said: How can the data be scrapped with a particular scrapper (when there are multiple scrappers enabled)? Or at least how can priority be given to a particular scrapper? We don''t have "scrapers". We have Emby SDK Reference: MetadataProviders and you can implement custom ones in a plugin by deriving from Emby SDK Reference: IRemoteMetadataProvider, Emby SDK Reference: ILocalMetadataProvider or Emby SDK Reference: ICustomMetadataProvider. Users can choose and re-order them in the library configuration dialog: Edited December 8, 2024 by softworkz 1
DimitarCC 20 Posted December 8, 2024 Author Posted December 8, 2024 Thanks a lot for the explanation! I am very pleased that Emby team are so open to discussions with side developers and Emby users. That is not the same with your competitors so i am glad i moved in. So now a bit technical stuff.... 58 minutes ago, softworkz said: There's a complicated way to have dependencies by including them as resource and loading them in a module initializer. I've done it once, but it should be gnerally avoided. If the dependency is open source, it's easier to include its source code directly in your plugin. About that...Well I got a bit lost with IHttpClient...I coudnt figure out how to add a proxy support to it nor cache functionality, cookies and so on. So i had to use another .NET library HttpClient.Caching with customized HttpClient which however requires Newtonsoft.Json version 13...But Emby server complains that it can not find version 13 of it. So now i am not sure which version is there and what to do if that is really required for the library to work. Otherwise, i managed to embed the dependencies already with ILRepack tool. Thanks again for all help that you provide. I am very very grateful!!!!! 1
softworkz 4423 Posted December 8, 2024 Posted December 8, 2024 11 hours ago, DimitarCC said: So now i am not sure which version is there and what to do if that is really required for the library to work. Otherwise, i managed to embed the dependencies already with ILRepack tool. Sorry, I shouldn't have even mentioned that route, as so I have accidentally created the impression that the problem would be the embedding. I was a bit too lazy and stopped prematurely. The right answer regarding embedding of dependencies is: don't do it, it's a doomed path and even when it all looks good initially, it may take up until you're all done that you realize that you need to re-do substantial parts of your plugin. So, some of the real problems are these: What happens when your plugin X embeds libA and there's also another plugin Y which embeds libA? In a single process (more precisely AppDomain), it is not possible to have two or more implementations of the same type. So when one of the plugins gets loaded, it also loads libA, but when the other one gets loaded and tries to load libA, it fails and loader exceptions cannot be caught and remedied Since embedded libs are loaded from a byte stream, the usual loader mechanism do not work (i.e. "assembly with path p is loaded already"). There are several things that work differently and overall, you just can't handle it properly Also you don't know what other plugin developers might have implemented this, there are no rules or negotation for it. And anyway it's unlikely that two plugins would have embedded the exact same version of a lib, and in the end this makes countless points of potential breakage and it will breake more often than it works You could do assembly prefixing, but this doesn't work when you have two or more dependency libs where one depends on the other. Ultimately you could create a separate AppDomain and load your dependencies there, but then you'll have to deal so much with bridging in and out, that it would draw off all the fun from developing a plugin (plus otrher drawbacks). Before you look up assembly prefixing and AppDomains - please don't. These aren't meant to be suggestion and we're also not even done yet, as there's more: By using the built-in functionality, you are using implementations which are the result of work and experience of more than a decade. When you use "some lib" with "some json serializer", then you'll have to go through it the hard way and solve all the things yourself which we have solved long time ago: For example, we have special and different implementations of Emby SDK Reference: IHttpClient for platforms, e.g for Android and IIRC also when running on Mono We use ServiceStack serializaton everywhere, but with some adjustments that are important when deailing with information about movies and tv in metadata providers [enough for now] Bottom line is: use Emby implementations rather than external ones, this will allow you to create solid and reliable plugins without bad surprises and without needing to solve problems that we have solved long before. For the use of Emby SDK Reference: IHttpClient, just take a look at those plugins (and other public ones on our GitHub site): https://github.com/MediaBrowser/Emby.Plugins.AniSearch https://github.com/MediaBrowser/Emby.Plugins.MyAnimeList https://github.com/MediaBrowser/Emby.Plugins.Proxer https://github.com/MediaBrowser/Emby.Plugins.Anime Caching et al. can be configured with the Emby SDK Reference: HttpRequestOptions class. 1
DimitarCC 20 Posted December 9, 2024 Author Posted December 9, 2024 (edited) 8 hours ago, softworkz said: For the use of Emby SDK Reference: IHttpClient, just take a look at those plugins (and other public ones on our GitHub site): https://github.com/MediaBrowser/Emby.Plugins.AniSearch https://github.com/MediaBrowser/Emby.Plugins.MyAnimeList https://github.com/MediaBrowser/Emby.Plugins.Proxer https://github.com/MediaBrowser/Emby.Plugins.Anime Caching et al. can be configured with the Emby SDK Reference: HttpRequestOptions class. First of all thanks again for the information you have provided. Its very useful for me. So i found an example how to set cookies and configure basic caching with IHttpClient.... But as for the proxy...All examples i found is using basic HttpClient and not IHttpClient interface.... Is that mean there is no proxy capability built in in IHttpClient? P.S. Also isnt it good if as a standard is included libraries like HtmlAgilityPack? In that way will minimize need to embed such a big lubraries and to have compatability issues you have mentioned.. Edited December 9, 2024 by DimitarCC
softworkz 4423 Posted December 9, 2024 Posted December 9, 2024 It should use the system-configured proxy settings but it doesn't expose any custom settings (atm). Why would you need that?
DimitarCC 20 Posted December 9, 2024 Author Posted December 9, 2024 (edited) 9 minutes ago, softworkz said: It should use the system-configured proxy settings but it doesn't expose any custom settings (atm). Why would you need that? Its basically needed for methadata providers that have geo-restrictions for some reason. and that is is basically for the users convenience... The idea is not to specify system-wide proxy but the proxy only for specific methadata provider... Edited December 9, 2024 by DimitarCC
DimitarCC 20 Posted December 10, 2024 Author Posted December 10, 2024 Hello again.... So i dont understand how IExternalId interface works... I have it implemented but it doesnt take the values i enter... how that is passed to the getMethadata or GetSearchResults functions? I am completly confused... @softworkzcan you shed some light on that? Thanks!
softworkz 4423 Posted December 10, 2024 Posted December 10, 2024 Here's an example from the TVMaze plugin: public class TvMazeExternalId : IExternalId { public string Key { get { return MetadataProviders.TvMaze.ToString(); } } public string Name { get { return "TV Maze Series"; } } public string UrlFormatString { get { return "http://www.tvmaze.com/shows/{0}"; } } public bool Supports(IHasProviderIds item) { return item is Series; } }
DimitarCC 20 Posted December 10, 2024 Author Posted December 10, 2024 (edited) OK what are the properties do? Key is for what? I suppose UrlFormatString is how to form the url (where to put the value that comes from the field in metadata search dialog).... But it not works on my side....Maybe because my key is not correct? So what the key should be? And what if that UrlFormatString is api url where its tokenized? Edited December 10, 2024 by DimitarCC
softworkz 4423 Posted December 10, 2024 Posted December 10, 2024 20 minutes ago, DimitarCC said: And what if that UrlFormatString is api url where its tokenized? This is only for public web pages in order to show a link in the apps. If there's no public link, then return null. It does not have any other functionality. 23 minutes ago, DimitarCC said: Maybe because my key is not correct? So what the key should be? The key should be someting unique and indicate where the data comes from, like TvDB, OMVDB, IMVDB, TvMaze. You can use the same key for different item types from the same source, like in case of TvMaze, there are several derivates of providerId (for series, seasons, episodes, persons) and they are all using the same key (TvMaze). To make things match, you need to add your provider key + itemid to the provider ids dictionary to everything you are returning. For example: var season = new Season(); season.ProviderIds[MetadataProviders.TvMaze.ToString()] = mazeSeason.id.ToString("D"); You should install the DataExplorer plugin. Itt's very useful for developing metadata providers.
DimitarCC 20 Posted December 11, 2024 Author Posted December 11, 2024 OK. Thanks for the tip... But what i dont get is how that fields are used. I have one in my provider but entering a value does nothing because is just not used... How that value from that fields reach the search and how to get it so to call the correct endpoint?
softworkz 4423 Posted December 11, 2024 Posted December 11, 2024 Can you please describe what exactly you are aiming to achieve and what you have tried to far?
DimitarCC 20 Posted December 11, 2024 Author Posted December 11, 2024 So my idea is simple.... In methadata searchdialog The red square fields are standard and that is normal search criteria that is used when searching for methadata. The orange square fields are seems to be added from IExternalId interface implementations. So i want to add for my methadata rovider such a field for specific movie id so when i enter an id in that fields and press search (even without name entered) a search to be made regarding that id that i specified in my custom id field (similar to MovieDb Id field). However i can not understand how to use that field value in the code behind... What i have done so far is implement IExternalId and utilize the standard Name and date fields into methadata search (GetMethadata/GetSearchResults). That works but i want to utilize my custom Id from my custom field...
softworkz 4423 Posted December 11, 2024 Posted December 11, 2024 27 minutes ago, Luke said: item.GetProviderId(xxx) There's no item in metadata search
DimitarCC 20 Posted December 11, 2024 Author Posted December 11, 2024 I suppose MovieInfo object can be used instead...
softworkz 4423 Posted December 11, 2024 Posted December 11, 2024 46 minutes ago, DimitarCC said: What i have done so far is implement IExternalId and utilize the standard Name and date fields into methadata search (GetMethadata/GetSearchResults). That works but i want to utilize my custom Id from my custom field... How did you implement the provider? Did you already implement an Emby SDK Reference: IRemoteMetadataProvider ?
DimitarCC 20 Posted December 11, 2024 Author Posted December 11, 2024 1 minute ago, softworkz said: How did you implement the provider? Did you already implement an Emby SDK Reference: IRemoteMetadataProvider ? Yes i have already implemented IRemoteMetadataProvider.. 1 minute ago, softworkz said: Which kind of items is this about? Its movie items.
softworkz 4423 Posted December 11, 2024 Posted December 11, 2024 Let's look at the search from TvMazeSeriesProvider: public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(SeriesInfo searchInfo, CancellationToken cancellationToken) { if (IsValidSeries(searchInfo.ProviderIds)) { var metadata = await this.GetMetadata(searchInfo, cancellationToken).ConfigureAwait(false); if (metadata.HasMetadata) { return new List<RemoteSearchResult> { metadata.ToRemoteSearchResult(TvMazePlugin.ProviderName), }; } } return await this.DataHub.FindSeries(searchInfo.Name, searchInfo.Year, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); } internal static bool IsValidSeries(Dictionary<string, string> seriesProviderIds) { return seriesProviderIds.HasProviderId(MetadataProviders.TvMaze) || seriesProviderIds.HasProviderId(MetadataProviders.Tvdb) || seriesProviderIds.HasProviderId(MetadataProviders.Imdb); } TvMaze supports TvDB and Imdb IDs besides its own IDs, this means that when any of those IDs is present, the identity is already clear and no search needs to be performed. Otherwise it tries to search by name and year (FindSeries). Now, the GetSearchResults() method is only called when the user performs a search manually. In most cases though, the server calls just GetMetadata() on the providers and here the metadata may need to perform a search as well. The difference between the two cases is this: In case of GetSearchResults(), the provider can perform a more "loose" search and return more items, since the results are reviewed by a user who will then make a choice. In case of GetMetadata(), it's an unattended process - there's no user involved and the metadata provider should only use the result of a search if it is really sure that it's the right item Here's the GetMetadata() implementation from the TvMazeSeriesProvider: public async Task<MetadataResult<Series>> GetMetadata(SeriesInfo seriesInfo, CancellationToken cancellationToken) { var result = new MetadataResult<Series>(); if (!IsValidSeries(seriesInfo.ProviderIds)) { if (!seriesInfo.ProviderIds.HasProviderId(MetadataProviders.TvMaze)) { var srch = await this.DataHub.FindSeries(seriesInfo.Name, seriesInfo.Year, seriesInfo.MetadataLanguage, CancellationToken.None) .ConfigureAwait(false); var entry = srch.FirstOrDefault(); if (entry != null) { var id = entry.GetProviderId(MetadataProviders.TvMaze.ToString()); seriesInfo.SetProviderId(MetadataProviders.TvMaze.ToString(), id); } } } cancellationToken.ThrowIfCancellationRequested(); if (IsValidSeries(seriesInfo.ProviderIds)) { var mazeSeries = await this.DataHub.GetMazeSeries(seriesInfo.ProviderIds, seriesInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false); if (mazeSeries != null) { result = this.CreateResult(mazeSeries); result.HasMetadata = true; } } return result; } Note: "DataHub" is a combination of remote apiclient and app-level cache. 1
DimitarCC 20 Posted December 11, 2024 Author Posted December 11, 2024 OK. I think i got it...Will try it and see what will happen. Thanks! 1
softworkz 4423 Posted December 11, 2024 Posted December 11, 2024 29 minutes ago, DimitarCC said: OK. I think i got it...Will try it and see what will happen. Thanks! Do you have debugging set up properly? If you follow these instructions (https://dev.emby.media/doc/plugins/dev/index.html#debugging), you'll be able to change code during runtime (edit & continue => now called "Hot Reload").
DimitarCC 20 Posted December 12, 2024 Author Posted December 12, 2024 Well i am running my test server on Asustor so I cannot use VS debugging. But i plan to install it on my dev machine (Windows) so to be able to do so...
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