Jump to content

Recommended Posts

Posted

Hi guys,

First of all thank you for this wonderful SDK, it's a masterclass in dependency injection. It's a beautiful thing.

However, I can't work out how to stop my channel caching everything at once. I'm using IHasChangeEvent, which when triggered calls IChannel.GetChannelItems and caches everything at once, rather than calling IChannel.GetChannelItem as the user navigates the structure. What am I doing wrong?

Thanks

Posted
Quote

channel caching everything at once

Hi, what exactly do you mean by this?

Posted
7 minutes ago, Luke said:

Hi, what exactly do you mean by this?

I'm creating channel items for categories, which are ChannelItemType.Folder, and channel items for remote videos in those categories. It's works well. But upon content change, IChannel.GetChannelItems is called immediately. My IChannel.GetChannelItems gets the video paths and data from the remote, which takes a long while if it's all done immediately and not on demand as the channel is navigated.

Posted

Have your channel implement IHasChangeEvent and trigger the ContentChanged event.

Posted
7 minutes ago, Luke said:

Have your channel implement IHasChangeEvent and trigger the ContentChanged event.

As mentioned I have already implemented the interface and ContentChanged is triggered. But that is what calls GetChannelItems. I just want GetChannelItems to be called when the user navigates an item. Not for hundreds of thousands of items, immediately and recursively, before they're navigated to. The method GetChannelItems first creates some top level category folders, and before the user navigates those folders, GetChannelItems is called again to create the items within those folders.  I probably don't understand what's going on, any help is appreciated.

Posted
Quote

I just want GetChannelItems to be called when the user navigates an item.

That's not how channels work, sorry. Everything gets preloaded into the server database in order to use all of emby's features.

Posted

We actually used to do this a long time ago. When we fetch on the fly, it essentially turns us into a front-end for that content. Now you can argue whether that's good or not, but it causes a lot of Emby features to not be possible. This results in lots of buttons in the UI that don't work - unless we build some major infrastructure to allow the server to inform the UI of everything that is supported. That's a bit of an undertaking. It's doable, but personal media is our core focus area.

Additionally, and just as importantly, when the server hosting the content is responding slowly or not at all, this could cause the Emby UI to stall. Then users come in here and tell us that something is wrong with our software.  So I think things got to a point where we just couldn't take that anymore.

Posted
2 minutes ago, Luke said:

We actually used to do this a long time ago. When we fetch on the fly, it essentially turns us into a front-end for that content. Now you can argue whether that's good or not, but it causes a lot of Emby features to not be possible. This results in lots of buttons in the UI that don't work - unless we build some major infrastructure to allow the server to inform the UI of everything that is supported. That's a bit of an undertaking. It's doable, but personal media is our core focus area.

Additionally, and just as importantly, when the server hosting the content is responding slowly or not at all, this could cause the Emby UI to stall. Then users come in here and tell us that something is wrong with our software.  So I think things got to a point where we just couldn't take that anymore.

Yeah that's fair enough :) It does makes very large remote libraries difficult tho; it can take a long time to populate, some items don't appear for hours, updating content is no trivial thing so it has to be done on an interval, leaving dead items until it's all updated again. Granted this is an edge case and I'm trying to fit a square peg in a round hole! 😄 If I were to create multiple channels, can emby update them in parallel? Is it possible to hide channels from the users? I could have multiple hidden channels update in parallel and have a master channel pull from them. Or is this insane?

Posted
Quote

If I were to create multiple channels, can emby update them in parallel?

Yes.

Posted
Quote

Is it possible to hide channels from the users?

Only using normal channel access configured by the admin in user permissions. There is no way for the channel to force it.

Posted
1 hour ago, nack said:

It does makes very large remote libraries difficult tho

Not that much. We have an upcoming channel plugin which can add tens-of-thousands of items without problems.

If your remote queries are taking a long time, I would recommend to not do this from a call to GetChannelItems. 

Instead:

  • Create your own scheduled task
  • Cache the data that you retrieve locally in some way
  • If it takes very long to acquire the data, prepare for interruption (e.g. Emby Server restart) and resuming retrieval
  • When all data is complete, trigger the event and let Emby Server retrieve all the data from your local cache, so that the whole retrieval is a relatively quick operation

This gives you a much greate flexibility. You can decide which parts of the data to retrieve more frequently or  less frequently and it allows you to do targeted and punctual updates (e.g. for removing stale items) and get those reflected in Emby Server quickly.

Also make sure that the IDs you are assigning to items are persistent and don't change across updates.

Posted
3 minutes ago, softworkz said:

Not that much. We have an upcoming channel plugin which can add tens-of-thousands of items without problems.

If your remote queries are taking a long time, I would recommend to not do this from a call to GetChannelItems. 

Instead:

  • Create your own scheduled task
  • Cache the data that you retrieve locally in some way
  • If it takes very long to acquire the data, prepare for interruption (e.g. Emby Server restart) and resuming retrieval
  • When all data is complete, trigger the event and let Emby Server retrieve all the data from your local cache, so that the whole retrieval is a relatively quick operation

This gives you a much greate flexibility. You can decide which parts of the data to retrieve more frequently or  less frequently and it allows you to do targeted and punctual updates (e.g. for removing stale items) and get those reflected in Emby Server quickly.

Also make sure that the IDs you are assigning to items are persistent and don't change across updates.

Thank you for the advice! This is actually how I did it originally, but I had some problems with the path of the remote media, here last year :

So I ended up having to cache everything and place the paths within local strm files. This seems fixed now though., so I can probably cache the metadata but not have to use strm files.

  • Thanks 1
Posted
21 minutes ago, nack said:

Thank you for the advice! This is actually how I did it originally, but I had some problems with the path of the remote media, here last year 

It's also possible to create a Emby SDK Reference: IMediaSourceProvider for providing the URLs if the need to be consrtructed dynamically.

I have an example somewhere.

Posted

Here's a function for adding mediasource information in a sane way:

 

        private static void AddMediaSourceInfo(ChannelItemInfo channelItem, string path, string container)
        {
            MediaProtocol protocol = MediaProtocol.File;
            if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
            {
                protocol = MediaProtocol.Http;
            }
            else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
            {
                protocol = MediaProtocol.Rtmp;
            }
            else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
            {
                protocol = MediaProtocol.Rtsp;
            }
            else if (path.StartsWith("udp", StringComparison.OrdinalIgnoreCase))
            {
                protocol = MediaProtocol.Udp;
            }
            else if (path.StartsWith("rtp", StringComparison.OrdinalIgnoreCase))
            {
                protocol = MediaProtocol.Rtmp;
            }

            var httpHeaders = new Dictionary<string, string>();

            if (protocol == MediaProtocol.Http)
            {
                httpHeaders["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.85 Safari/537.36";
            }

            var streams = new List<MediaStream>();

            if (channelItem.MediaType == ChannelMediaType.Video)
            {
                streams.Add(
                    new MediaStream
                    {
                        Type = MediaStreamType.Video,

                        // Set the index to -1 because we don't know the exact index of the video stream within the container
                        Index = -1,
                    });
            }
            else
            {
                streams.Add(
                    new MediaStream
                    {
                        Type = MediaStreamType.Audio,

                        // Set the index to -1 because we don't know the exact index of the audio stream within the container
                        Index = -1,
                        Channels = 2,
                    });
            }

            var mediaSource = new MediaSourceInfo
            {
                Name = channelItem.Name,
                Path = path,
                Protocol = protocol,
                RunTimeTicks = channelItem.RunTimeTicks,
                Container = container,
                MediaStreams = streams,
                RequiresOpening = true,
                RequiresClosing = true,
                RequiresLooping = false,

                Id = GetMD5(path).ToString("N"),
                ////IsInfiniteStream = true,
                IsRemote = true,

                ////SupportsDirectPlay = supportsDirectPlay,

                RequiredHttpHeaders = httpHeaders,
            };

            mediaSource.InferTotalBitrate();

            channelItem.MediaSources = new List<MediaSourceInfo> { mediaSource };
        }

 

Posted (edited)
16 minutes ago, softworkz said:

It's also possible to create a Emby SDK Reference: IMediaSourceProvider for providing the URLs if the need to be consrtructed dynamically.

Actually, the easier way for providing dynamic media source info (almost forgot about it) is to implement Emby SDK Reference: IRequiresMediaInfoCallback in your plugin class.

The code snipped in the post above is best for static URLs.

Edited by softworkz
Posted

Hi again!

So I've got this working over the weekend. Meta data is cached, videos play. The only problem so far is an IO error when downloading subtitles.

2025-04-14 16:19:48.216 Error SubtitleManager: Error searching for subtitles on Open Subtitles
        *** Error Report ***
        Version: 4.8.11.0
        Command line: D:\LocalLibrary\Apps\embyserver-win-x64-4.8.10.0\system\EmbyServer.dll -nointerface
        Operating system: Microsoft Windows 10.0.22631
        Framework: .NET 6.0.36
        OS/Process: x64/x64
        Runtime: D:/LocalLibrary/Apps/embyserver-win-x64-4.8.10.0/system/System.Private.CoreLib.dll
        Processor count: 16
        Data path: D:\LocalLibrary\Apps\embyserver-win-x64-4.8.10.0\programdata
        Application path: D:\LocalLibrary\Apps\embyserver-win-x64-4.8.10.0\system
        System.IO.IOException: System.IO.IOException: The filename, directory name, or volume label syntax is incorrect. : 'D:\LocalLibrary\Apps\embyserver-win-x64-4.8.10.0\system\http:\192.168.1.2\video\1895801.mp4'
           at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
           at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
           at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
           at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize)
           at System.IO.Strategies.FileStreamHelpers.ChooseStrategy(FileStream fileStream, String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, Int64 preallocationSize)
           at System.IO.File.OpenRead(String path)
           at Emby.Server.Implementations.IO.ManagedFileSystem.OpenRead(String path)
           at OpenSubtitles.OpenSubtitleComDownloader.SearchInternal(SubtitleSearchRequest request, Boolean useFilename, OpenSubtitleOptions options, CancellationToken cancellationToken)
           at OpenSubtitles.OpenSubtitleComDownloader.Search(SubtitleSearchRequest request, OpenSubtitleOptions options, CancellationToken cancellationToken)
           at Emby.Providers.Subtitles.SubtitleManager.<>c__DisplayClass17_0.<<SearchSubtitles>b__3>d.MoveNext()
        Source: System.Private.CoreLib
        TargetSite: Microsoft.Win32.SafeHandles.SafeFileHandle CreateFile(System.String, System.IO.FileMode, System.IO.FileAccess, System.IO.FileShare, System.IO.FileOptions)

Here is the meat of the code in GetChannelItems.

                        var item = new ChannelItemInfo();
                        item.ContentType = ChannelMediaContentType.Movie;
                        item.Id = $@"{movie.id}";
                        item.MediaType = ChannelMediaType.Video;
                        item.ParentIndexNumber = 0;
                        item.Name = movie.name;
                        item.Type = ChannelItemType.Media;
                        item.ForceUpdate = KBBLPlugin.Options.ForceUpdate;
                        item.IndexNumber = int.Parse(movie.id);   

                        var mediaSourceInfo = new MediaSourceInfo();
                        mediaSourceInfo.Protocol = MediaProtocol.Http;
                        mediaSourceInfo.Container = movie.ext;
                        mediaSourceInfo.Name = movie.name;
                        mediaSourceInfo.Id = path.GetMD5().ToString("N");
                        mediaSourceInfo.Path = path;
                        mediaSourceInfo.SupportsDirectStream = true;
                        mediaSourceInfo.SupportsDirectPlay = true;
                        mediaSourceInfo.IsRemote = true;
                        mediaSourceInfo.MediaStreams = new List<MediaStream>
                        {
                            new MediaStream
                            {
                                Type = MediaStreamType.Video,
                                Index = -1,
                            },                            
                        };

                        item.MediaSources = new List<MediaSourceInfo>
                        {
                            mediaSourceInfo
                        };

                        mediaSourceInfo.InferTotalBitrate();					
                        items.Add(item);

Thanks for looking if you get the time, guys :)

Cheers.

Posted

bad url: http:\192.168.1.2\video\1895801.mp4

Posted
1 minute ago, Luke said:

bad url: http:\192.168.1.2\video\1895801.mp4

The url works when playing the video. Emby is trying something weird with it, combining it with the local path of the system folder?

"D:\LocalLibrary\Apps\embyserver-win-x64-4.8.10.0\system\http:\192.168.1.2\video\1895801.mp4"

Posted

Bit more info, the path fed to emby is "http:\\192.168.1.2\video\1895801.mp4".

The path the subtitle manager is attempting to save user downloaded subtitles looks like it's that path escaped and appended to the emby system path.

I have also:

ChannelItemInfo.IsRemote = true

ChannelFeatures.LibraryOptions.SaveSubtitlesWithMedia = false,

Posted

Correction, the URL is "http://192.168.1.2/video/1895796.mkv". Apologies.

Posted

Right it seemed like your slashes were reversed.

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...