HMS

Home Media Server for Roku Players
git clone https://www.brianlane.com/git/HMS
Log | Files | Refs | README | LICENSE

commit 6efb5270187e007adf1c482b4e585309faa729bc
parent 59fc711ca4da373341faffc142027b2d845f6116
Author: Brian C. Lane <bcl@brianlane.com>
Date:   Sat, 13 Mar 2010 08:22:19 -0800

Add per-use resume playback support

The playback position is now stored in the database when the video is
exited. This is stored on a per-user basis, so multiple users can watch
the same movie, and as long as it was selected from their list it will
resume at their last position.

 * Add URL handling for playback position support
 * Add posting playback position to the server
 * Add userID and mediaId to the XML output for lists
 * Add POST of the last position when exiting the video player screen
 * Turned off xsrf cookies
   This is because the Roku has no way to post the correct cookie to the
   server when it is storing the last played position of the media.
 * Add storage of the last position
 * Add new table, last_position, to store the per-user/media positions.
 * Added lastPos to the XML for the media details
 * Add resume playback from last position to the details screen
 * Refresh the detail screen after playback is stopped

Diffstat:
Mroku_player/source/appDetailScreen.brs | 13++++++++++++-
Mroku_player/source/appVideoScreen.brs | 22+++++++++++++++++++++-
Mroku_player/source/showFeed.brs | 14++++++++++----
Mserver/hms/hms.py | 73+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mserver/hms/templates/xmllist.html | 3+++
5 files changed, 117 insertions(+), 8 deletions(-)

diff --git a/roku_player/source/appDetailScreen.brs b/roku_player/source/appDetailScreen.brs @@ -57,8 +57,13 @@ Function showDetailScreen(screen As Object, showList As Object, showIndex as Int print "ButtonPressed" if msg.GetIndex() = 1 showVideoScreen(showList[showIndex]) + refreshShowDetail(screen, showList, showIndex) endif if msg.GetIndex() = 2 + ' Set the starting position + showList[showIndex].PlayStart = showList[showIndex].LastPos + showVideoScreen(showList[showIndex]) + refreshShowDetail(screen, showList, showIndex) endif if msg.GetIndex() = 3 endif @@ -95,7 +100,13 @@ Function refreshShowDetail(screen As Object, showList As Object, showIndex as In screen.SetDescriptionStyle(show.ContentType) screen.ClearButtons() - screen.AddButton(1, "play") + if show.LastPos = 0 then + screen.AddButton(1, "play") + else + screen.AddButton(2, "resume playing") + screen.AddButton(1, "play from beginning") + end if + screen.SetContent(show) screen.Show() diff --git a/roku_player/source/appVideoScreen.brs b/roku_player/source/appVideoScreen.brs @@ -31,14 +31,34 @@ Function showVideoScreen(episode As Object) 'Uncomment his line to dump the contents of the episode to be played PrintAA(episode) + lastpos = 0 + screen.SetPositionNotificationPeriod(1) while true msg = wait(0, port) - if type(msg) = "roVideoScreenEvent" then print "showHomeScreen | msg = "; msg.getMessage() " | index = "; msg.GetIndex() if msg.isScreenClosed() print "Screen closed" + + ' Save the last position to the server + print "lastpos = "; lastpos + + ' Need to know: + ' server URL + url = "http://" + RegRead("ServerURL") + + ' User ID and Media ID + url = url +"/user/last/"+ episode.UserId +"/"+ episode.MediaId + + print "url: " + url + http = NewHttp(url) + rsp = http.PostFromStringWithTimeout(itostr(lastpos), 30) + + episode.LastPos = lastpos exit while + elseif msg.isPlaybackPosition() + print "playback position: "; msg.GetIndex() + lastpos = msg.GetIndex() elseif msg.isRequestFailed() print "Video request failure: "; msg.GetIndex(); " " msg.GetData() elseif msg.isStatusMessage() diff --git a/roku_player/source/showFeed.brs b/roku_player/source/showFeed.brs @@ -52,14 +52,17 @@ End Function Function init_show_feed_item() As Object o = CreateObject("roAssociativeArray") + o.UserId = "" + o.MediaId = "" o.ContentId = "" o.Title = "" o.ContentType = "" o.Description = "" o.Length = "" - o.Actors = CreateObject("roArray", 3, true) - o.Director = CreateObject("roArray", 1, true) - o.Categories = CreateObject("roArray", 3, true) + o.LastPos = "" + o.Actors = CreateObject("roArray", 3, true) + o.Director = CreateObject("roArray", 1, true) + o.Categories = CreateObject("roArray", 3, true) o.StreamFormats = CreateObject("roArray", 5, true) o.StreamQualities = CreateObject("roArray", 5, true) o.StreamBitrates = CreateObject("roArray", 5, true) @@ -132,12 +135,15 @@ Function parse_show_feed(xml As Object, feed As Object) As Void 'fetch all values from the xml for the current show item.HDPosterUrl = validstr(curShow@hdPosterUrl) item.SDPosterUrl = validstr(curShow@sdPosterUrl) - item.ContentId = validstr(curShow.contentId.GetText()) + item.ContentId = validstr(curShow.contentId.GetText()) + item.UserId = validstr(curShow.userId.GetText()) + item.MediaId = validstr(curShow.mediaId.GetText()) item.Title = validstr(curShow.title.GetText()) item.Description = validstr(curShow.description.GetText()) item.ContentType = validstr(curShow.contentType.GetText()) item.ContentQuality = validstr(curShow.contentQuality.GetText()) item.Length = strtoi(validstr(curShow.length.GetText())) + item.LastPos = strtoi(validstr(curShow.lastPos.GetText())) item.HDBifUrl = validstr(curShow.hdBifUrl.GetText()) item.SDBifUrl = validstr(curShow.sdBifUrl.GetText()) item.StreamFormat = validstr(curShow.streamFormat.GetText()) diff --git a/server/hms/hms.py b/server/hms/hms.py @@ -165,6 +165,17 @@ class DbSchema(object): update schema set version=7; """, + """ + create table last_position( + id INTEGER PRIMARY KEY, + media_id INTEGER REFERENCES media(id), + user_id INTEGER REFERENCES user(id), + position INTEGER + ); + + update schema set version=8; + """, + ] def __init__(self, database): @@ -1088,6 +1099,45 @@ class UserHandler(BaseHandler): return +class UserLastPositionHandler(BaseHandler): + """ + Handle the playback position for users + This cannot be protected because the Roku doesn't login to the server, so + it is possible to spoof the playback positions + """ + def get(self, user_id, media_id): + """ + Retrieve the last position this user played for the passed media_id + """ + print "User: %s Media %s" % (user_id, media_id) + + def post(self, user_id, media_id): + """ + Save the last position this user played for the passed media_id + """ + conn = sqlite3.connect(options.database) + conn.row_factory = sqlite3.Row + cur = conn.cursor() + try: + # Check to see if there is an entry for this user and media + cur.execute("select * from last_position where user_id=? and media_id=?", (user_id, media_id)) + row = cur.fetchone() + if not row: + cur.execute("insert into last_position(user_id, media_id, position) " + "values(?,?,?)", + (user_id, media_id, int(self.request.body))) + else: + cur.execute("update last_position set position=? where user_id=? " + "and media_id=?", + (int(self.request.body), user_id, media_id)) + conn.commit() + except: + print traceback.format_exc() + finally: + cur.close() + conn.close() + + class UserImageHandler(BaseHandler): def get(self, user_id): # Get the user's image @@ -1301,6 +1351,7 @@ class XMLListHandler(BaseHandler): conn = sqlite3.connect(options.database) conn.row_factory = sqlite3.Row cur = conn.cursor() + pos_cur = conn.cursor() if int(list_id) > -1: cur.execute("select * from media JOIN list_media on list_media.media_id = media.id AND list_media.user_id=? AND list_media.list_id=?", (int(user_id), int(list_id))) @@ -1309,7 +1360,7 @@ class XMLListHandler(BaseHandler): media = [] for row in cur: - print row +# print row coverImage = "%s/images/default.jpg" % (host) sdBifUrl = None @@ -1340,6 +1391,20 @@ class XMLListHandler(BaseHandler): else: categories = [] + # Get this user's last played position for this media + lastpos = 0 + try: + print "user_id=%s media_id=%s" % (user_id, row["id"]) + + # Check to see if there is an entry for this user and media + pos_cur.execute("select * from last_position where " + "user_id=? and media_id=?", + (int(user_id), int(row["id"]))) + pos_row = pos_cur.fetchone() + if pos_row: + lastpos = int(pos_row["position"]) + except: + pass metadata = { 'contentType' : row["contentType"] or "movie", @@ -1357,7 +1422,10 @@ class XMLListHandler(BaseHandler): 'url' : "%s/media/play/%s" % (host, row["id"]), }], 'length' : int(row['length']), + 'lastPos' : lastpos, 'id' : "user_%s-list_%s-movie_%s" % (user_id, list_id, row["id"]), + 'userId' : int(user_id), + 'mediaId' : row["id"], 'streamFormat' : row["streamFormat"], 'releaseDate' : row["releaseDate"], 'rating' : row["rating"], @@ -1461,7 +1529,7 @@ def main(): "static_path": os.path.join(os.path.dirname(__file__), "static"), "cookie_secret": "480BE2C7-E684-4CFB-9BE7-E7BA55952ECB", "login_url": "/login", - "xsrf_cookies": True, + "xsrf_cookies": False, } application = tornado.web.Application([ @@ -1479,6 +1547,7 @@ def main(): (r"/tmdb/search/(.*)", SearchTMDBHandler), (r"/tmdb/update/(.*)/(.*)", UpdateTMDBHandler), (r"/list/(.*)/(.*)", ListHandler), + (r"/user/last/(.*)/(.*)", UserLastPositionHandler), (r"/user/image/(.*)", UserImageHandler), (r"/user/edit/(.*)", UserEditHandler), (r"/user/(.*)", UserHandler), diff --git a/server/hms/templates/xmllist.html b/server/hms/templates/xmllist.html @@ -12,6 +12,9 @@ <titleSeason>{{ metadata["titleSeason"] or "" }}</titleSeason> <description>{{ metadata["description"] or "" }}</description> <contentId>{{ metadata["id"] }}</contentId> + <userId>{{ metadata["userId"] }}</userId> + <mediaId>{{ metadata["mediaId"] }}</mediaId> + <lastPos>{{ metadata["lastPos"] }}</lastPos> <sdBifUrl>{{ metadata["sdbifurl"] }}</sdBifUrl> <hdBifUrl>{{ metadata["hdbifurl"] }}</hdBifUrl>