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:
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>