|
24 | 24 | setupInfoNodes() |
25 | 25 | setupDataTasks() |
26 | 26 | setupScreenSaver() |
| 27 | + applyTheme() |
27 | 28 |
|
28 | | - m.buttonCount = m.buttons.getChildCount() |
29 | 29 | m.seekPosition.translation = [720 - (m.seekPosition.width / 2), m.seekPosition.translation[1]] |
30 | 30 |
|
31 | 31 | m.screenSaverTimeout = 300 |
|
43 | 43 | pageContentChanged() |
44 | 44 | setShuffleIconState() |
45 | 45 | setLoopButtonImage() |
| 46 | + ' Set lastFocus for JRScreen lifecycle management |
| 47 | + m.top.lastFocus = m.buttons |
| 48 | +end sub |
| 49 | + |
| 50 | +sub applyTheme() |
| 51 | + constants = m.global.constants |
| 52 | + |
| 53 | + m.backDropMask.color = constants.colorBlack + constants.alpha40 |
46 | 54 |
|
47 | | - m.buttons.setFocus(true) |
| 55 | + for each button in m.buttons.getChildren(-1, 0) |
| 56 | + button.iconBackground = constants.colorTextSecondary |
| 57 | + button.iconFocusBackground = constants.colorTextSecondary |
| 58 | + end for |
| 59 | + |
| 60 | + m.seekBar.color = constants.colorBlack + constants.alpha60 |
| 61 | + m.bufferPosition.color = constants.colorBackgroundSecondary |
| 62 | + m.playPosition.color = constants.colorSecondary |
| 63 | + m.thumb.blendColor = constants.colorPrimary |
48 | 64 | end sub |
49 | 65 |
|
50 | 66 | sub onScreensaverTimeoutLoaded() |
|
71 | 87 | end sub |
72 | 88 |
|
73 | 89 | sub setupAnimationTasks() |
74 | | - m.displayButtonsAnimation = m.top.FindNode("displayButtonsAnimation") |
75 | 90 | m.playPositionAnimation = m.top.FindNode("playPositionAnimation") |
76 | 91 | m.playPositionAnimationWidth = m.top.FindNode("playPositionAnimationWidth") |
77 | 92 |
|
78 | 93 | m.bufferPositionAnimation = m.top.FindNode("bufferPositionAnimation") |
79 | 94 | m.bufferPositionAnimationWidth = m.top.FindNode("bufferPositionAnimationWidth") |
80 | | - |
81 | | - m.screenSaverStartAnimation = m.top.FindNode("screenSaverStartAnimation") |
82 | 95 | end sub |
83 | 96 |
|
84 | 97 | ' Creates tasks to gather data needed to render Scene and play song |
|
101 | 114 | ' Setup playback buttons, default to Play button selected |
102 | 115 | sub setupButtons() |
103 | 116 | m.buttons = m.top.findNode("buttons") |
104 | | - m.top.observeField("selectedButtonIndex", "onButtonSelectedChange") |
105 | 117 |
|
106 | | - ' If we're playing a mixed playlist, remove the shuffle and loop buttons |
107 | | - if m.playlistTypeCount > 1 |
108 | | - shuffleButton = m.top.findNode("shuffle") |
109 | | - m.buttons.removeChild(shuffleButton) |
| 118 | + ' Get button references for later use |
| 119 | + m.playButton = m.buttons.findNode("play") |
| 120 | + m.shuffleButton = m.buttons.findNode("shuffle") |
| 121 | + m.repeatButton = m.buttons.findNode("repeat") |
110 | 122 |
|
111 | | - loopButton = m.top.findNode("loop") |
112 | | - m.buttons.removeChild(loopButton) |
| 123 | + ' If we're playing a mixed playlist, remove the shuffle and repeat buttons |
| 124 | + if m.playlistTypeCount > 1 |
| 125 | + if isValid(m.shuffleButton) |
| 126 | + m.buttons.removeChild(m.shuffleButton) |
| 127 | + m.shuffleButton = invalid |
| 128 | + end if |
113 | 129 |
|
114 | | - m.previouslySelectedButtonIndex = 0 |
115 | | - m.top.selectedButtonIndex = 1 |
116 | | - return |
| 130 | + if isValid(m.repeatButton) |
| 131 | + m.buttons.removeChild(m.repeatButton) |
| 132 | + m.repeatButton = invalid |
| 133 | + end if |
117 | 134 | end if |
118 | 135 |
|
119 | | - m.previouslySelectedButtonIndex = 1 |
120 | | - m.top.selectedButtonIndex = 2 |
| 136 | + ' Set default focus to play button (index 1) |
| 137 | + m.buttons.buttonFocused = 1 |
| 138 | + |
| 139 | + ' Center button group horizontally |
| 140 | + m.buttons.callFunc("center") |
| 141 | + |
| 142 | + ' Observe button selection |
| 143 | + m.buttons.observeField("buttonSelected", "onButtonSelected") |
121 | 144 | end sub |
122 | 145 |
|
123 | | -' Event handler when user selected a different playback button |
124 | | -sub onButtonSelectedChange() |
125 | | - ' Change previously selected button back to default image |
126 | | - selectedButton = m.buttons.getChild(m.previouslySelectedButtonIndex) |
127 | | - selectedButton.uri = selectedButton.uri.Replace("-selected", "-default") |
| 146 | +' Event handler when user selects a button via OK key |
| 147 | +sub onButtonSelected() |
| 148 | + buttonIndex = m.buttons.buttonSelected |
| 149 | + button = m.buttons.getChild(buttonIndex) |
| 150 | + |
| 151 | + if not isValid(button) then return |
128 | 152 |
|
129 | | - ' Change selected button image to selected image |
130 | | - selectedButton = m.buttons.getChild(m.top.selectedButtonIndex) |
131 | | - selectedButton.uri = selectedButton.uri.Replace("-default", "-selected") |
| 153 | + if button.id = "play" |
| 154 | + playAction() |
| 155 | + else if button.id = "previous" |
| 156 | + previousClicked() |
| 157 | + else if button.id = "next" |
| 158 | + nextClicked() |
| 159 | + else if button.id = "shuffle" |
| 160 | + shuffleClicked() |
| 161 | + else if button.id = "repeat" |
| 162 | + loopClicked() |
| 163 | + end if |
132 | 164 | end sub |
133 | 165 |
|
134 | 166 | sub setupInfoNodes() |
135 | 167 | m.albumCover = m.top.findNode("albumCover") |
136 | 168 | m.backDrop = m.top.findNode("backdrop") |
| 169 | + m.backDropMask = m.top.findNode("backDropMask") |
137 | 170 | m.playPosition = m.top.findNode("playPosition") |
138 | 171 | m.bufferPosition = m.top.findNode("bufferPosition") |
139 | 172 | m.seekBar = m.top.findNode("seekBar") |
140 | 173 | m.thumb = m.top.findNode("thumb") |
141 | | - m.shuffleIndicator = m.top.findNode("shuffleIndicator") |
142 | | - m.loopIndicator = m.top.findNode("loopIndicator") |
143 | 174 | m.positionTimestamp = m.top.findNode("positionTimestamp") |
144 | 175 | m.seekPosition = m.top.findNode("seekPosition") |
145 | 176 | m.seekTimestamp = m.top.findNode("seekTimestamp") |
|
247 | 278 | end sub |
248 | 279 |
|
249 | 280 | sub audioStateChanged() |
| 281 | + ' Update play/pause button icon based on audio state |
| 282 | + if isValid(m.playButton) |
| 283 | + if m.audioPlayer.state = "playing" |
| 284 | + m.playButton.icon = "pkg:/images/icons/pause.png" |
| 285 | + else |
| 286 | + m.playButton.icon = "pkg:/images/icons/play.png" |
| 287 | + end if |
| 288 | + end if |
| 289 | + |
250 | 290 | ' Song Finished, attempt to move to next song |
251 | 291 | if m.audioPlayer.state = "finished" |
252 | 292 | ' User has enabled single song loop, play current song again |
|
279 | 319 |
|
280 | 320 | if m.audioPlayer.state = "playing" |
281 | 321 | m.audioPlayer.control = "pause" |
| 322 | + if isValid(m.playButton) |
| 323 | + m.playButton.icon = "pkg:/images/icons/play.png" |
| 324 | + end if |
282 | 325 | ' Allow screen to go to real screensaver |
283 | 326 | WriteAsciiFile("tmp:/scene.temp", "nowplaying-paused") |
284 | 327 | MoveFile("tmp:/scene.temp", "tmp:/scene") |
285 | 328 | else if m.audioPlayer.state = "paused" |
286 | 329 | m.audioPlayer.control = "resume" |
| 330 | + if isValid(m.playButton) |
| 331 | + m.playButton.icon = "pkg:/images/icons/pause.png" |
| 332 | + end if |
287 | 333 | ' Write screen tracker for screensaver |
288 | 334 | WriteAsciiFile("tmp:/scene.temp", "nowplaying") |
289 | 335 | MoveFile("tmp:/scene.temp", "tmp:/scene") |
290 | 336 | else if m.audioPlayer.state = "finished" |
291 | 337 | m.audioPlayer.control = "play" |
| 338 | + if isValid(m.playButton) |
| 339 | + m.playButton.icon = "pkg:/images/icons/pause.png" |
| 340 | + end if |
292 | 341 | ' Write screen tracker for screensaver |
293 | 342 | WriteAsciiFile("tmp:/scene.temp", "nowplaying") |
294 | 343 | MoveFile("tmp:/scene.temp", "tmp:/scene") |
|
355 | 404 | end function |
356 | 405 |
|
357 | 406 | sub setLoopButtonImage() |
| 407 | + if not isValid(m.repeatButton) then return |
| 408 | + |
358 | 409 | if m.audioPlayer.loopMode = "all" |
359 | | - m.loopIndicator.opacity = "1" |
360 | | - m.loopIndicator.uri = m.loopIndicator.uri.Replace("-off", "-on") |
| 410 | + ' Repeat all - use repeat.png with active state |
| 411 | + m.repeatButton.icon = "pkg:/images/icons/repeat.png" |
| 412 | + m.repeatButton.selected = true |
361 | 413 | else if m.audioPlayer.loopMode = "one" |
362 | | - m.loopIndicator.uri = m.loopIndicator.uri.Replace("-on", "1-on") |
| 414 | + ' Repeat one - use repeat-1.png with active state |
| 415 | + m.repeatButton.icon = "pkg:/images/icons/repeat-1.png" |
| 416 | + m.repeatButton.selected = true |
363 | 417 | else |
364 | | - m.loopIndicator.uri = m.loopIndicator.uri.Replace("1-on", "-off") |
| 418 | + ' Repeat off - use repeat.png without active state |
| 419 | + m.repeatButton.icon = "pkg:/images/icons/repeat.png" |
| 420 | + m.repeatButton.selected = false |
365 | 421 | end if |
366 | 422 | end sub |
367 | 423 |
|
|
424 | 480 | toggleShuffleEnabled() |
425 | 481 |
|
426 | 482 | if not m.queueManager.callFunc("getIsShuffled") |
427 | | - m.shuffleIndicator.opacity = ".4" |
428 | | - m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-on", "-off") |
| 483 | + ' Shuffle off |
| 484 | + if isValid(m.shuffleButton) |
| 485 | + m.shuffleButton.selected = false |
| 486 | + end if |
429 | 487 | m.queueManager.callFunc("setPosition", currentSongIndex) |
430 | 488 | setTrackNumberDisplay() |
431 | 489 | return true |
432 | 490 | end if |
433 | 491 |
|
434 | | - m.shuffleIndicator.opacity = "1" |
435 | | - m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-off", "-on") |
| 492 | + ' Shuffle on |
| 493 | + if isValid(m.shuffleButton) |
| 494 | + m.shuffleButton.selected = true |
| 495 | + end if |
436 | 496 | setTrackNumberDisplay() |
437 | 497 |
|
438 | 498 | return true |
439 | 499 | end function |
440 | 500 |
|
441 | 501 | sub setShuffleIconState() |
| 502 | + if not isValid(m.shuffleButton) then return |
| 503 | + |
442 | 504 | if m.queueManager.callFunc("getIsShuffled") |
443 | | - m.shuffleIndicator.opacity = "1" |
444 | | - m.shuffleIndicator.uri = m.shuffleIndicator.uri.Replace("-off", "-on") |
| 505 | + m.shuffleButton.selected = true |
| 506 | + else |
| 507 | + m.shuffleButton.selected = false |
445 | 508 | end if |
446 | 509 | end sub |
447 | 510 |
|
448 | 511 | sub setTrackNumberDisplay() |
449 | | - setFieldTextValue("numberofsongs", "Track " + stri(m.queueManager.callFunc("getPosition") + 1) + "/" + stri(m.queueManager.callFunc("getCount"))) |
| 512 | + setFieldTextValue("numberofsongs", tr("Track") + " " + tr("%1 of %2").Replace("%1", stri(m.queueManager.callFunc("getPosition") + 1).trim()).Replace("%2", stri(m.queueManager.callFunc("getCount")).trim())) |
450 | 513 | end sub |
451 | 514 |
|
452 | 515 | sub LoadNextSong() |
|
474 | 537 | m.LoadAudioStreamTask.control = "RUN" |
475 | 538 | end sub |
476 | 539 |
|
477 | | -' If we have more and 1 song to play, fade in the next and previous controls |
| 540 | +' If we have more than 1 song to play, set initial button states |
478 | 541 | sub loadButtons() |
479 | 542 | if m.queueManager.callFunc("getCount") > 1 |
480 | | - m.shuffleIndicator.opacity = ".4" |
481 | | - m.loopIndicator.opacity = ".4" |
482 | | - m.displayButtonsAnimation.control = "start" |
| 543 | + ' Set initial loop/repeat button state |
483 | 544 | setLoopButtonImage() |
484 | 545 | end if |
485 | 546 | end sub |
|
511 | 572 | useMetaTask = true |
512 | 573 | end if |
513 | 574 |
|
| 575 | + ' If we don't have enough data to populate the screen, load metadata |
514 | 576 | if useMetaTask |
515 | 577 | m.LoadMetaDataTask.itemId = currentItem.id |
516 | 578 | m.LoadMetaDataTask.observeField("content", "onMetaDataLoaded") |
517 | 579 | m.LoadMetaDataTask.control = "RUN" |
518 | 580 | else |
519 | | - if isValid(currentItem.ParentBackdropItemId) |
520 | | - setBackdropImage(ImageURL(currentItem.ParentBackdropItemId, "Backdrop", { "maxHeight": "720", "maxWidth": "1280" })) |
| 581 | + ' backdrop image |
| 582 | + if isValid(currentItem.ParentBackdropItemId) and isValid(currentItem.ParentBackdropImageTags) and currentItem.ParentBackdropImageTags.count() > 0 |
| 583 | + setBackdropImage(ImageURL(currentItem.ParentBackdropItemId, "Backdrop", { "maxHeight": "720", "maxWidth": "1280", "tag": currentItem.ParentBackdropImageTags[0] })) |
| 584 | + else |
| 585 | + ' no backdrop available, clear existing backdrop |
| 586 | + setBackdropImage("pkg:/images/1px-transparent.png") |
| 587 | + end if |
| 588 | + |
| 589 | + ' poster image |
| 590 | + if isValid(currentItem.AlbumId) and isValid(currentItem.AlbumPrimaryImageTag) and currentItem.AlbumPrimaryImageTag <> "" |
| 591 | + ' use primary album image |
| 592 | + setPosterImage(ImageURL(currentItem.AlbumId, "Primary", { "maxHeight": 500, "maxWidth": 500, "tag": currentItem.AlbumPrimaryImageTag })) |
| 593 | + else if isValid(currentItem.ImageTags) and currentItem.ImageTags["Primary"] <> invalid |
| 594 | + ' use primary poster image |
| 595 | + setPosterImage(ImageURL(currentItem.id, "Primary", { "maxHeight": 500, "maxWidth": 500, "tag": currentItem.ImageTags["Primary"] })) |
| 596 | + else |
| 597 | + ' no poster available, show placeholder image |
| 598 | + setPosterImage("pkg:/images/icons/album.png") |
521 | 599 | end if |
522 | 600 |
|
523 | | - setPosterImage(ImageURL(currentItem.id, "Primary", { "maxHeight": 500, "maxWidth": 500 })) |
524 | 601 | setScreenTitle(currentItem) |
525 | 602 | setOnScreenTextValues(currentItem) |
526 | 603 | m.songDuration = currentItem.RunTimeTicks / 10000000.0 |
|
571 | 648 | sub setPosterImage(posterURL) |
572 | 649 | if isValid(posterURL) |
573 | 650 | if m.albumCover.uri <> posterURL |
| 651 | + if posterURL = "pkg:/images/icons/album.png" |
| 652 | + constants = m.global.constants |
| 653 | + ' color the white album placeholder to match theme |
| 654 | + m.albumCover.blendColor = constants.colorTextSecondary |
| 655 | + m.screenSaverAlbumCover.blendColor = constants.colorTextSecondary |
| 656 | + else |
| 657 | + ' reset blend color to default white |
| 658 | + m.albumCover.blendColor = "0xFFFFFFFF" |
| 659 | + m.screenSaverAlbumCover.blendColor = "0xFFFFFFFF" |
| 660 | + end if |
| 661 | + |
574 | 662 | m.albumCover.uri = posterURL |
575 | 663 | m.screenSaverAlbumCover.uri = posterURL |
576 | 664 | end if |
|
618 | 706 | end if |
619 | 707 | end sub |
620 | 708 |
|
621 | | -' setSelectedButtonState: Changes the icon state url for the currently selected button |
622 | | -' |
623 | | -' @param {string} oldState - current state to replace in icon url |
624 | | -' @param {string} newState - state to replace {oldState} with in icon url |
625 | | -sub setSelectedButtonState(oldState as string, newState as string) |
626 | | - selectedButton = m.buttons.getChild(m.top.selectedButtonIndex) |
627 | | - selectedButton.uri = selectedButton.uri.Replace(oldState, newState) |
628 | | -end sub |
629 | | - |
630 | 709 | ' processScrubAction: Handles +/- seeking for the audio trickplay bar |
631 | 710 | ' |
632 | 711 | ' @param {integer} seekStep - seconds to move the trickplay position (negative values allowed) |
|
694 | 773 | ' exitScrubMode: Moves player out of scrub mode state, resets back to standard play mode |
695 | 774 | ' |
696 | 775 | sub exitScrubMode() |
697 | | - m.buttons.setFocus(true) |
| 776 | + m.buttons.callFunc("focus") |
698 | 777 | m.thumb.setFocus(false) |
699 | 778 |
|
700 | 779 | if m.seekPosition.visible |
|
705 | 784 |
|
706 | 785 | m.inScrubMode = false |
707 | 786 | m.thumb.visible = false |
708 | | - setSelectedButtonState("-default", "-selected") |
709 | 787 | end sub |
710 | 788 |
|
711 | 789 | ' Process key press events |
|
752 | 830 | if key = "up" |
753 | 831 | if not m.thumb.visible |
754 | 832 | m.thumb.visible = true |
755 | | - setSelectedButtonState("-selected", "-default") |
756 | 833 | end if |
757 | 834 | if not m.seekPosition.visible |
758 | 835 | m.seekPosition.visible = true |
|
777 | 854 | return previousClicked() |
778 | 855 | else if key = "fastforward" |
779 | 856 | return nextClicked() |
780 | | - else if key = "left" |
781 | | - if m.buttons.hasFocus() |
782 | | - if m.queueManager.callFunc("getCount") = 1 then return false |
783 | | - |
784 | | - if m.top.selectedButtonIndex > 0 |
785 | | - m.previouslySelectedButtonIndex = m.top.selectedButtonIndex |
786 | | - m.top.selectedButtonIndex = m.top.selectedButtonIndex - 1 |
787 | | - end if |
788 | | - return true |
789 | | - end if |
790 | | - else if key = "right" |
791 | | - if m.buttons.hasFocus() |
792 | | - if m.queueManager.callFunc("getCount") = 1 then return false |
793 | | - |
794 | | - m.previouslySelectedButtonIndex = m.top.selectedButtonIndex |
795 | | - if m.top.selectedButtonIndex < m.buttonCount - 1 then m.top.selectedButtonIndex = m.top.selectedButtonIndex + 1 |
796 | | - return true |
797 | | - end if |
798 | | - else if key = "OK" |
799 | | - if m.buttons.hasFocus() |
800 | | - if m.buttons.getChild(m.top.selectedButtonIndex).id = "play" |
801 | | - return playAction() |
802 | | - else if m.buttons.getChild(m.top.selectedButtonIndex).id = "previous" |
803 | | - return previousClicked() |
804 | | - else if m.buttons.getChild(m.top.selectedButtonIndex).id = "next" |
805 | | - return nextClicked() |
806 | | - else if m.buttons.getChild(m.top.selectedButtonIndex).id = "shuffle" |
807 | | - return shuffleClicked() |
808 | | - else if m.buttons.getChild(m.top.selectedButtonIndex).id = "loop" |
809 | | - return loopClicked() |
810 | | - end if |
811 | | - end if |
812 | 857 | end if |
813 | 858 | end if |
814 | 859 |
|
|
0 commit comments