Roku Developer Program

Developers and content creators—a complete solution for growing an audience directly.
cancel
Showing results for 
Search instead for 
Did you mean: 
dhoard
Level 7

Code to store playback position of last X streams played

I'm currently using a REST API on a server to store the playback position value, but want to remove that dependency.

Does anyone have any code / pointers to code to store the playback position in the local registry for the last X streams played ? Basically a performant registry based persistent MRU implementation.

Most examples seem to blindly just store the media id (key)/ playback position (value) and hope the registry won't become full. Removing the key / value when the playback position is at the end of the stream will help alleviate the storage concern, but there still is an edge case.

Thanks,

-Doug
0 Kudos
11 Replies
Roku Employee
Roku Employee

Re: Code to store playback position of last X streams played

You could read the list of IDs/Positions the first time a video playback writes to the registry, if there are more than x keys, delete the oldest one, then write the new one, you would need to put a timestamp in your playback position for that.

- Joel
0 Kudos
belltown
Level 7

Re: Code to store playback position of last X streams played

I have a registry section for my content history.

Each content item is stored with its ContentId as the key and a Json string used for the value. The Json string value is obtained by Json-encoding an associative array containing all the content item's properties I'm interested in saving, including the last-used timestamp, playback position, status, etc.

I define a "desired" maximum number of content items I can store in the history section. In the channel's startup code, I go through the registry deleting any items in excess of the desired maximum, the least-recently accessed items first. It is conceivably possible during any given run of the channel to fill up the registry, but highly unlikely given the size of the registry, the size of each key/value and the number of new content items likely to be accessed during a single run of the channel. This method is mainly designed to prevent the registry history growing over time past its limit.

To delete any excess keys when the channel is run, I calculate the number of excess keys I need to delete, [MAX - GetKeyList().Count()], create an "overflow" array of that size, then read each key in turn [For Each key In GetKeyList()]. For each key, if there's room in the array, I Push {key name, timestamp} into the array. If the array is full, I read each array item to find the one with the newest timestamp; if that timestamp is newer than the current key's timestamp then I replace that item with the current key and its timestamp. At the end of looping through each key in the History section, I loop through my overflow array, Delete() from the registry all the items whose keys I had stored in the array, then Flush() the registry.

It seems to me to be a fairly simple and efficient algorithm. There's no need to apply any complicated sort algorithm to the registry keys to get them sorted in timestamp order, and even though I read every item in the overflow array for each (or most) or the registry keys, the overflow array probably won't have more than a handful of items to read. And there's no special processing needed after the channel has initialized as new keys are added.

The other option would be to check the number of keys in the registry each time a new key is to be added, and if you're already at your limit then delete the oldest key. That would involve reading through each registry key each time a new content item is added to find the oldest one.

Note that in my case I don't actually need a sorted list of most recently-used items. If I did then I'd need additional sorting logic.
https://github.com/belltown/
0 Kudos
EnTerr
Level 8

Re: Code to store playback position of last X streams played

Just think of teh registry as a dictionary of dictionaries (i.e. `registry.section.key = strValue`) with different API. Only when reading it from disk (on obj creation) or writing to disk (on flush) there is a (minimal) performance hit. It does not matter if you work with a roRegistrySection or roRegistry, to/from disk takes the same time per current implementation. Just think of it as a single INI file, that should help.

Doing a MRU (most-recently used) list is as simple as storing array of content IDs under some registry key. Where the natural order of the array determines what's the newest. And the only maintenance step is to enforce array size below certain maximum. In code-ese:

reg = createObject("roRegistrySection", "sect")

'read MRU from disk
MRU = reg.read("MRU").tokenize(",")

'store MRU to disk
val = ""
first = MRU.count() - 10: if first < 0 then first = 0
for i = first to MRU.count() - 1:
val = val + MRU[i] + ","
end for
reg.write("MRU", val)
reg.flush()
0 Kudos
dhoard
Level 7

Re: Code to store playback position of last X streams played

I decided to do the purge on the put in the cache (video playback). O(n) to purge the oldest entry, which could only occur when putting something new in the cache. For my usage in a the playback notification message loop, the purge would happen on the first playback notification message.

Anyone see any issues with the performance of this code ?


'
' Object to implement a persistent cache of a maximum size
'
Function PersistentCache(p_name As String, p_size As Integer) As Object

this = {
name : p_name
size : p_size
registrySectionData : CreateObject("roRegistrySection", p_name + ".data")
registrySectionTimestamp : CreateObject("roRegistrySection", p_name + ".timestamp")

Put: PersistentCache_Put
ContainsKey: PersistentCache_ContainsKey
Get: PersistentCache_Get
Remove: PersistentCache_Remove
Clear: PersistentCache_Clear
}

return this

End Function

Sub PersistentCache_Put(p_key As String, p_value As String)

m.registrySectionData.Write(p_key, p_value)
m.registrySectionTimestamp.Write(p_key, CreateObject("roDateTime").AsSeconds().ToStr())

If (m.registrySectionData.GetKeyList().Count() > m.size) Then

oldestKey = m.registrySectionData.GetKeyList()[0]
oldestTimestamp = m.registrySectionTimestamp.Read(oldestKey).ToInt()

For Each key In m.registrySectionData.GetKeyList()

tempTimestamp = m.registrySectionTimestamp.Read(key).ToInt()

If (tempTimestamp < oldestTimestamp) Then

oldestKey = key
oldestTimestamp = tempTimestamp

End If

End For

m.registrySectionData.Delete(oldestKey)
m.registrySectionTimestamp.Delete(oldestKey)

End If

m.registrySectionData.Flush()
m.registrySectionTimestamp.Flush()

End Sub

Function PersistentCache_ContainsKey(p_key As String) As Boolean

return m.registrySectionData.Exists(p_key)

End Function

Function PersistentCache_Get(p_key As String) As Object

If (m.registrySectionData.Exists(p_key)) Then

return m.registrySectionData.Read(p_key)

Else

return Invalid

End If

End Function

Sub PersistentCache_Remove(p_key As String)

m.registrySectionData.Delete(p_key)
m.registrySectionTimestamp.Delete(p_key)

m.registrySectionData.Flush()
m.registrySectionTimestamp.Flush()

End Sub

Sub PersistentCache_Clear()

For Each key In m.registrySectionData.GetKeyList()

m.registrySectionData.Delete(key)
m.registrySectionTimestamp.Delete(key)

End For

m.registrySectionData.Flush()
m.registrySectionTimestamp.Flush()

End Sub
0 Kudos
EnTerr
Level 8

Re: Code to store playback position of last X streams played

Seems passable albeit complicated: why look for smallest timestamp when in a list you always know what's the oldest entry - the first one. And O(1) trumps O(n). Who said "At Google everything is O(1)", i forgot.

Anywho, you should be fine. I would replace `IF (m.registrySectionData.GetKeyList().Count() > m.size)` with a `WHILE (...)` in Put, so that it will trim the hedge appropriately if later PersistentCache is instantiated with a smaller p_size.
0 Kudos
dhoard
Level 7

Re: Code to store playback position of last X streams played

I didn't see anywhere in the documentation that stated ifRegistrySection.GetKeyList() would return a list sorted with oldest key first, so I assumed it wasn't sorted.

Good catch regarding the If to While.

Thanks,

-Doug
0 Kudos
EnTerr
Level 8

Re: Code to store playback position of last X streams played

"dhoard" wrote:
I didn't see anywhere in the documentation that stated ifRegistrySection.GetKeyList() would return a list sorted with oldest key first, so I assumed it wasn't sorted.

I did not imply `GetKeyList()` returns keys in particular order. If you ask me to take a guess, mine is that registry does not preserve key order.
Storing ordered list in registry is DIY job and i showed one way. Another is using formatJSON*/parseJSON.

(*) except formatJSON is N/A in fw3. "why?" is a good question.
0 Kudos
RokuMarkn
Level 7

Re: Code to store playback position of last X streams played

That is correct, GetKeyList does not return keys in any guaranteed order.

However EnTerr, I, like dhoard, read your comments to imply that you thought otherwise. So I guess we're both wondering now, what you meant when you said that his loop was unnecessary?

--Mark
0 Kudos
EnTerr
Level 8

Re: Code to store playback position of last X streams played

"RokuMarkn" wrote:
However EnTerr, I, like dhoard, read your comments to imply that you thought otherwise. So I guess we're both wondering now, what you meant when you said that his loop was unnecessary?

See above. Keep MRU list of keys:
- read from registry on start
- on new content, `mruList.push(newKey)`
- before reg.flush(), expire oldies and store mruList
for _ = 1 to mruList.count() - N
reg.delete(mruList.shift())
end for
reg.write("MRU", toJSON(mruList))

PS. replaced formatJSON() with toJSON() call to actually make this work
0 Kudos