HMS

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

appMediaServer.brs (22903B)


      1 '********************************************************************
      2 '**  Home Media Server Application - Main
      3 '**  Copyright (c) 2010-2013 Brian C. Lane All Rights Reserved.
      4 '********************************************************************
      5 
      6 '******************************************************
      7 '** Display a roGridScreen of the available media
      8 '******************************************************
      9 Function roGridMediaServer( url As String, has_keystore As Boolean ) As Object
     10     print "url: ";url
     11     print "has_keystore: "; has_keystore
     12 
     13     port=CreateObject("roMessagePort")
     14     grid = CreateObject("roGridScreen")
     15     grid.SetMessagePort(port)
     16     grid.SetDisplayMode("scale-to-fit")
     17     grid.SetGridStyle("flat-movie")
     18 
     19     categories = getSortedCategoryTitles(url)
     20     print categories
     21     grid.SetupLists(categories.Count())
     22     grid.SetListNames(categories)
     23     ' Keep track of which rows have been populated
     24     populated_rows = CreateObject("roArray", categories.Count(), false)
     25     for i = 0 to categories.Count()-1
     26         populated_rows[i] = false
     27     end for
     28 
     29     ' Add a utility row (just setup for now)
     30     utilityRow = getSetupRow(url)
     31     grid.SetContentList(0, utilityRow)
     32 
     33     showTimeBreadcrumb(grid, true)
     34     grid.Show()
     35 
     36     grid.SetFocusedListItem(1, 0)
     37 
     38     ' Hold all the movie objects
     39     screen = CreateObject("roArray", categories.Count(), false)
     40     screen[0] = "unused"
     41 
     42     populated_row = 1
     43     play_focused = -1
     44     focus_row = -1
     45     focus_col = 0
     46     last_row = -1
     47     idle_timeout = 30000
     48     while true
     49         msg = wait(idle_timeout, port)
     50         if type(msg) = "roGridScreenEvent" then
     51             idle_timeout = 30000
     52 
     53             ' Only Play to start a video works, none of the others return true
     54             wasPlayPressed = false
     55             backPressed = false
     56             downPressed = false
     57             if msg.isRemoteKeyPressed() then
     58                 print "key = "; msg.GetIndex()
     59                 if msg.getIndex() = 13 then
     60                     wasPlayPressed = true
     61                 elseif msg.getIndex() = 0 then
     62                     backPressed = true
     63                 elseif msg.getIndex() = 3 then
     64                     downPressed = true
     65                 end if
     66             end if
     67 
     68             if msg.isScreenClosed() then
     69                 return -1
     70             elseif msg.isListItemFocused() then
     71                 focus_row = msg.getIndex()
     72                 focus_col = msg.getData()
     73 
     74                 print "row, col = "; focus_row, focus_col
     75                 if focus_row <> last_row then
     76                     last_row = focus_row
     77 
     78                     ' Here is where we start fetching things for the next/previous rows and columns
     79                     ' focus can skip intermediate rows, need to fill them all in.
     80                     for each i in [focus_row, focus_row+1, focus_row+2, focus_row-1]
     81                         if i > 0 and i < populated_rows.Count() and populated_rows[i] = false then
     82                             grid.SetBreadcrumbText("Loading " + categories[i], "")
     83                             metadata = getCategoryMetadata(url, categories[i])
     84                             screen[i] = metadata
     85                             grid.setContentList(i, metadata)
     86                             populated_rows[i] = true
     87                             last_col = getFocusedItem(url, has_keystore, categories[i], metadata.Count())
     88                             grid.SetListOffset(i, last_col)
     89                         end if
     90                     end for
     91                     showTimeBreadcrumb(grid, true)
     92                 end if
     93             elseif msg.isListItemSelected() or wasPlayPressed then
     94                 if focus_row = 0 and focus_col = 0 then
     95                     checkServerUrl(true)
     96                 elseif focus_row = 0 then
     97                     ' Select row to jump to A-Z (1-26)
     98                     print "col = "; focus_col
     99                     search_chr = Chr(64+focus_col)
    100                     ' Find the first category that starts with this letter (case-insensitive)
    101                     for i = 1 to categories.Count()-1
    102                         first = UCase(Left(categories[i], 1))
    103                         if first >= search_chr then
    104                             last_col = getFocusedItem(url, has_keystore, categories[i], metadata.Count())
    105                             grid.SetFocusedListitem(i, focus_col)
    106                             exit for
    107                         end if
    108                     end for
    109                 else
    110                     print "row, col = "; focus_row, focus_col
    111                     if has_keystore = true then
    112                         setKeyValue(url, categories[focus_row], tostr(focus_col))
    113                     end if
    114                     result = playMovie(screen[focus_row][focus_col], url, has_keystore)
    115                     if result = true and focus_col < screen[focus_row].Count() then
    116                         ' Advance to the next video and save it
    117                         grid.SetFocusedListitem(focus_row, focus_col+1)
    118                         if has_keystore = true then
    119                             setKeyValue(url, categories[focus_row], tostr(focus_col+1))
    120                         end if
    121                     end if
    122                 end if
    123             elseif wasDownPressed then
    124                 print "down row = "; focus_row
    125                 print "last row = "; categories.Count()
    126                 if focus_row = categories.Count() then
    127                     print "need to wrap back to top"
    128                 end if
    129             else
    130                 print msg
    131             endif
    132         else if msg = invalid then
    133             showTimeBreadcrumb(grid, true)
    134 
    135             ' If the screen has been idle for 30 seconds go and load an un-populated row
    136             for i = 1 to populated_rows.Count()
    137                 if populated_rows[i] = false then
    138                     print "Idle, populating "; categories[i]
    139                     grid.SetBreadcrumbText("Loading " + categories[i], "")
    140                     metadata = getCategoryMetadata(url, categories[i])
    141                     screen[i] = metadata
    142                     grid.setContentList(i, metadata)
    143                     populated_rows[i] = true
    144                     showTimeBreadcrumb(grid, true)
    145                     last_col = getFocusedItem(url, has_keystore, categories[i], metadata.Count())
    146                     grid.SetListOffset(i, last_col)
    147 
    148                     ' Speed up loading while it remains idle
    149                     if idle_timeout > 5000 then
    150                         idle_timeout = idle_timeout - 5000
    151                     end if
    152                     exit for
    153                 end if
    154             end for
    155         end if
    156     end while
    157 End Function
    158 
    159 '******************************************************
    160 '** Display a scrolling grid of everything on the server
    161 '******************************************************
    162 Function roPosterMediaServer( url As String, has_keystore As Boolean ) As Object
    163     print "url: ";url
    164     print "has_keystore: "; has_keystore
    165 
    166     port = CreateObject("roMessagePort")
    167     screen = CreateObject("roPosterScreen")
    168     screen.SetMessagePort(port)
    169     screen.SetListStyle("arced-portrait")
    170     screen.setListDisplayMode("scale-to-fit")
    171 
    172     ' Build list of Category Names from the top level directories
    173     listing = getDirectoryListing(url)
    174     if listing = invalid then
    175         print "Failed to get directory listing for ";url
    176         return invalid
    177     end if
    178     categories = displayFiles(listing, {}, true)
    179     Sort(categories, function(k)
    180                        return LCase(k[0])
    181                      end function)
    182     titles = catTitles(categories)
    183     screen.SetListNames(titles)
    184     max_titles = titles.Count()-1
    185 
    186     screen.SetFocusToFilterBanner(true)
    187     last_title = getFocusedItem(url, has_keystore, "filter_pos", max_titles)
    188     screen.SetFocusedList(last_title)
    189     showTimeBreadcrumb(screen, true)
    190     screen.Show()
    191 
    192     cache = CreateObject("roAssociativeArray")
    193 
    194     ' Load the selected title
    195     metadata = getCategoryMetadata(url, categories[last_title][0])
    196     if metadata.Count() > 0 then
    197         cache.AddReplace(tostr(last_title), metadata)
    198         screen.SetContentList(metadata)
    199         screen.SetFocusedListItem(getFocusedItem(url, has_keystore, titles[last_title], metadata.Count()))
    200     end if
    201 
    202     play_focus = -1
    203     setup_selected = false
    204     while true
    205         msg = wait(30000, port)
    206         if type(msg) = "roPosterScreenEvent" then
    207             if msg.isRemoteKeyPressed() and msg.getIndex() = 13 then
    208                 wasPlayPressed = true
    209             else
    210                 wasPlayPressed = false
    211             end if
    212             if msg.isScreenClosed() then
    213                 return -1
    214             elseif msg.isListSelected() then
    215                 if msg.GetIndex() = max_titles then
    216                     screen.SetContentList(getSetupRow(url))
    217                     setup_selected = true
    218                 else
    219                     setup_selected = false
    220                     last_title = msg.GetIndex()
    221                     print "selected "; titles[last_title]
    222 
    223                     ' Save this as the last selected filter position
    224                     if has_keystore = true then
    225                         setKeyValue(url, "filter_pos", tostr(msg.GetIndex()))
    226                     end if
    227                     screen.SetContentList([])
    228 
    229                     ' Is this cached? If not, clear it and look it up
    230                     if not cache.DoesExist(tostr(last_title)) then
    231                         metadata = getCategoryMetadata(url, categories[last_title][0])
    232                         cache.AddReplace(tostr(last_title), metadata)
    233                     else
    234                         metadata = cache.Lookup(tostr(last_title))
    235                     end if
    236                     screen.SetContentList(metadata)
    237                     screen.SetFocusedListItem(getFocusedItem(url, has_keystore, titles[last_title], metadata.Count()))
    238                 end if
    239             elseif msg.isListItemSelected() and setup_selected = true then
    240                 checkServerUrl(true)
    241                 screen.SetFocusToFilterBanner(true)
    242             elseif msg.isListItemFocused() then
    243                 play_focus = msg.getIndex()
    244             elseif msg.isListItemSelected() or wasPlayPressed then
    245                 print "Focus is on"; play_focus
    246                 if has_keystore = true then
    247                     setKeyValue(url, titles[last_title], tostr(play_focus))
    248                 end if
    249                 movies = screen.GetContentList()
    250                 print movies[play_focus]
    251                 result = playMovie(movies[play_focus], url, has_keystore)
    252                 if result = true and play_focus < movies.Count() then
    253                     ' Advance to the next video and save it
    254                     screen.SetFocusedListItem(play_focus+1)
    255                     if has_keystore = true then
    256                         if play_focus < movies.Count() then
    257                             setKeyValue(url, titles[last_title], tostr(play_focus+1))
    258                         end if
    259                     end if
    260                 end if
    261                 showTimeBreadcrumb(screen, true)
    262             end if
    263         else if msg = invalid then
    264             showTimeBreadcrumb(screen, true)
    265         endif
    266     end while
    267 End Function
    268 
    269 '*************************************
    270 '** Get the utility row (Setup, Search)
    271 '*************************************
    272 Function getUtilRow(url As String) As Object
    273     ' Setup the Search/Setup entries for first row
    274     search = CreateObject("roArray", 2, true)
    275     o = CreateObject("roAssociativeArray")
    276     o.ContentType = "episode"
    277     o.Title = "Setup"
    278     o.SDPosterUrl = url+"/Setup-SD.png"
    279     o.HDPosterUrl = url+"/Setup-HD.png"
    280     search.Push(o)
    281 
    282     o = CreateObject("roAssociativeArray")
    283     o.ContentType = "episode"
    284     o.Title = "Search"
    285     o.SDPosterUrl = url+"/Search-SD.png"
    286     o.HDPosterUrl = url+"/Search-HD.png"
    287     search.Push(o)
    288 
    289     return search
    290 End Function
    291 
    292 
    293 '*************************************
    294 '** Get the Setup row
    295 '*************************************
    296 Function getSetupRow(url As String) As Object
    297     ' Setup the Search
    298     setup = CreateObject("roArray", 27, true)
    299     o = CreateObject("roAssociativeArray")
    300     o.ContentType = "episode"
    301     o.Title = "Setup"
    302     o.SDPosterUrl = url+"/Setup-SD.png"
    303     o.HDPosterUrl = url+"/Setup-HD.png"
    304     setup.Push(o)
    305 
    306     ' Add the A-Z posters
    307     for i = 0 to 25
    308         o = CreateObject("roAssociativeArray")
    309         o.ContentType = "episode"
    310         o.Title = Chr(Asc("A") + i)
    311         o.SDPosterUrl = url+"/.icons/" + Chr(Asc("a") + i) + ".jpg"
    312         o.HDPosterUrl = url+"/.icons/" + Chr(Asc("a") + i) + ".jpg"
    313         setup.Push(o)
    314     end for
    315     return setup
    316 End Function
    317 
    318 '**********************************
    319 '** Return the type of the directory
    320 '**********************************
    321 Function directoryType(listing_hash As Object) As Integer
    322     if listing_hash.DoesExist("photos") then
    323         return 1
    324     else if listing_hash.DoesExist("songs") then
    325         return 2
    326     else if listing_hash.DoesExist("episodes") then
    327         return 3
    328     else if listing_hash.DoesExist("movies") then
    329         return 4
    330     end if
    331     return 0
    332 End Function
    333 
    334 
    335 '******************************************
    336 '** Create an object with the movie metadata
    337 '******************************************
    338 Function MovieObject(file As Object, url As String, listing_hash as Object) As Object
    339     o = CreateObject("roAssociativeArray")
    340     o.ContentType = "movie"
    341     o.ShortDescriptionLine1 = file[1]["basename"]
    342 
    343     ' Search for SD & HD images and .bif files
    344     if listing_hash.DoesExist(file[1]["basename"]+"-SD.png") then
    345         o.SDPosterUrl = url+file[1]["basename"]+"-SD.png"
    346     else if listing_hash.DoesExist(file[1]["basename"]+"-SD.jpg") then
    347         o.SDPosterUrl = url+file[1]["basename"]+"-SD.jpg"
    348     else
    349         o.SDPosterUrl = url+"default-SD.png"
    350     end if
    351 
    352     o.IsHD = false
    353     ' With the roPosterScreen it always wants to request the HDPosterURL, no matter what IsHD is set to.
    354     ' So Fake it out and reuse the -SD images for now.
    355     if listing_hash.DoesExist(file[1]["basename"]+"-SD.png") then
    356         o.HDPosterUrl = url+file[1]["basename"]+"-SD.png"
    357     else if listing_hash.DoesExist(file[1]["basename"]+"-SD.jpg") then
    358         o.HDPosterUrl = url+file[1]["basename"]+"-SD.jpg"
    359     else
    360         o.HDPosterUrl = url+"default-SD.png"
    361     end if
    362 
    363     ' Setup the .bif file
    364     if listing_hash.DoesExist(file[1]["basename"]+"-SD.bif") then
    365         o.SDBifUrl = url+file[1]["basename"]+"-SD.bif"
    366     end if
    367     if listing_hash.DoesExist(file[1]["basename"]+"-HD.bif") then
    368         o.HDBifUrl = url+file[1]["basename"]+"-HD.bif"
    369     end if
    370 
    371     if listing_hash.DoesExist(file[1]["basename"]+".txt") then
    372         o.Description = getDescription(url+file[1]["basename"]+".txt")
    373     end if
    374 
    375     o.HDBranded = false
    376     o.Rating = "NR"
    377     o.StarRating = 100
    378     o.Title = file[1]["basename"]
    379     o.Length = 0
    380 
    381     ' Video related stuff (can I put this all in the same object?)
    382     o.StreamBitrates = [0]
    383     o.StreamUrls = [url + file[0]]
    384     o.StreamQualities = ["SD"]
    385 
    386     streamFormat = { mp4 : "mp4", m4v : "mp4", mov : "mp4",
    387                      wmv : "wmv", hls : "hls"
    388                    }
    389     if streamFormat.DoesExist(file[1]["extension"].Mid(1)) then
    390         o.StreamFormat = streamFormat[file[1]["extension"].Mid(1)]
    391     else
    392         o.StreamFormat = ["mp4"]
    393     end if
    394 
    395     return o
    396 End Function
    397 
    398 '********************************
    399 '** Set breadcrumb to current time
    400 '********************************
    401 Function showTimeBreadcrumb(screen As Object, use_ampm As Boolean)
    402     now = CreateObject("roDateTime")
    403     now.ToLocalTime()
    404     hour = now.GetHours()
    405     if use_ampm then
    406         if hour < 12 then
    407             ampm = " AM"
    408         else
    409             ampm = " PM"
    410             if hour > 12 then
    411                 hour = hour - 12
    412             end if
    413         end if
    414     end if
    415     hour = tostr(hour)
    416     minutes = now.GetMinutes()
    417     if minutes < 10 then
    418         minutes = "0"+tostr(minutes)
    419     else
    420         minutes = tostr(minutes)
    421     end if
    422     bc = now.AsDateString("short-month-short-weekday")+" "+hour+":"+minutes+ampm
    423     screen.SetBreadcrumbText(bc, "")
    424 End Function
    425 
    426 '*************************************
    427 '** Get the last position for the movie
    428 '*************************************
    429 Function getLastPosition(title As String, url As String, has_keystore As Boolean) As Integer
    430     ' use movie.Title as the filename
    431     last_pos = ReadAsciiFile("tmp:/"+title)
    432     if last_pos <> "" then
    433         return last_pos.toint()
    434     end if
    435     ' No position stored on local filesystem, query keystore
    436     if has_keystore = true then
    437         last_pos = getKeyValue(url, title)
    438         if last_pos <> "" then
    439             return last_pos.toint()
    440         end if
    441     end if
    442     return 0
    443 End Function
    444 
    445 '******************************************************
    446 '** Return a list of the Videos and directories
    447 '**
    448 '** Videos end in the following extensions
    449 '** .mp4 .m4v .mov .wmv
    450 '******************************************************
    451 Function displayFiles( files As Object, fileTypes As Object, dirs=false As Boolean ) As Object
    452     list = []
    453     for each f in files
    454         ' This expects the path to have a system volume at the start
    455         p = CreateObject("roPath", "pkg:/" + f)
    456         if p.IsValid() and f.Left(1) <> "." then
    457             fileType = fileTypes[p.Split().extension.mid(1)]
    458             if (dirs and f.Right(1) = "/") or fileType = true then
    459                 list.push([f, p.Split()])
    460             end if
    461         end if
    462     end for
    463 
    464     return list
    465 End Function
    466 
    467 '******************************************************
    468 '** Play the video using the data from the movie
    469 '** metadata object passed to it
    470 '******************************************************
    471 Sub playMovie(movie As Object, url As String, has_keystore As Boolean) As Boolean
    472     p = CreateObject("roMessagePort")
    473     video = CreateObject("roVideoScreen")
    474     video.setMessagePort(p)
    475     video.SetPositionNotificationPeriod(15)
    476 
    477     movie.PlayStart = getLastPosition(movie.Title, url, has_keystore)
    478     video.SetContent(movie)
    479     video.show()
    480 
    481     last_pos = 0
    482     while true
    483         msg = wait(0, video.GetMessagePort())
    484         if type(msg) = "roVideoScreenEvent"
    485             if msg.isScreenClosed() then 'ScreenClosed event
    486                 exit while
    487             else if msg.isPlaybackPosition() then
    488                 last_pos = msg.GetIndex()
    489                 WriteAsciiFile("tmp:/"+movie.Title, tostr(last_pos))
    490                 if has_keystore = true then
    491                     setKeyValue(url, movie.Title, tostr(last_pos))
    492                 end if
    493             else if msg.isfullresult() then
    494                 DeleteFile("tmp:/"+movie.Title)
    495                 if has_keystore = true then
    496                     setKeyValue(url, movie.Title, "")
    497                 end if
    498                 return true
    499             else if msg.isRequestFailed() then
    500                 print "play failed: "; msg.GetMessage()
    501             else
    502                 print "Unknown event: "; msg.GetType(); " msg: "; msg.GetMessage()
    503             end if
    504         end if
    505     end while
    506 
    507     return false
    508 End Sub
    509 
    510 '******************************************************
    511 '** Check to see if a description file (.txt) exists
    512 '** and read it into a string.
    513 '** And if it is missing return ""
    514 '******************************************************
    515 Function getDescription(url As String)
    516     http = CreateObject("roUrlTransfer")
    517     http.SetUrl(url)
    518     resp = http.GetToString()
    519 
    520     if resp <> invalid then
    521         return resp
    522     end if
    523     return ""
    524 End Function
    525 
    526 '******************************************************
    527 ' Return a roArray of just the category names
    528 ' include the Setup row as the first entry
    529 '******************************************************
    530 Function catTitles(categories As Object) As Object
    531     titles = CreateObject("roArray", categories.Count()+1, false)
    532     titles.Push("Jump To")
    533     for i = 0 to categories.Count()-1
    534         titles.Push(getLastElement(categories[i][0]))
    535     end for
    536     return titles
    537 End Function
    538 
    539 '******************************************************
    540 '** Get a sorted roArray of category titles
    541 '******************************************************
    542 Function getSortedCategoryTitles(url as String) As Object
    543      ' Build list of Category Names from the top level directories
    544      listing = getDirectoryListing(url)
    545      if listing = invalid then
    546          return invalid
    547      end if
    548      categories = displayFiles(listing, {}, true)
    549      Sort(categories, function(k)
    550                         return LCase(k[0])
    551                       end function)
    552     return catTitles(categories)
    553 End Function
    554 
    555 '*******************************************************************
    556 ' Return a roArray of roAssociativeArrays for the selected category
    557 '*******************************************************************
    558 Function getCategoryMetadata(url As String, category As String) As Object
    559     cat_url = url + "/" + category + "/"
    560     listing = getDirectoryListing(cat_url)
    561     listing_hash = CreateObject("roAssociativeArray")
    562     for each f in listing
    563         listing_hash.AddReplace(f, "")
    564     end for
    565 
    566     ' What kind of directory is this?
    567     dirType = directoryType(listing_hash)
    568     if dirType = 1 then
    569         displayList  = displayFiles(listing, { jpg : true })
    570     else if dirType = 2 then
    571         displayList = displayFiles(listing, { mp3 : true })
    572     else if dirType = 3 then
    573         displayList = displayFiles(listing, { mp4 : true, m4v : true, mov : true, wmv : true } )
    574     else if dirType = 4 then
    575         displayList = displayFiles(listing, { mp4 : true, m4v : true, mov : true, wmv : true } )
    576     else
    577         ' Assume movies if there is no type file
    578         displayList = displayFiles(listing, { mp4 : true, m4v : true, mov : true, wmv : true } )
    579     end if
    580 
    581     Sort(displayList, function(k)
    582                        return LCase(k[0])
    583                      end function)
    584     list = CreateObject("roArray", displayList.Count(), false)
    585     for j = 0 to displayList.Count()-1
    586         list.Push(MovieObject(displayList[j], cat_url, listing_hash))
    587     end for
    588     return list
    589 End Function
    590 
    591 '******************************************************
    592 ' Get the last focused item for a category
    593 ' or return 0 if there is no keystore or an error
    594 '******************************************************
    595 Function getFocusedItem(url As String, has_keystore As Boolean, category As String, max_items As Integer) As Integer
    596     if has_keystore = true then
    597         focus_pos = getKeyValue(url, category)
    598         if focus_pos <> "" and focus_pos.toint() < max_items then
    599             print "category ";category;" focus is ";focus_pos.toint()
    600             return focus_pos.toint()
    601         end if
    602     end if
    603     print "category ";category;" focus is 0"
    604     return 0
    605 End Function