writing my first plex channel

Hi All,

 

I have a few plugins that I've wrote for XBMC, Roku3 and would like to port it to plex for a friend.  So far, I've gotten the hang of things, and I have it working.  However, I am not satisfied with it, meaning alot can be improved in terms of performance.

 

1.  I have a function that creates the MainMenu, which add the DirectoryObjects with the key=Callback to the function below

2. here is the gut of it

@route(PREFIX + '/collections/{collection_id}/contents', collection_id = int)
def ContentsForCollection(collection_name, collection_id):
    requestUrl = BASE_REQUEST_URL + "/collections/%d/contents" %collection_id
    oc = ObjectContainer(title1=collection_name)
    contents = JSON.ObjectFromURL(requestUrl + "?$include=[Id,Name,Desc,Images,ContentPackages]")
    for content in contents['Items']:
        try: thumbnail = content['Images'][0]['Url']
        except: thumbnail = ''
        oc.add(VideoClipObject(
            url = requestUrl + "/%d/contentpackages/%d/stacks" %(content['Id'], content['ContentPackages'][0]['Id']),
            title=content['Name'],
            summary=content['Desc'],
            thumb=Resource.ContentsOfURLWithFallback(url=thumbnail, fallback=ICON)
        ))
    if len(oc) < 1:
        return ObjectContainer(header="Empty", message="No contents found for " + collection_name)
    return oc

and here is the URLService code

def MetadataObjectForURL(url):
    Log.Debug(">>>MetadataObjectForURL: " + url)
    return VideoClipObject()

def MediaObjectsForURL(url):
Log.Debug(">>>MediaObjectsForURL: " + url)

stacks = JSON.ObjectFromURL(url)
parts = []
for stack in stacks['Items']:
    parts.append(PartObject(key=Callback(PlayVideo, url=url + "/%d/manifest.m3u8" %stack['Id'])))

return [
    MediaObject(
        protocol = 'hls',
        parts = parts
    )
]

@indirect
def PlayVideo(url):
return IndirectResponse(VideoClipObject, key=HTTPLiveStreamURL(url))

While this work, here are some of the problems I am encountering;

1.  From the main menu, if I click a directory and if that directory has over 50+ videos, the plugin crash.  and it's due to my noobness --- MediaObjectsForURL makes http calls.  this also causes a performance issue, as each item is added to the directory, a call is made to this function.  What I want to do is only retrieve it when requested ie, user click on it.

2. MetadataObjectForURL - doesn't seem to get called as I don't see the occurrence of that debug line any where in the log file - by not executing I believe this causes a thrid and final problem

3. Clicking on a video, you'll be presented with a screen and it's very empty, only has a play button.

 

Now here's the reason why I made an http call in MediaObjectForURL - it's because, the stream to the video is broken up into parts.  for example, an 30min show will have 3 part of 10 mins each.  I want to play it consecutively.  Where would be a good place to grab the parts, would PlayVideo work if so, what can I call to add it?

 

Any tips on how to improve the performance is greatly appreciated.!

 

Thanks,

Welcome to the wonderful world of Plex channel development.

In regards to your issues:

  1. The app crash is likely due to the number of http requests being made and the time required for them all to return. Obviously a crash is bad but it should be corrected by restructuring your data parsing. Adding some paging can also improve load times but that's secondary to removing HTTP calls from the MediaObjectsForURL() method. If you know that there are usually 3 parts per MediaObject, then restructuring things will be pretty easy. Take a look at the URL Service for The Daily Show. You'll see that there are multiple parts listed in each MediaObject but the actual processing of the individual parts happens in the PlayVideo() method according to the "index" passed from the MediaObject.
  2. The MetadataObjectForURL() method should be getting called when the pre-play screen is loaded. The logging for it may not be in the plugin log but rather the com.plexapp.system.log.
  3. You're returning an empty VideoClipObject without any actual metadata. You need to provide the metadata here for it to display in the pre-play screen.

P.S. I'm moving this topic to the Channel Delevopment forum.

Mike,

Thanks for the reply - most of my problems were self inflicted :D  After reading the API, I just wanted to get it working, once working refactor it and improve it.

#2 & 3 - i left it blank because i didn't know when it would be called and i was looking in the plugin's log file.  thanks for pointing out the log file.  This should be easy to solve now that i know when it's being called.

1. I found the NextPageObject() which I will use to page ie, "Load More..." to add more items to the list.  So it would solve the pagination issue.  Also, I do not know how many parts there would be for a given video.  So i would need to make a rest call to retrieve it and construct the parts object.

The flow is like so:

1. retrieve main menu - the rest api will return a list of id and names.  This are things line (All Shows, Latest, News, Sports, etc...)

2. use the id above to find out it's content types, it could be one of 2 - a video type or a media type (media type is analogous to a season object)

3. make appropriate call with the id retrieved in step 1 and 2 to get a list of videos

4. each video will need another call to get the number of segments and its' id

5. full path to the m3u8 is from the id retrieved in the previous steps

with this, I am wondering if the URLservice would hinder performance than improving it.

Bonus questions - suppose when I add a VideoClipObject

        oc.add(VideoClipObject(
            key=Callback(GenerateMetadata(some_url)),
            rating_key=url,
            duration=duration,
            title=title,
            summary=summary,
            thumb=thumb,
            items=Callback(CreateMediaParts(some_url))
        ))

GenerateMetadata(some_url) would return an ObjectContainer with a VideClipObject with its metadata published.

CreateMediaParts(some_url) would return a list of MediaObjects with all the parts added, each PartObject added will have a callback to PlayVideo that takes in a URL

Given the callbacks added, would it this be the correct sequence of execution?

1. VideoClipObject added, no call back executed

2. User click item, GenerateMetadata is executed and the pre-play screen is shown

3. User click Play, CreateMediaParts is called

4. System does its thing, for each PartObject it will callback PlayVideo

Thanks,

So I have rewritten the plugin not to use a URLService.  Everything so far is fine without the pagination (which i'll add shortly) but now the video won't play when i click play.

I am sure it is something trivial that i am not doing correctly.

@route(PREFIX + '/contents')
def Contents(title, target):
    Log.Debug(">>>Contents for " + target)
    oc = ObjectContainer(title1=title)
    contents = JSON.ObjectFromURL(target + "?$include=[Id,Name,Desc,Images,ContentPackages]", cacheTime=CACHE_1HOUR)
    for content in contents['Items']:
        try: thumb = content['Images'][0]['Url']
        except: thumb = ''
        title = content['Name']
        summary = content['Desc']
        duration = int(content['ContentPackages'][0]['Duration'])
        package_id = content['ContentPackages'][0]['Id']
        stacks_url = target + "/%d/contentpackages/%d/stacks" %(content['Id'], package_id)
        oc.add(VideoClipObject(
            key=Callback(GenerateMetadata, content=content, rating_key=stacks_url),
            rating_key=stacks_url,
            duration=duration,
            title=title,
            summary=summary,
            thumb=thumb,
            items=CreateMediaParts(stacks_url)
        ))
if len(oc) < 1:
    return ObjectContainer(header="Empty", message="No contents available.")
return oc

@route(PREFIX + “/metadata”, content=dict)
def GenerateMetadata(content, rating_key):
Log.Debug(">>>GenerateMetadata")
try: thumb = content[‘Images’][0][‘Url’]
except: thumb = ‘’
title = content[‘Name’]
summary = content[‘Desc’]
duration = int(content[‘ContentPackages’][0][‘Duration’])
return VideoClipObject(
thumb=thumb,
title=title,
summary=summary,
duration=duration,
key=rating_key,
rating_key=rating_key
)

def CreateMediaParts(target):
Log.Debug(">>>CreateMediaParts for " + target)
stacks = JSON.ObjectFromURL(target, cacheTime=CACHE_1DAY * 7)
parts = []
for stack in stacks[‘Items’]:
parts.append(PartObject(key=Callback(PlayVideo, target=target + “/%d/manifest.m3u8” % stack[‘Id’])))
return [
MediaObject(
protocol=‘hls’,
parts=parts
)
]

@indirect
def PlayVideo(target):
Log.Debug(">>>PlayVideo " + target)
return IndirectResponse(VideoClipObject, key=target)

the PlayVideo method isn't being called back.  There doesn't seem to be any errors in the plugin log and the system log.  All that I see is 

2014-01-17 19:55:37,305 (14ec) :  DEBUG (logkit:13) - >>>GenerateMetadata
2014-01-17 19:55:37,308 (14ec) :  DEBUG (runtime:915) - Response: [200] VideoClipObject, 719 bytes
 
which suggests to me the GenerateMetadata is returning a VideoClipObject. 
 
Please help me spot my error.
 
Thanks

Take a look at how the Jerry Seinfeld channel is laid out to work without an URL Service.

cheers mikedm139,


I’ll release some of these channels when I am done as an appreciation for all the help :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.