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