Implement Recently Played Items

Hi all,

 

I'm trying to implement the Recently Played Items list for my channel, but apparently I can't use Data API from the shared code to store the history.

 

Are there any alternatives or is this a no-go?

Storing persistent data from within the channel code is allowed. Implement some sort of tracking mechanism for what is being watched won't be straightforward though. If you're using a URL Service (and it sounds like you are), as is the general recommendation, you could get creative in your PlayVideo() function before actually returning the video url.

If you implement the "recently viewed" tracking function in the channel code, something like this:

@route("/video/myplugin/trackplayback/{url}")
def TrackPlayback(url):
  recently_viewed = [String.Decode(url)]
  MAX_LENGTH = 20
  i = 0
  for len(Dict['recently_viewed']):
    recently_viewed = recently_viewed + Dict['recently_viewed']*
    i++
    if len(recently_viewed) >= MAX_LENGTH:
      Dict['recently_viewed'] = recently_viewed
      break
    else:
      pass
  return

Then you can call it from your PlayVideo() function (or anywhere) by cheating and making an HTTP request to the URL on your PMS.

def MediaObjectsForURL(url):
  ...
  ...
  ...

@indirect
def PlayVideo(url):
‘’‘make a call to the tracking function’’’
try:
encoded_url = String.Encode(url)
url_tracked = HTTP.Request(http://localhost:32400/video/myplugin/trackplayback/encoded_url, immediate=True)
except:
‘’‘url tracking failed’’’
Log(“URL not added to recently viewed list”)
#now do whatever needs to be done to return a playable video url
return IndirectResponse(VideoClipObject, key=video_url)

It's not ideal but, it should work (more or less). At some point, it may no longer be necessary. There have been discussions about actually keeping track of view state for channel content for a long time. That being said, there's no ETA for that feature and IMO it's likely still a long ways off.

@Mikedm139

Thank you for the suggestion, I've come up with something very similar. But there are a few issues I haven't yet resolved:

1. How to get the proper URL (at least server hostname, port and path to plugin), because I wouldn't like to hardcode it. I've found out earlier that I can't even pass the function reference to the shared code due to sandbox restrictions, so using Callback object is probably not an option.

2. Whether I can send the request asynchronously (like fire-and-forget) to reduce the delay before play start

  1. It's likely unnecessary to worry about the "proper URL" for the server etc. Since all channel code is executed on the Plex Media Server rather than the individual client, referencing localhost should always work. PMS always listens on port 32400 even though it may assign another port to your plugin when loading. The path the plugin is determined by the Prefix that you define (following the standard) in your plugin. So, really the only thing that needs to be dynamic is the argument that you pass for tracking whether it be an URL or other reference. That being said, the plugin Framework does include methods for getting the IP address of the server.
  2. If the delay from making the extra function call in the PlayVideo() is noticeable slowing things down, you could consider implementing a separate thread. The Framework does support threading although, it is not commonly used. If you decide to go down that road, it might look something like this:
    @indirect
    def PlayVideo(url):
      ''' fire the tracking request in a separate thread '''
      Thread.Create(Played, url=url)
      # do stuff to get your playable video url
      return IndirectResponse(VideoClipObject, key=video_url)
    

    def Played(url):
    track_url = HTTP.Request(tracking_url + String.Encode(url), immediate=True).content
    return


  1. It's likely unnecessary to worry about the "proper URL" for the server etc. Since all channel code is executed on the Plex Media Server rather than the individual client, referencing localhost should always work. PMS always listens on port 32400 even though it may assign another port to your plugin when loading. The path the plugin is determined by the Prefix that you define (following the standard) in your plugin. So, really the only thing that needs to be dynamic is the argument that you pass for tracking whether it be an URL or other reference. That being said, the plugin Framework does include methods for getting the IP address of the server

Of note 32400 is the default port for PMS to listen on, but it can be overridden in the PMS settings still as far as I know, so while it would probably work to assume that for almost all setups there still may be the odd setup where a user has configured a different port for their PMS.

Of note 32400 is the default port for PMS to listen on, but it can be overridden in the PMS settings still as far as I know, so while it would probably work to assume that for almost all setups there still may be the odd setup where a user has configured a different port for their PMS.

I stand corrected on this statement.  The internal port is always 32400 (the setting I was thinking about apparently only affects the listening port for opening up things on firewalls with upnp).

Thanks, I've found quite a lot of hardcoded '127.0.0.1' and '32400' in the Plex framework itself, so I guess I'll go with that.

Also, thanks for the threading tip.

Whoopsie...

in PlayVideo
    Thread.Create(history.track_playback, url=track_url)
NameError: global name 'Thread' is not defined

 

Whoopsie...

in PlayVideo
    Thread.Create(history.track_playback, url=track_url)
NameError: global name 'Thread' is not defined

Ah. Crap. Thread must be blocked for URL Services. Sorry about that. Let me see if there's another way to fire an HTTP request asynchronously.

The HTTP.PreCache() method might do the trick.

From the docs:

HTTP.PreCache(urlvalues=Noneheaders={}cacheTime=Noneencoding=Noneerrors=None)

Instructs the framework to pre-cache the result of a given HTTP request in a background thread. This method returns nothing - it is designed to ensure that cached data is available for future calls to HTTP.Request.

 

Apparently that won't work either. It seems like the only options available are to

  1. put up with the delay while the HTTP Request executes and returns, or
  2. rewrite the plugin without using an URL Service

Of course neither option is ideal and there's no guarantee that it'll work exactly as planned (or at all?) but it doesn't hurt to try.

Yep. Okay, waiting for the request to localhost shouldn't be very noticeable anyway.

Edit: this is getting ridiculous:

in Request
    raise Framework.exceptions.FrameworkException("Accessing the media server's HTTP interface is not permitted.")
FrameworkException: Accessing the media server's HTTP interface is not permitted.

networkkit.py:

    if url.find(':32400/') > -1 and self._sandbox.policy.elevated_execution == False:
      raise Framework.exceptions.FrameworkException("Accessing the media server's HTTP interface is not permitted.")

But I'm already running the plugin with the Elevated policy. Looks like the plugin's URL Services don't take that into the account.

Edit 2:

Got away with this:

    import urllib2
    urllib2.urlopen(track_url)

Would probably be able to make it asynchronous, too, but it seems quick enough as it is.

It's a shame the framework has to get in the way like this.

Sigh. Unsupported behaviour.  :unsure:

Glad you managed a workaround.

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