├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ └── feature_request.yml
├── workflows
│ └── test.yml
└── pull_request_template.md
├── components
├── config
│ ├── ConfigData.bs
│ ├── ServerDiscoveryTask.xml
│ ├── FormList.xml
│ ├── ConfigData.xml
│ ├── FormElement.xml
│ ├── FormElement.bs
│ └── JFServer.xml
├── data
│ ├── PublicUserData.bs
│ ├── OptionsButton.bs
│ ├── AlbumData.bs
│ ├── MusicArtistData.xml
│ ├── MusicAlbumSongListData.xml
│ ├── JFContentItem.bs
│ ├── ServerData.bs
│ ├── ImageData.bs
│ ├── OptionsButton.xml
│ ├── GetFiltersTask.xml
│ ├── PhotoData.xml
│ ├── TVEpisode.xml
│ ├── AudioStreamData.xml
│ ├── ServerData.xml
│ ├── VideoData.xml
│ ├── ColorOptionData.xml
│ ├── PublicUserData.xml
│ ├── AlbumData.xml
│ ├── GetFiltersTask.bs
│ ├── FolderData.xml
│ ├── PersonData.xml
│ ├── ChannelData.xml
│ ├── GetPlaylistDataTask.bs
│ ├── MusicAlbumData.xml
│ ├── PlaylistData.bs
│ ├── PlaylistItemData.bs
│ ├── GetPlaylistDataTask.xml
│ ├── TVSeasonData.bs
│ ├── CollectionData.xml
│ ├── SeriesData.xml
│ ├── MusicAlbumData.bs
│ ├── MusicSongData.bs
│ ├── TVSeasonData.xml
│ ├── PlaylistData.xml
│ ├── ImageData.xml
│ ├── OptionsData.xml
│ ├── MusicSongData.xml
│ ├── UserData.xml
│ ├── RecordingData.bs
│ ├── MovieData.xml
│ ├── TVEpisodeData.bs
│ ├── JFContentItem.xml
│ ├── VideoData.bs
│ ├── TVEpisodeData.xml
│ ├── RecordingData.xml
│ ├── ScheduleProgramData.xml
│ ├── ExtrasData.xml
│ ├── HomeData.xml
│ ├── SearchData.xml
│ ├── ChannelData.bs
│ ├── PlaylistItemData.xml
│ ├── SceneManager.xml
│ ├── OptionsData.bs
│ ├── TVEpisode.bs
│ ├── MusicAlbumSongListData.bs
│ ├── PhotoData.bs
│ ├── PersonData.bs
│ ├── MusicArtistData.bs
│ ├── UserData.bs
│ └── ScheduleProgramData.bs
├── options
│ ├── OptionNode.bs
│ ├── OptionNode.xml
│ ├── OptionsSlider.xml
│ └── OptionsSlider.bs
├── Buttons
│ ├── ButtonData.bs
│ ├── TextSizeTask.bs
│ ├── TextSizeTask.xml
│ ├── SlideOutButton.xml
│ ├── ButtonData.xml
│ ├── ExpandingLabel.xml
│ └── JFButtons.xml
├── .DS_Store
├── labels
│ ├── Text.xml
│ ├── ScrollingText.xml
│ ├── Text.bs
│ └── ScrollingText.bs
├── Spinner.xml
├── settings
│ ├── setting.xml
│ ├── Slider.xml
│ ├── ColorGrid.bs
│ └── settings.xml
├── Spinner.bs
├── JFGroup.bs
├── JFButton.xml
├── WhatsNewDialog.xml
├── ButtonGroupHoriz.xml
├── JFScreen.xml
├── tvshows
│ ├── TVSeasonRow.xml
│ ├── TVExtrasTask.xml
│ ├── TVEpisodeRow.xml
│ ├── TVExtrasTask.bs
│ ├── TVEpisodes.xml
│ ├── TVListOptions.xml
│ └── TVSeasonRow.bs
├── music
│ ├── LoadScreenSaverTimeoutTask.xml
│ ├── SimilarArtistGrid.xml
│ ├── LoadScreenSaverTimeoutTask.bs
│ ├── PlaylistItems.xml
│ ├── AlbumTrackList.xml
│ ├── AlbumGrid.xml
│ ├── PlaylistItems.bs
│ ├── SongItem.bs
│ ├── SongItem.xml
│ ├── AlbumTrackList.bs
│ ├── SimilarArtistGrid.bs
│ ├── Lyrics.xml
│ ├── PlaylistItem.xml
│ ├── AlbumGrid.bs
│ ├── PlaylistView.xml
│ └── AlbumView.xml
├── search
│ ├── SearchTask.xml
│ ├── SearchRow.xml
│ ├── SearchTask.bs
│ └── SearchResults.xml
├── PlaystateTask.xml
├── GetPlaybackInfoTask.xml
├── ItemGrid
│ ├── FavoriteItemsTask.xml
│ ├── AlphaItem.bs
│ ├── AlphaItem.xml
│ ├── FavoriteItemsTask.bs
│ ├── ColorOption.bs
│ ├── ColorOption.xml
│ ├── MusicArtistGridItem.xml
│ ├── GridItemSmall.xml
│ ├── LoadVideoContentTask.xml
│ ├── LoadItemsTask2.xml
│ ├── GridItem.xml
│ ├── GridItemMedium.xml
│ └── AudioBookGridItem.xml
├── GetShuffleEpisodesTask.xml
├── login
│ ├── UserRow.xml
│ ├── UserItem.xml
│ ├── UserRow.bs
│ └── UserItem.bs
├── ButtonGroupVert.xml
├── StandardDialog.xml
├── photos
│ ├── LoadPhotoTask.xml
│ ├── LoadPhotoTask.bs
│ └── PhotoDetails.xml
├── quickConnect
│ ├── QuickConnect.xml
│ ├── QuickConnectDialog.xml
│ └── QuickConnect.bs
├── GetNextEpisodeTask.xml
├── home
│ ├── HomeRow.xml
│ ├── HomeRows.xml
│ ├── LoadItemsTask.xml
│ ├── Home.xml
│ └── HomeItem.xml
├── subtitle
│ ├── SubtitleData.xml
│ ├── SubtitleItem.xml
│ └── SubtitleItem.bs
├── movies
│ ├── AudioTrackListData.xml
│ ├── VideoTrackListData.xml
│ ├── SubtitleTrackListData.xml
│ ├── AudioTrackListItem.xml
│ ├── VideoTrackListItem.xml
│ ├── AudioTrackListItem.bs
│ └── VideoTrackListItem.bs
├── liveTv
│ ├── RecordProgramTask.xml
│ ├── LoadSheduleTask.xml
│ ├── LoadProgramDetailsTask.xml
│ ├── LoadChannelsTask.xml
│ ├── ChannelInfo.xml
│ ├── schedule.xml
│ ├── LoadSheduleTask.bs
│ └── LoadProgramDetailsTask.bs
├── SearchBox.xml
├── keyboards
│ └── IntegerKeyboard.xml
├── RadioDialog.xml
├── video
│ ├── PreloadTrickplayImagesTask.xml
│ └── PreloadTrickplayImagesTask.bs
├── RemoteSubtitleDialog.xml
├── GetShuffleEpisodesTask.bs
├── JFGroup.xml
├── Clock.xml
├── GetNextEpisodeTask.bs
├── section
│ └── sectionScroller.xml
├── mediaPlayers
│ └── AudioPlayer.xml
├── captionTask.xml
├── extras
│ ├── ExtrasRowList.xml
│ ├── ExtrasItem.xml
│ └── ExtrasSlider.xml
├── tasks
│ └── PostTask.xml
├── PlayedCheckmark.xml
├── JFButton.bs
├── ButtonGroupHoriz.bs
├── JFMessageDialog.xml
├── BaseScene.xml
├── JFScreen.bs
├── SettingDialog.xml
├── StandardButton.xml
├── ListPoster.xml
├── IconButton.xml
├── SearchBox.bs
├── TextButton.xml
├── MovieDetailButton.xml
├── Libraries
│ ├── LiveTVLibraryView.xml
│ └── AudioBookLibraryView.xml
├── StandardDialog.bs
├── ButtonGroupVert.bs
├── PlaystateTask.bs
└── StandardButton.bs
├── source
├── enums
│ ├── String.bs
│ ├── MediaStreamType.bs
│ ├── AnimationControl.bs
│ ├── MenuType.bs
│ ├── TaskControl.bs
│ ├── SubtitleSelection.bs
│ ├── TimerControl.bs
│ ├── CaptionMoveDirection.bs
│ ├── LiveTVScreen.bs
│ ├── SearchButtonState.bs
│ ├── SeriesStatus.bs
│ ├── ViewLoadStatus.bs
│ ├── PersonType.bs
│ ├── AnimationState.bs
│ ├── ImageLayout.bs
│ ├── MediaSegmentAction.bs
│ ├── VideoControl.bs
│ ├── ResumePopupAction.bs
│ ├── VideoType.bs
│ ├── PosterLoadStatus.bs
│ ├── SettingType.bs
│ ├── MediaSegmentType.bs
│ ├── PlaybackMethod.bs
│ ├── MediaPlaybackState.bs
│ ├── KeyCode.bs
│ ├── CollectionType.bs
│ ├── ImageType.bs
│ ├── ColorPalette.bs
│ └── ItemType.bs
├── constants
│ └── HomeRowItemSizes.bs
├── static
│ └── whatsNew
│ │ └── 3.1.0.json
└── utils
│ └── parsedUrl.bs
├── bslint.json
├── images
├── logo.png
├── red.png
├── fresh.png
├── rotten.png
├── white.png
├── dialog.9.png
├── icons
│ ├── cc.png
│ ├── cd.png
│ ├── album.png
│ ├── check.png
│ ├── heart.png
│ ├── info.png
│ ├── loop.png
│ ├── next.png
│ ├── pause.png
│ ├── play.png
│ ├── plus.png
│ ├── star.png
│ ├── stop.png
│ ├── trash.png
│ ├── cancel.png
│ ├── cassette.png
│ ├── circle.png
│ ├── circle64.png
│ ├── details.png
│ ├── favorite.png
│ ├── itemNext.png
│ ├── mic_icon.png
│ ├── options.png
│ ├── previous.png
│ ├── puzzle.png
│ ├── settings.png
│ ├── shuffle.png
│ ├── subtitle.png
│ ├── up_black.png
│ ├── up_white.png
│ ├── down_black.png
│ ├── down_white.png
│ ├── instantMix.png
│ ├── musicNote.png
│ ├── numberList.png
│ ├── openCircle.png
│ ├── playPause.png
│ ├── trash-red.png
│ ├── userGroup.png
│ ├── videoInfo.png
│ ├── Search-dark.png
│ ├── addToMyList.png
│ ├── check_black.png
│ ├── check_white.png
│ ├── dropdown-dark.png
│ ├── itemPrevious.png
│ ├── musicFolder.png
│ ├── nextChapter.png
│ ├── search-light.png
│ ├── checkboxChecked.png
│ ├── dropdown-light.png
│ ├── previousChapter.png
│ ├── checkboxUnchecked.png
│ ├── favorite_selected.png
│ ├── loopindicator-off.png
│ ├── loopindicator-on.png
│ ├── loopindicator1-on.png
│ ├── shuffleIndicator-off.png
│ └── shuffleIndicator-on.png
├── spinner.png
├── white.9.png
├── fhd_focus.9.png
├── hd_focus.9.png
├── postermask.png
├── transparent.png
├── background
│ ├── bg1.jpg
│ └── bg2.jpg
├── backgroundmask.png
├── logo-icon120.jpg
├── missingArtist.png
├── osdBackground.png
├── option-menu-bg.9.png
├── splash-screen_hd.png
├── splash-screen_sd.png
├── channel-poster_fhd.png
├── channel-poster_hd.png
├── channel-poster_sd.png
├── media_type_icons
│ ├── tv.png
│ ├── movie.png
│ ├── photo.png
│ ├── folder_white.png
│ ├── music_white.png
│ ├── photoFolder.png
│ └── live_tv_white.png
├── splash-screen_fhd.png
├── channel-poster_hd_dev.png
├── channel-poster_sd_dev.png
├── sharp_star_white_18dp.png
├── channel-poster_fhd_dev.png
└── baseline_person_white_48dp.png
├── screenshots
├── home.jpg
├── tvLibrary.jpg
├── movieDetails.jpg
└── musicPlayback.jpg
├── .gitignore
├── renovate.json
├── dictionary.txt
├── PRIVACY.md
├── bsfmt.json
├── bsconfig.json
├── .vscode
├── settings.json
├── extensions.json
├── tasks.json
└── launch.json
├── bsconfig-tests.json
├── manifest
└── package.json
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @jellyfin/roku
2 |
--------------------------------------------------------------------------------
/components/config/ConfigData.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | end sub
3 |
--------------------------------------------------------------------------------
/components/data/PublicUserData.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | end sub
3 |
--------------------------------------------------------------------------------
/components/options/OptionNode.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | end sub
3 |
--------------------------------------------------------------------------------
/source/enums/String.bs:
--------------------------------------------------------------------------------
1 | enum String
2 | EMPTY = ""
3 | end enum
4 |
--------------------------------------------------------------------------------
/bslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "unused-variable": "warn"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/logo.png
--------------------------------------------------------------------------------
/images/red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/red.png
--------------------------------------------------------------------------------
/components/Buttons/ButtonData.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.iconSide = "left"
3 | end sub
4 |
--------------------------------------------------------------------------------
/images/fresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/fresh.png
--------------------------------------------------------------------------------
/images/rotten.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/rotten.png
--------------------------------------------------------------------------------
/images/white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/white.png
--------------------------------------------------------------------------------
/source/enums/MediaStreamType.bs:
--------------------------------------------------------------------------------
1 | enum MediaStreamType
2 | VIDEO = "video"
3 | end enum
4 |
--------------------------------------------------------------------------------
/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/components/.DS_Store
--------------------------------------------------------------------------------
/images/dialog.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/dialog.9.png
--------------------------------------------------------------------------------
/images/icons/cc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/cc.png
--------------------------------------------------------------------------------
/images/icons/cd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/cd.png
--------------------------------------------------------------------------------
/images/spinner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/spinner.png
--------------------------------------------------------------------------------
/images/white.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/white.9.png
--------------------------------------------------------------------------------
/screenshots/home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/screenshots/home.jpg
--------------------------------------------------------------------------------
/source/enums/AnimationControl.bs:
--------------------------------------------------------------------------------
1 | enum AnimationControl
2 | START = "start"
3 | end enum
4 |
--------------------------------------------------------------------------------
/source/enums/MenuType.bs:
--------------------------------------------------------------------------------
1 | enum MenuType
2 | VIEW
3 | SORT
4 | FILTER
5 | end enum
6 |
--------------------------------------------------------------------------------
/images/fhd_focus.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/fhd_focus.9.png
--------------------------------------------------------------------------------
/images/hd_focus.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/hd_focus.9.png
--------------------------------------------------------------------------------
/images/icons/album.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/album.png
--------------------------------------------------------------------------------
/images/icons/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/check.png
--------------------------------------------------------------------------------
/images/icons/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/heart.png
--------------------------------------------------------------------------------
/images/icons/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/info.png
--------------------------------------------------------------------------------
/images/icons/loop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/loop.png
--------------------------------------------------------------------------------
/images/icons/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/next.png
--------------------------------------------------------------------------------
/images/icons/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/pause.png
--------------------------------------------------------------------------------
/images/icons/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/play.png
--------------------------------------------------------------------------------
/images/icons/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/plus.png
--------------------------------------------------------------------------------
/images/icons/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/star.png
--------------------------------------------------------------------------------
/images/icons/stop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/stop.png
--------------------------------------------------------------------------------
/images/icons/trash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/trash.png
--------------------------------------------------------------------------------
/images/postermask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/postermask.png
--------------------------------------------------------------------------------
/images/transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/transparent.png
--------------------------------------------------------------------------------
/source/enums/TaskControl.bs:
--------------------------------------------------------------------------------
1 | enum TaskControl
2 | RUN = "RUN"
3 | STOP = "STOP"
4 | end enum
5 |
--------------------------------------------------------------------------------
/images/background/bg1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/background/bg1.jpg
--------------------------------------------------------------------------------
/images/background/bg2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/background/bg2.jpg
--------------------------------------------------------------------------------
/images/backgroundmask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/backgroundmask.png
--------------------------------------------------------------------------------
/images/icons/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/cancel.png
--------------------------------------------------------------------------------
/images/icons/cassette.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/cassette.png
--------------------------------------------------------------------------------
/images/icons/circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/circle.png
--------------------------------------------------------------------------------
/images/icons/circle64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/circle64.png
--------------------------------------------------------------------------------
/images/icons/details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/details.png
--------------------------------------------------------------------------------
/images/icons/favorite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/favorite.png
--------------------------------------------------------------------------------
/images/icons/itemNext.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/itemNext.png
--------------------------------------------------------------------------------
/images/icons/mic_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/mic_icon.png
--------------------------------------------------------------------------------
/images/icons/options.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/options.png
--------------------------------------------------------------------------------
/images/icons/previous.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/previous.png
--------------------------------------------------------------------------------
/images/icons/puzzle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/puzzle.png
--------------------------------------------------------------------------------
/images/icons/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/settings.png
--------------------------------------------------------------------------------
/images/icons/shuffle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/shuffle.png
--------------------------------------------------------------------------------
/images/icons/subtitle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/subtitle.png
--------------------------------------------------------------------------------
/images/icons/up_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/up_black.png
--------------------------------------------------------------------------------
/images/icons/up_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/up_white.png
--------------------------------------------------------------------------------
/images/logo-icon120.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/logo-icon120.jpg
--------------------------------------------------------------------------------
/images/missingArtist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/missingArtist.png
--------------------------------------------------------------------------------
/images/osdBackground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/osdBackground.png
--------------------------------------------------------------------------------
/screenshots/tvLibrary.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/screenshots/tvLibrary.jpg
--------------------------------------------------------------------------------
/images/icons/down_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/down_black.png
--------------------------------------------------------------------------------
/images/icons/down_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/down_white.png
--------------------------------------------------------------------------------
/images/icons/instantMix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/instantMix.png
--------------------------------------------------------------------------------
/images/icons/musicNote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/musicNote.png
--------------------------------------------------------------------------------
/images/icons/numberList.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/numberList.png
--------------------------------------------------------------------------------
/images/icons/openCircle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/openCircle.png
--------------------------------------------------------------------------------
/images/icons/playPause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/playPause.png
--------------------------------------------------------------------------------
/images/icons/trash-red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/trash-red.png
--------------------------------------------------------------------------------
/images/icons/userGroup.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/userGroup.png
--------------------------------------------------------------------------------
/images/icons/videoInfo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/videoInfo.png
--------------------------------------------------------------------------------
/images/option-menu-bg.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/option-menu-bg.9.png
--------------------------------------------------------------------------------
/images/splash-screen_hd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/splash-screen_hd.png
--------------------------------------------------------------------------------
/images/splash-screen_sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/splash-screen_sd.png
--------------------------------------------------------------------------------
/source/enums/SubtitleSelection.bs:
--------------------------------------------------------------------------------
1 | enum SubtitleSelection
2 | NOTSET = -2
3 | NONE = -1
4 | end enum
5 |
--------------------------------------------------------------------------------
/source/enums/TimerControl.bs:
--------------------------------------------------------------------------------
1 | enum TimerControl
2 | START = "start"
3 | STOP = "stop"
4 | end enum
5 |
--------------------------------------------------------------------------------
/images/channel-poster_fhd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/channel-poster_fhd.png
--------------------------------------------------------------------------------
/images/channel-poster_hd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/channel-poster_hd.png
--------------------------------------------------------------------------------
/images/channel-poster_sd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/channel-poster_sd.png
--------------------------------------------------------------------------------
/images/icons/Search-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/Search-dark.png
--------------------------------------------------------------------------------
/images/icons/addToMyList.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/addToMyList.png
--------------------------------------------------------------------------------
/images/icons/check_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/check_black.png
--------------------------------------------------------------------------------
/images/icons/check_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/check_white.png
--------------------------------------------------------------------------------
/images/icons/dropdown-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/dropdown-dark.png
--------------------------------------------------------------------------------
/images/icons/itemPrevious.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/itemPrevious.png
--------------------------------------------------------------------------------
/images/icons/musicFolder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/musicFolder.png
--------------------------------------------------------------------------------
/images/icons/nextChapter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/nextChapter.png
--------------------------------------------------------------------------------
/images/icons/search-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/search-light.png
--------------------------------------------------------------------------------
/images/media_type_icons/tv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/media_type_icons/tv.png
--------------------------------------------------------------------------------
/images/splash-screen_fhd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/splash-screen_fhd.png
--------------------------------------------------------------------------------
/screenshots/movieDetails.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/screenshots/movieDetails.jpg
--------------------------------------------------------------------------------
/screenshots/musicPlayback.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/screenshots/musicPlayback.jpg
--------------------------------------------------------------------------------
/source/enums/CaptionMoveDirection.bs:
--------------------------------------------------------------------------------
1 | enum CaptionMoveDirection
2 | NOTSET
3 | UP
4 | DOWN
5 | end enum
6 |
--------------------------------------------------------------------------------
/source/enums/LiveTVScreen.bs:
--------------------------------------------------------------------------------
1 | enum LiveTVScreen
2 | CHANNELS = "channels"
3 | GUIDE = "guide"
4 | end enum
5 |
--------------------------------------------------------------------------------
/images/channel-poster_hd_dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/channel-poster_hd_dev.png
--------------------------------------------------------------------------------
/images/channel-poster_sd_dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/channel-poster_sd_dev.png
--------------------------------------------------------------------------------
/images/icons/checkboxChecked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/checkboxChecked.png
--------------------------------------------------------------------------------
/images/icons/dropdown-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/dropdown-light.png
--------------------------------------------------------------------------------
/images/icons/previousChapter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/previousChapter.png
--------------------------------------------------------------------------------
/images/sharp_star_white_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/sharp_star_white_18dp.png
--------------------------------------------------------------------------------
/source/enums/SearchButtonState.bs:
--------------------------------------------------------------------------------
1 | enum SearchButtonState
2 | ACTIVE
3 | INACTIVE
4 | DISABLED
5 | end enum
6 |
--------------------------------------------------------------------------------
/source/enums/SeriesStatus.bs:
--------------------------------------------------------------------------------
1 | enum SeriesStatus
2 | CONTINUING = "continuing"
3 | ENDED = "ended"
4 | end enum
5 |
--------------------------------------------------------------------------------
/images/channel-poster_fhd_dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/channel-poster_fhd_dev.png
--------------------------------------------------------------------------------
/images/icons/checkboxUnchecked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/checkboxUnchecked.png
--------------------------------------------------------------------------------
/images/icons/favorite_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/favorite_selected.png
--------------------------------------------------------------------------------
/images/icons/loopindicator-off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/loopindicator-off.png
--------------------------------------------------------------------------------
/images/icons/loopindicator-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/loopindicator-on.png
--------------------------------------------------------------------------------
/images/icons/loopindicator1-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/loopindicator1-on.png
--------------------------------------------------------------------------------
/images/media_type_icons/movie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/media_type_icons/movie.png
--------------------------------------------------------------------------------
/images/media_type_icons/photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/media_type_icons/photo.png
--------------------------------------------------------------------------------
/source/enums/ViewLoadStatus.bs:
--------------------------------------------------------------------------------
1 | enum ViewLoadStatus
2 | INIT
3 | FIRSTLOAD
4 | RELOAD
5 | LOADED
6 | end enum
7 |
--------------------------------------------------------------------------------
/components/labels/Text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/images/baseline_person_white_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/baseline_person_white_48dp.png
--------------------------------------------------------------------------------
/images/icons/shuffleIndicator-off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/shuffleIndicator-off.png
--------------------------------------------------------------------------------
/images/icons/shuffleIndicator-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/icons/shuffleIndicator-on.png
--------------------------------------------------------------------------------
/components/Spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/components/data/OptionsButton.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/config.bs"
2 |
3 | sub init()
4 | end sub
5 |
6 | sub press()
7 | end sub
8 |
--------------------------------------------------------------------------------
/images/media_type_icons/folder_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/media_type_icons/folder_white.png
--------------------------------------------------------------------------------
/images/media_type_icons/music_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/media_type_icons/music_white.png
--------------------------------------------------------------------------------
/images/media_type_icons/photoFolder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/media_type_icons/photoFolder.png
--------------------------------------------------------------------------------
/components/labels/ScrollingText.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/images/media_type_icons/live_tv_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jellyfin/jellyfin-roku/HEAD/images/media_type_icons/live_tv_white.png
--------------------------------------------------------------------------------
/source/enums/PersonType.bs:
--------------------------------------------------------------------------------
1 | enum PersonType
2 | DIRECTOR = "director"
3 | ACTOR = "actor"
4 | PERSON = "person"
5 | end enum
6 |
--------------------------------------------------------------------------------
/source/enums/AnimationState.bs:
--------------------------------------------------------------------------------
1 | enum AnimationState
2 | PAUSED = "paused"
3 | RUNNING = "running"
4 | STOPPED = "stopped"
5 | end enum
6 |
--------------------------------------------------------------------------------
/source/enums/ImageLayout.bs:
--------------------------------------------------------------------------------
1 | enum ImageLayout
2 | DEFAULT = "default"
3 | LANDSCAPE = "landscape"
4 | PORTRAIT = "portrait"
5 | end enum
6 |
--------------------------------------------------------------------------------
/source/enums/MediaSegmentAction.bs:
--------------------------------------------------------------------------------
1 | enum MediaSegmentAction
2 | ASKTOSKIP = "asktoskip"
3 | SKIP = "skip"
4 | NONE = "none"
5 | end enum
6 |
--------------------------------------------------------------------------------
/components/data/AlbumData.bs:
--------------------------------------------------------------------------------
1 | sub setFields()
2 | datum = m.top.json
3 |
4 | m.top.id = datum.id
5 | m.top.title = datum.name
6 | end sub
7 |
8 |
--------------------------------------------------------------------------------
/components/data/MusicArtistData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/source/enums/VideoControl.bs:
--------------------------------------------------------------------------------
1 | enum VideoControl
2 | STOP = "STOP"
3 | RESUME = "RESUME"
4 | PAUSE = "PAUSE"
5 | PLAY = "PLAY"
6 | end enum
7 |
--------------------------------------------------------------------------------
/source/enums/ResumePopupAction.bs:
--------------------------------------------------------------------------------
1 | enum ResumePopupAction
2 | RESUME
3 | STARTOVER
4 | GOTOSERIES
5 | GOTOSEASON
6 | GOTOEPISODE
7 | end enum
8 |
--------------------------------------------------------------------------------
/source/enums/VideoType.bs:
--------------------------------------------------------------------------------
1 | enum VideoType
2 | EPISODE = "episode"
3 | SERIES = "series"
4 | VIDEO = "video"
5 | VIDEOFILE = "videofile"
6 | end enum
7 |
--------------------------------------------------------------------------------
/components/data/MusicAlbumSongListData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/source/enums/PosterLoadStatus.bs:
--------------------------------------------------------------------------------
1 | enum PosterLoadStatus
2 | NONE = "none"
3 | LOADING = "loading"
4 | READY = "ready"
5 | FAILED = "failed"
6 | end enum
7 |
--------------------------------------------------------------------------------
/components/settings/setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/source/enums/SettingType.bs:
--------------------------------------------------------------------------------
1 | enum SettingType
2 | BOOL = "bool"
3 | COLORGRID = "colorgrid"
4 | integer = "integer"
5 | RADIO = "radio"
6 | SLIDER = "slider"
7 | end enum
8 |
--------------------------------------------------------------------------------
/components/Spinner.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.poster.uri = "pkg:/images/spinner.png"
3 | m.top.control = "start"
4 | m.top.clockwise = true
5 | m.top.spinInterval = 1
6 | end sub
7 |
--------------------------------------------------------------------------------
/components/JFGroup.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | end sub
3 |
4 | function onKeyEvent(key as string, press as boolean) as boolean
5 | if not press then return false
6 |
7 | return false
8 | end function
9 |
--------------------------------------------------------------------------------
/components/data/JFContentItem.bs:
--------------------------------------------------------------------------------
1 | ' Called whenever m.top.json changes.
2 | ' It is expected that each node that extends JFContentItem will override this function
3 | sub setFields()
4 |
5 | end sub
6 |
--------------------------------------------------------------------------------
/components/data/ServerData.bs:
--------------------------------------------------------------------------------
1 | ' Called whenever m.top.json changes.
2 | ' It is expected that each node that extends JFContentItem will override this function
3 | sub setFields()
4 |
5 | end sub
6 |
--------------------------------------------------------------------------------
/components/JFButton.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/data/ImageData.bs:
--------------------------------------------------------------------------------
1 | sub setFields()
2 | json = m.top.json
3 | m.top.imagetype = json.imagetype
4 | m.top.size = json.size
5 | m.top.height = json.height
6 | m.top.width = json.width
7 | end sub
8 |
--------------------------------------------------------------------------------
/components/WhatsNewDialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/source/enums/MediaSegmentType.bs:
--------------------------------------------------------------------------------
1 | enum MediaSegmentType
2 | UNKNOWN = "unknown"
3 | COMMERCIAL = "commercial"
4 | PREVIEW = "preview"
5 | RECAP = "recap"
6 | OUTRO = "outro"
7 | INTRO = "intro"
8 | end enum
9 |
--------------------------------------------------------------------------------
/source/enums/PlaybackMethod.bs:
--------------------------------------------------------------------------------
1 | enum PlaybackMethod
2 | PLAYNORMALLY = "playNormally"
3 | FORCETRANSCODEALLOWREMUX = "forceTranscodeAllowRemux"
4 | FORCETRANSCODEDISABLEREMUX = "forceTranscodeDisableRemux"
5 | end enum
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.svg
2 | .env
3 | # BrightScript
4 | dist/apps
5 | out/
6 | build/
7 | roku_modules
8 | source/globals.brs
9 | jellyfin-roku.zip
10 | # NPM
11 | node_modules/
12 | # Eclipse
13 | .buildpath
14 | .project
15 | .settings
--------------------------------------------------------------------------------
/components/config/ServerDiscoveryTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/source/enums/MediaPlaybackState.bs:
--------------------------------------------------------------------------------
1 | enum MediaPlaybackState
2 | BUFFERING = "buffering"
3 | ERROR = "error"
4 | FINISHED = "finished"
5 | PAUSED = "paused"
6 | PLAYING = "playing"
7 | STOPPED = "stopped"
8 | end enum
9 |
--------------------------------------------------------------------------------
/components/config/FormList.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/ButtonGroupHoriz.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/JFScreen.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/tvshows/TVSeasonRow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/music/LoadScreenSaverTimeoutTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/music/SimilarArtistGrid.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/data/OptionsButton.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/components/search/SearchTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/source/constants/HomeRowItemSizes.bs:
--------------------------------------------------------------------------------
1 | ' @fileoverview Constants for rowItemSize on the home view
2 |
3 | namespace homeRowItemSizes
4 | const WIDE_POSTER = [464, 331]
5 | const MOVIE_POSTER = [180, 331]
6 | const MUSIC_ALBUM = [261, 331]
7 | end namespace
8 |
--------------------------------------------------------------------------------
/components/PlaystateTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/tvshows/TVExtrasTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/music/LoadScreenSaverTimeoutTask.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.functionName = "getScreensaverTimeout"
3 | end sub
4 |
5 | sub getScreensaverTimeout()
6 | appinfo = CreateObject("roAppManager")
7 | m.top.content = appinfo.GetScreensaverTimeout() * 60
8 | end sub
9 |
--------------------------------------------------------------------------------
/components/data/GetFiltersTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/data/PhotoData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/data/TVEpisode.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/GetPlaybackInfoTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/ItemGrid/FavoriteItemsTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/GetShuffleEpisodesTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/data/AudioStreamData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/data/ServerData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/config/ConfigData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/data/VideoData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/login/UserRow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:recommended", ":disableDependencyDashboard"],
4 | "packageRules": [
5 | {
6 | "matchUpdateTypes": ["minor", "patch"],
7 | "automerge": true
8 | }
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/components/data/ColorOptionData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/ButtonGroupVert.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/music/PlaylistItems.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/StandardDialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/data/PublicUserData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/options/OptionNode.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/data/AlbumData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/music/AlbumTrackList.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/data/GetFiltersTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/config.bs"
2 | import "pkg:/source/api/sdk.bs"
3 |
4 | sub init()
5 | m.top.functionName = "getFiltersTask"
6 | end sub
7 |
8 | sub getFiltersTask()
9 | m.filters = api.items.GetFilters(m.top.params)
10 | m.top.filters = m.filters
11 | end sub
12 |
--------------------------------------------------------------------------------
/source/enums/KeyCode.bs:
--------------------------------------------------------------------------------
1 | enum KeyCode
2 | UP = "up"
3 | DOWN = "down"
4 | LEFT = "left"
5 | RIGHT = "right"
6 | OK = "OK"
7 | BACK = "back"
8 | OPTIONS = "options"
9 | FASTFORWRD = "fastforward"
10 | REWIND = "rewind"
11 | REPLAY = "replay"
12 | PLAY = "play"
13 | end enum
14 |
--------------------------------------------------------------------------------
/components/music/AlbumGrid.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/components/photos/LoadPhotoTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/dictionary.txt:
--------------------------------------------------------------------------------
1 | Jellyfin
2 | VSCode
3 | BrighterScript
4 | BrightScript
5 | sideload
6 | Sideload
7 | Reddit
8 | DEVGUIDE
9 | ImageMagick
10 | ing
11 | hardcode
12 | Hardcoding
13 | pre-release
14 | breakpoint
15 | repo
16 | Repo
17 | dev
18 | Dev
19 | assignees
20 | HTTPS
21 | dropdown
22 | JSDoc
23 | JavaScript
24 | namespaces
25 |
--------------------------------------------------------------------------------
/components/data/FolderData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/quickConnect/QuickConnect.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/GetNextEpisodeTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/data/PersonData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/home/HomeRow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/source/enums/CollectionType.bs:
--------------------------------------------------------------------------------
1 | enum CollectionType
2 | BOOKS = "books"
3 | BOXSET = "boxset"
4 | BOXSETS = "boxsets"
5 | HOMEVIDEOS = "homevideos"
6 | MOVIES = "movies"
7 | MUSIC = "music"
8 | MUSICVIDEOS = "musicvideos"
9 | MYLIST = "mylist"
10 | NEXTUP = "nextup"
11 | TVSHOWS = "tvshows"
12 | end enum
13 |
--------------------------------------------------------------------------------
/components/data/ChannelData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/home/HomeRows.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/subtitle/SubtitleData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/tvshows/TVEpisodeRow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/movies/AudioTrackListData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/components/data/GetPlaylistDataTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/sdk.bs"
2 | import "pkg:/source/utils/config.bs"
3 |
4 | sub init()
5 | m.top.functionName = "getPlaylistData"
6 | end sub
7 |
8 | sub getPlaylistData()
9 | playlistData = api.playlists.GetUser(m.top.playlistID, m.global.session.user.id)
10 | m.top.playlistData = playlistData
11 | end sub
12 |
--------------------------------------------------------------------------------
/components/data/MusicAlbumData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/liveTv/RecordProgramTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature Request
2 | description: Suggest a new feature
3 | labels: feature
4 |
5 | body:
6 | - type: markdown
7 | attributes:
8 | value: |
9 | # Do not submit feature requests here! It will get closed. Feature requests MUST be submitted at https://features.jellyfin.org/
10 |
--------------------------------------------------------------------------------
/components/SearchBox.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/liveTv/LoadSheduleTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/ItemGrid/AlphaItem.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/misc.bs"
2 | import "pkg:/source/enums/ColorPalette.bs"
3 |
4 | sub init()
5 | m.letterText = m.top.findNode("letterText")
6 | m.letterText.color = ColorPalette.WHITE
7 | m.letterText.font.size = 25
8 | end sub
9 |
10 | sub showContent()
11 | m.letterText.text = m.top.itemContent.title
12 | end sub
13 |
--------------------------------------------------------------------------------
/components/data/PlaylistData.bs:
--------------------------------------------------------------------------------
1 | sub setFields()
2 | datum = m.top.json
3 |
4 | m.top.id = datum.id
5 | m.top.title = datum.name
6 | m.top.overview = datum.overview
7 | end sub
8 |
9 | sub setPoster()
10 | if m.top.image <> invalid
11 | m.top.posterURL = m.top.image.url
12 | else
13 | m.top.posterURL = ""
14 | end if
15 | end sub
16 |
--------------------------------------------------------------------------------
/components/keyboards/IntegerKeyboard.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/data/PlaylistItemData.bs:
--------------------------------------------------------------------------------
1 | sub setFields()
2 | datum = m.top.json
3 |
4 | m.top.id = datum.id
5 | m.top.title = datum.name
6 | m.top.overview = datum.overview
7 | end sub
8 |
9 | sub setPoster()
10 | if m.top.image <> invalid
11 | m.top.posterURL = m.top.image.url
12 | else
13 | m.top.posterURL = ""
14 | end if
15 | end sub
16 |
--------------------------------------------------------------------------------
/components/movies/VideoTrackListData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/movies/SubtitleTrackListData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/source/enums/ImageType.bs:
--------------------------------------------------------------------------------
1 | enum ImageType
2 | PRIMARY = "Primary"
3 | ART = "Art"
4 | BACKDROP = "Backdrop"
5 | BANNER = "Banner"
6 | LOGO = "Logo"
7 | THUMB = "Thumb"
8 | DISC = "Disc"
9 | BOX = "Box"
10 | SCREENSHOT = "Screenshot"
11 | MENU = "Menu"
12 | CHAPTER = "Chapter"
13 | BOXREAR = "BoxRear"
14 | PROFILE = "Profile"
15 | end enum
16 |
--------------------------------------------------------------------------------
/components/liveTv/LoadProgramDetailsTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/data/GetPlaylistDataTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/components/data/TVSeasonData.bs:
--------------------------------------------------------------------------------
1 | sub setFields()
2 | datum = m.top.json
3 |
4 | m.top.id = datum.id
5 | m.top.title = datum.name
6 | m.top.overview = datum.overview
7 |
8 | setPoster()
9 | end sub
10 |
11 | sub setPoster()
12 | if m.top.image <> invalid
13 | m.top.posterURL = m.top.image.url
14 | else
15 | m.top.posterURL = ""
16 | end if
17 |
18 | end sub
19 |
--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy
2 |
3 | Jellyfin does not collect personal information outside of that needed to log in to the specified Jellyfin server. We simply pass this data along to the Jellyfin server. We do not collect, broadcast, nor send this data anywhere but to the Jellyfin server you specify.
4 |
5 | Read Jellyfin's Privacy Policy at
6 |
--------------------------------------------------------------------------------
/components/data/CollectionData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/RadioDialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/components/data/SeriesData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/video/PreloadTrickplayImagesTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/bsfmt.json:
--------------------------------------------------------------------------------
1 | {
2 | "indentStyle": "spaces",
3 | "indentSpaceCount": 4,
4 | "keywordCase": "lower",
5 | "keywordCaseOverride": {
6 | "stop": "upper"
7 | },
8 | "formatSingleLineCommentType": "singlequote",
9 | "sortImports": true,
10 | "files": [
11 | "source/**/*.brs",
12 | "source/**/*.bs",
13 | "components/**/*.brs",
14 | "components/**/*.bs",
15 | "!**/roku_modules/**/*.*"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/components/RemoteSubtitleDialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/components/GetShuffleEpisodesTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/config.bs"
2 | import "pkg:/source/api/sdk.bs"
3 |
4 | sub init()
5 | m.top.functionName = "getShuffleEpisodesTask"
6 | end sub
7 |
8 | sub getShuffleEpisodesTask()
9 | data = api.shows.GetEpisodes(m.top.showID, {
10 | UserId: m.global.session.user.id,
11 | SortBy: "Random",
12 | Limit: 200
13 | })
14 |
15 | m.top.data = data
16 | end sub
17 |
--------------------------------------------------------------------------------
/components/data/MusicAlbumData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/misc.bs"
2 |
3 | sub setFields()
4 | datum = m.top.json
5 |
6 | m.top.id = datum.id
7 | m.top.title = datum.name
8 | m.top.overview = datum.overview
9 | end sub
10 |
11 | sub setPoster()
12 | if isvalid(m.top.image)
13 | m.top.posterURL = m.top.image.url
14 | else
15 | m.top.posterURL = "pkg:/images/icons/album.png"
16 | end if
17 | end sub
18 |
--------------------------------------------------------------------------------
/bsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "manifest",
4 | "source/**/*.*",
5 | "components/**/*.*",
6 | "images/**/*.*",
7 | "locale/**/*.*",
8 | "settings/*.*"
9 | ],
10 | "plugins": ["@rokucommunity/bslint"],
11 | "diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"],
12 | "sourceMap": true,
13 | "autoImportComponentScript": true,
14 | "stagingDir": "build/staging",
15 | "retainStagingDir": true
16 | }
17 |
--------------------------------------------------------------------------------
/components/search/SearchRow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/JFGroup.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/Clock.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/components/data/MusicSongData.bs:
--------------------------------------------------------------------------------
1 | sub setFields()
2 | datum = m.top.json
3 |
4 | m.top.id = datum.id
5 | m.top.title = datum.name
6 | m.top.overview = datum.overview
7 | m.top.trackNumber = datum.IndexNumber
8 | m.top.length = datum.RunTimeTicks
9 | end sub
10 |
11 | sub setPoster()
12 | if m.top.image <> invalid
13 | m.top.posterURL = m.top.image.url
14 | else
15 | m.top.posterURL = ""
16 | end if
17 | end sub
18 |
--------------------------------------------------------------------------------
/components/GetNextEpisodeTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/config.bs"
2 | import "pkg:/source/api/sdk.bs"
3 |
4 | sub init()
5 | m.top.functionName = "getNextEpisodeTask"
6 | end sub
7 |
8 | sub getNextEpisodeTask()
9 | m.nextEpisodeData = api.shows.GetEpisodes(m.top.showID, {
10 | UserId: m.global.session.user.id,
11 | StartItemId: m.top.videoID,
12 | Limit: 2
13 | })
14 |
15 | m.top.nextEpisodeData = m.nextEpisodeData
16 | end sub
17 |
--------------------------------------------------------------------------------
/components/search/SearchTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/Items.bs"
2 | import "pkg:/source/api/baserequest.bs"
3 | import "pkg:/source/utils/config.bs"
4 | import "pkg:/source/api/Image.bs"
5 | import "pkg:/source/utils/deviceCapabilities.bs"
6 |
7 | sub init()
8 | m.top.functionName = "search"
9 | end sub
10 |
11 | sub search()
12 | if m.top.query <> invalid and m.top.query <> ""
13 | m.top.results = searchMedia(m.top.query)
14 | end if
15 | end sub
16 |
--------------------------------------------------------------------------------
/components/data/TVSeasonData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/components/tvshows/TVExtrasTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/Items.bs"
2 | import "pkg:/source/api/baserequest.bs"
3 | import "pkg:/source/utils/config.bs"
4 | import "pkg:/source/api/Image.bs"
5 | import "pkg:/source/utils/deviceCapabilities.bs"
6 |
7 | sub init()
8 | m.top.functionName = "getExtras"
9 | end sub
10 |
11 | sub getExtras()
12 | if isValid(m.top.seasonID) and m.top.seasonID <> ""
13 | m.top.results = TVSeasonExtras(m.top.seasonID)
14 | end if
15 | end sub
16 |
--------------------------------------------------------------------------------
/source/static/whatsNew/3.1.0.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "description": "Use 1st found episode image as season poster on TV series screen when no image available",
4 | "author": "1hitsong"
5 | },
6 | {
7 | "description": "Use 1st found season image as series poster on TV series screen when no image available",
8 | "author": "1hitsong"
9 | },
10 | {
11 | "description": "Allow translation of skip media segment buttons",
12 | "author": "1hitsong"
13 | }
14 | ]
15 |
--------------------------------------------------------------------------------
/components/ItemGrid/AlphaItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
--------------------------------------------------------------------------------
/components/Buttons/TextSizeTask.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.functionName = "getTextSize"
3 | end sub
4 |
5 | sub getTextSize()
6 |
7 | reg = CreateObject("roFontRegistry")
8 | font = reg.GetDefaultFont(m.top.fontsize, false, false)
9 |
10 | res = []
11 |
12 | for each line in m.top.text
13 | res.push(font.GetOneLineWidth(line, m.top.maxWidth))
14 | end for
15 |
16 | m.top.height = font.GetOneLineHeight()
17 |
18 |
19 | m.top.width = res
20 |
21 | end sub
22 |
--------------------------------------------------------------------------------
/components/section/sectionScroller.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/components/data/PlaylistData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/components/home/LoadItemsTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "*.ts": "xml"
4 | },
5 | "[xml]": {
6 | "editor.defaultFormatter": "redhat.vscode-xml"
7 | },
8 | "xml.format.maxLineWidth": 0,
9 | "editor.formatOnSave": true,
10 | "brightscript.output.hyperlinkFormat": "FilenameAndFunction",
11 | "brightscript.bsdk": "node_modules/brighterscript",
12 | "search.exclude": {
13 | "**/.git": true,
14 | "**/node_modules": true
15 | },
16 | "brightscriptcomment.addExtraAtStartAndEnd": false
17 | }
--------------------------------------------------------------------------------
/components/data/ImageData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/components/mediaPlayers/AudioPlayer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/components/Buttons/TextSizeTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components/quickConnect/QuickConnectDialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/components/config/FormElement.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/components/ItemGrid/FavoriteItemsTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/api/sdk.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub init()
6 | m.top.functionName = "setFavoriteStatus"
7 | end sub
8 |
9 | sub setFavoriteStatus()
10 | task = m.top.favTask
11 |
12 | if isStringEqual(task, "favorite")
13 | api.users.MarkFavorite(m.global.session.user.id, m.top.itemId)
14 | else
15 | api.users.UnmarkFavorite(m.global.session.user.id, m.top.itemId)
16 | end if
17 | end sub
18 |
--------------------------------------------------------------------------------
/components/data/OptionsData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/components/data/MusicSongData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 |
8 | jobs:
9 | test:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v6
15 |
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v6
18 | with:
19 | node-version: "24"
20 | cache: "npm"
21 |
22 | - name: Install dependencies
23 | run: npm ci
24 |
25 | - name: Build jellyfin-roku client project
26 | run: npm run build
27 |
--------------------------------------------------------------------------------
/source/enums/ColorPalette.bs:
--------------------------------------------------------------------------------
1 | enum ColorPalette
2 | BLACK77 = "0x00000077"
3 | BLACK90 = "0x00000090"
4 | DARKGREY = "#101010"
5 | ELEMENTBACKGROUND = "#101420"
6 | HIGHLIGHT = "0xff6867FF"
7 | LIGHTBLUE = "#c8fafa"
8 | LIGHTGREY = "#aaaaaa"
9 | LIGHTHIGHLIGHT = "#ff9a99"
10 | MIDGREY = "#777777"
11 | OFFWHITE = "#cccccc"
12 | RED = "#FF0000"
13 | SMOKE = "#303030"
14 | TRANSPARENT = "0x00000000"
15 | TRIADBLUE = "#6867ff"
16 | TRIADGREEN = "#67ff68"
17 | VIEWBACKGROUND = "#000018"
18 | WHITE = "#ffffff"
19 | end enum
20 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
4 | // List of extensions which should be recommended for users of this workspace.
5 | "recommendations": [
6 | "RokuCommunity.brightscript",
7 | "AliceBeckett.brightscriptcomment",
8 | "redhat.vscode-xml"
9 | ],
10 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
11 | "unwantedRecommendations": []
12 | }
13 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | ## Changes
7 |
8 |
9 | ## Issues
10 |
12 |
--------------------------------------------------------------------------------
/components/captionTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/components/data/UserData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components/settings/Slider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/bsconfig-tests.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | {
4 | "src": "source/**/!(Main.brs)",
5 | "dest": "source"
6 | },
7 | {
8 | "src": "components/**/*",
9 | "dest": "components"
10 | },
11 | {
12 | "src": "locale/**/*",
13 | "dest": "locale"
14 | },
15 | {
16 | "src": "settings/**/*",
17 | "dest": "settings"
18 | },
19 | "manifest"
20 | ],
21 | "diagnosticFilters": ["node_modules/**/*", "**/roku_modules/**/*"],
22 | "autoImportComponentScript": true,
23 | "stagingDir": "build",
24 | "retainStagingDir": true,
25 | "plugins": [],
26 | "sourceMap": true
27 | }
28 |
--------------------------------------------------------------------------------
/components/data/RecordingData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ItemType.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub setFields()
5 | datum = m.top.json
6 |
7 | m.top.id = datum.id
8 | m.top.type = ItemType.RECORDING
9 | m.top.title = datum.name
10 | m.top.showID = datum.SeriesID
11 | m.top.seasonID = datum.SeasonID
12 | m.top.overview = datum.overview
13 | m.top.favorite = datum.UserData.isFavorite
14 | end sub
15 |
16 | sub setPoster()
17 | if isValid(m.top.image)
18 | m.top.posterURL = m.top.image.url
19 | else
20 | m.top.posterURL = ""
21 | end if
22 | end sub
23 |
--------------------------------------------------------------------------------
/components/ItemGrid/ColorOption.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/config.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub init()
5 | m.backdrop = m.top.findNode("backdrop")
6 | m.checkmarkShadow = m.top.findNode("checkmarkShadow")
7 | m.checkmarkShadow.font.size = 42
8 |
9 | m.checkmark = m.top.findNode("checkmark")
10 | m.checkmark.font.size = 42
11 | end sub
12 |
13 | sub itemContentChanged()
14 | itemData = m.top.itemContent
15 | if not isValid(itemData) then return
16 |
17 | m.backdrop.color = itemData.colorCode
18 | m.checkmark.visible = itemData.isChecked
19 | m.checkmarkShadow.visible = itemData.isChecked
20 | end sub
21 |
--------------------------------------------------------------------------------
/components/labels/Text.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ColorPalette.bs"
2 | import "pkg:/source/enums/String.bs"
3 | import "pkg:/source/utils/misc.bs"
4 |
5 | sub init()
6 | m.top.color = chainLookupReturn(m.global.session, "user.settings.colorText", ColorPalette.WHITE)
7 | setFont()
8 |
9 | m.top.observeFieldScoped("font", "onFontChanged")
10 | end sub
11 |
12 | sub setFont()
13 | if m.global.fallbackFont = string.EMPTY then return
14 | if not chainLookupReturn(m.global.session, "user.settings.useFallbackFont", false) then return
15 |
16 | m.top.font.uri = m.global.fallbackFont
17 | end sub
18 |
19 | sub onFontChanged()
20 | setFont()
21 | end sub
22 |
--------------------------------------------------------------------------------
/components/music/PlaylistItems.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/KeyCode.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub init()
5 | m.top.setfocus(true)
6 | group = m.global.sceneManager.callFunc("getActiveScene")
7 | group.lastFocus = m.top
8 | end sub
9 |
10 | sub getData()
11 | m.top.content = m.top.PlaylistData
12 | m.top.doneLoading = true
13 | end sub
14 |
15 | function onKeyEvent(key as string, press as boolean) as boolean
16 | if not press then return false
17 |
18 | if isStringEqual(key, KeyCode.PLAY)
19 | m.top.itemSelected = m.top.itemFocused
20 | return true
21 | end if
22 |
23 | return false
24 | end function
25 |
--------------------------------------------------------------------------------
/components/labels/ScrollingText.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ColorPalette.bs"
2 | import "pkg:/source/enums/String.bs"
3 | import "pkg:/source/utils/misc.bs"
4 |
5 | sub init()
6 | m.top.color = chainLookupReturn(m.global.session, "user.settings.colorText", ColorPalette.WHITE)
7 | setFont()
8 |
9 | m.top.observeFieldScoped("font", "onFontChanged")
10 | end sub
11 |
12 | sub setFont()
13 | if m.global.fallbackFont = string.EMPTY then return
14 | if not chainLookupReturn(m.global.session, "user.settings.useFallbackFont", false) then return
15 |
16 | m.top.font.uri = m.global.fallbackFont
17 | end sub
18 |
19 | sub onFontChanged()
20 | setFont()
21 | end sub
22 |
--------------------------------------------------------------------------------
/components/liveTv/LoadChannelsTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components/movies/AudioTrackListItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/components/movies/VideoTrackListItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/components/data/MovieData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components/liveTv/ChannelInfo.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/components/extras/ExtrasRowList.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/components/music/SongItem.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/misc.bs"
2 |
3 | sub init()
4 | m.itemText = m.top.findNode("itemText")
5 | m.trackNumber = m.top.findNode("trackNumber")
6 | m.tracklength = m.top.findNode("tracklength")
7 | end sub
8 |
9 | sub itemContentChanged()
10 | itemData = m.top.itemContent
11 | if itemData = invalid then return
12 | m.itemText.text = itemData.LookupCI("title")
13 |
14 | if itemData.LookupCI("trackNumber") <> 0
15 | m.trackNumber.text = itemData.LookupCI("trackNumber")
16 | end if
17 |
18 | if isValid(itemData.LookupCI("RunTimeTicks"))
19 | m.tracklength.text = ticksToHuman(itemData.LookupCI("RunTimeTicks"))
20 | end if
21 | end sub
22 |
--------------------------------------------------------------------------------
/components/tasks/PostTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/components/data/TVEpisodeData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ItemType.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub setFields()
5 | if not isValid(m.top.json) then return
6 | datum = m.top.json
7 |
8 | m.top.id = datum.id
9 | m.top.type = ItemType.EPISODE
10 | m.top.title = datum.name
11 | m.top.showID = datum.SeriesID
12 | m.top.seasonID = datum.SeasonID
13 | m.top.overview = datum.overview
14 | m.top.favorite = datum.UserData.isFavorite
15 | m.top.watched = chainLookup(datum, "UserData.played")
16 | end sub
17 |
18 | sub setPoster()
19 | if isValid(m.top.image)
20 | m.top.posterURL = m.top.image.url
21 | else
22 | m.top.posterURL = ""
23 | end if
24 | end sub
25 |
--------------------------------------------------------------------------------
/manifest:
--------------------------------------------------------------------------------
1 | ## Channel Details
2 |
3 | title=jellyfin
4 | major_version=3
5 | minor_version=1
6 | build_version=0
7 |
8 | ### Main Menu Icons / Channel Poster Artwork
9 |
10 | mm_icon_focus_fhd=pkg:/images/channel-poster_fhd_dev.png
11 | mm_icon_focus_hd=pkg:/images/channel-poster_hd_dev.png
12 | mm_icon_focus_sd=pkg:/images/channel-poster_sd_dev.png
13 |
14 | ### Splash Screen + Loading Screen Artwork
15 |
16 | splash_screen_fhd=pkg:/images/splash-screen_fhd.png
17 | splash_screen_hd=pkg:/images/splash-screen_hd.png
18 | splash_screen_sd=pkg:/images/splash-screen_sd.png
19 |
20 | splash_min_time=1000
21 |
22 | ui_resolutions=fhd
23 |
24 | confirm_partner_button=1
25 |
26 | supports_input_launch=1
27 |
28 | bs_const=printReg=false
29 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build-dev",
8 | "type": "shell",
9 | "command": "npm run build",
10 | "problemMatcher": [],
11 | "presentation": {
12 | "echo": true,
13 | "focus": false,
14 | "panel": "shared",
15 | "showReuseMessage": false,
16 | "clear": true
17 | },
18 | "group": {
19 | "kind": "build",
20 | "isDefault": true
21 | }
22 | }
23 | ]
24 | }
--------------------------------------------------------------------------------
/components/PlayedCheckmark.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/components/options/OptionsSlider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/data/JFContentItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components/JFButton.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.observeFieldScoped("text", "onTextChanged")
3 | m.top.iconUri = ""
4 | m.top.focusedIconUri = ""
5 | m.top.showFocusFootprint = true
6 | m.top.minWidth = 0
7 | end sub
8 |
9 | '
10 | ' Whenever the text changes, pad both sides with whitespace so we can center the button text
11 | '
12 | sub onTextChanged()
13 | addSpaceAfter = true
14 | minChars = m.top.minChars
15 | if minChars = invalid then minChars = 50
16 | while m.top.text.Len() < minChars
17 | if addSpaceAfter
18 | m.top.text = m.top.text + Chr(160)
19 | else
20 | m.top.text = Chr(160) + m.top.text
21 | end if
22 | addSpaceAfter = addSpaceAfter = false
23 | end while
24 | end sub
25 |
--------------------------------------------------------------------------------
/components/ButtonGroupHoriz.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.layoutDirection = "horiz"
3 | end sub
4 |
5 | function onKeyEvent(key as string, press as boolean) as boolean
6 | if not press then return false
7 | if key = "right"
8 | i = m.top.buttonFocused
9 | target = i + 1
10 | if target >= m.top.getChildCount() then return false
11 | m.top.focusButton = target
12 | return true
13 | else if key = "left"
14 | i = m.top.buttonFocused
15 | target = i - 1
16 | if target < 0 then return false
17 | m.top.focusButton = target
18 | return true
19 | else if key = "up" or key = "down"
20 | m.top.escape = key
21 | return true
22 | end if
23 |
24 | return false
25 | end function
26 |
--------------------------------------------------------------------------------
/components/photos/LoadPhotoTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/Image.bs"
2 | import "pkg:/source/utils/config.bs"
3 | import "pkg:/source/api/baserequest.bs"
4 | import "pkg:/source/utils/misc.bs"
5 |
6 | sub init()
7 | m.top.functionName = "loadItems"
8 | end sub
9 |
10 | sub loadItems()
11 | params = {
12 | maxHeight: 1080,
13 | maxWidth: 1920
14 | }
15 |
16 | if isValid(m.top.itemNodeContent)
17 | item = m.top.itemNodeContent
18 | m.top.results = ImageURL(item.Id, "Primary", params)
19 | else if isValid(m.top.itemArrayContent)
20 | item = m.top.itemArrayContent
21 | m.top.results = ImageURL(item.Id, "Primary", params)
22 | else
23 | m.top.results = invalid
24 | end if
25 |
26 |
27 |
28 | end sub
29 |
--------------------------------------------------------------------------------
/source/enums/ItemType.bs:
--------------------------------------------------------------------------------
1 | enum ItemType
2 | AUDIO = "audio"
3 | AUDIOBOOK = "audiobook"
4 | BOOK = "book"
5 | BOXSET = "boxset"
6 | CHANNEL = "channel"
7 | COLLECTIONFOLDER = "collectionfolder"
8 | EPISODE = "episode"
9 | FOLDER = "folder"
10 | MOVIE = "movie"
11 | MUSICALBUM = "musicalbum"
12 | MUSICARTIST = "musicartist"
13 | MUSICVIDEO = "musicvideo"
14 | MYLIST = "mylist"
15 | PERSON = "person"
16 | PHOTO = "photo"
17 | PHOTOALBUM = "photoalbum"
18 | PLAYLIST = "playlist"
19 | PROGRAM = "program"
20 | RECORDING = "recording"
21 | SEASON = "season"
22 | SERIES = "series"
23 | STUDIO = "studio"
24 | TVCHANNEL = "tvchannel"
25 | USERVIEW = "userview"
26 | VIDEO = "video"
27 | end enum
28 |
--------------------------------------------------------------------------------
/components/music/SongItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/components/music/AlbumTrackList.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/KeyCode.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub init()
5 | getData()
6 | m.top.setfocus(true)
7 | end sub
8 |
9 | function getData()
10 | if m.top.MusicArtistAlbumData = invalid
11 | data = CreateObject("roSGNode", "ContentNode")
12 | return data
13 | end if
14 |
15 | m.top.content = m.top.MusicArtistAlbumData
16 |
17 | m.top.doneLoading = true
18 |
19 | return data
20 | end function
21 |
22 | function onKeyEvent(key as string, press as boolean) as boolean
23 | if not press then return false
24 |
25 | if isStringEqual(key, KeyCode.PLAY)
26 | m.top.itemSelected = m.top.itemFocused
27 | return true
28 | end if
29 |
30 | return false
31 | end function
32 |
--------------------------------------------------------------------------------
/components/quickConnect/QuickConnect.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/userauth.bs"
2 | import "pkg:/source/api/baserequest.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub init()
6 | m.top.functionName = "monitorQuickConnect"
7 | end sub
8 |
9 | sub monitorQuickConnect()
10 | authenticated = checkQuickConnect(m.top.secret)
11 |
12 | if authenticated = true
13 | loggedIn = AuthenticateViaQuickConnect(m.top.secret)
14 | if loggedIn
15 | currentUser = AboutMe()
16 | session.user.Login(currentUser, m.top.saveCredentials)
17 | session.user.LoadUserPreferences()
18 | LoadUserAbilities()
19 | m.top.authenticated = 1
20 | return
21 | end if
22 | end if
23 |
24 | m.top.authenticated = -1
25 | end sub
26 |
--------------------------------------------------------------------------------
/components/data/VideoData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/api/Image.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 |
8 | m.top.id = json.id
9 | m.top.Title = json.name
10 | m.top.Description = json.overview
11 | m.top.favorite = json.UserData.isFavorite
12 | m.top.watched = chainLookup(json, "UserData.played")
13 | m.top.Type = "Video"
14 |
15 | setPoster()
16 | end sub
17 |
18 | sub setPoster()
19 | if m.top.image <> invalid
20 | m.top.posterURL = m.top.image.url
21 | else if m.top.json.ImageTags.Primary <> invalid
22 | imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
23 | m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
24 | end if
25 | end sub
26 |
--------------------------------------------------------------------------------
/components/data/TVEpisodeData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/components/home/Home.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/JFMessageDialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/data/RecordingData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/components/BaseScene.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/components/data/ScheduleProgramData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/components/JFScreen.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | end sub
3 | ' Function called when the screen is displayed by the screen manager
4 | ' It is expected that screens override this function to handle focus
5 | ' managmenet and any other actions required on screen shown
6 | sub OnScreenShown()
7 | if m.top.lastFocus <> invalid
8 | m.top.lastFocus.setFocus(true)
9 | group = m.global.sceneManager.callFunc("getActiveScene")
10 | group.lastFocus = m.top.lastFocus
11 | else
12 | m.top.setFocus(true)
13 | group = m.global.sceneManager.callFunc("getActiveScene")
14 | group.lastFocus = m.top
15 | end if
16 | end sub
17 |
18 | ' Function called when the screen is hidden by the screen manager
19 | ' It is expected that screens override this function if required,
20 | ' to handle focus any actions required on the screen being hidden
21 | sub OnScreenHidden()
22 | end sub
23 |
24 |
--------------------------------------------------------------------------------
/components/movies/AudioTrackListItem.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.title = m.top.findNode("title")
3 | m.description = m.top.findNode("description")
4 | m.selectedIcon = m.top.findNode("selectedIcon")
5 | end sub
6 |
7 | sub itemContentChanged()
8 | m.title.text = m.top.itemContent.title
9 | m.description.text = m.top.itemContent.description
10 |
11 | if m.top.itemContent.description = ""
12 | m.title.translation = [50, 20]
13 | end if
14 |
15 | if m.top.itemContent.selected
16 | m.selectedIcon.uri = m.global.constants.icons.check_white
17 | else
18 | m.selectedIcon.uri = ""
19 | end if
20 |
21 | end sub
22 |
23 | '
24 | 'Scroll description if focused
25 | sub focusChanged()
26 |
27 | if m.top.itemHasFocus = true
28 | m.description.repeatCount = -1
29 | else
30 | m.description.repeatCount = 0
31 | end if
32 |
33 | end sub
34 |
--------------------------------------------------------------------------------
/components/movies/VideoTrackListItem.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.title = m.top.findNode("title")
3 | m.description = m.top.findNode("description")
4 | m.selectedIcon = m.top.findNode("selectedIcon")
5 | end sub
6 |
7 | sub itemContentChanged()
8 | m.title.text = m.top.itemContent.title
9 | m.description.text = m.top.itemContent.description
10 |
11 | if m.top.itemContent.description = ""
12 | m.title.translation = [50, 20]
13 | end if
14 |
15 | if m.top.itemContent.selected
16 | m.selectedIcon.uri = m.global.constants.icons.check_white
17 | else
18 | m.selectedIcon.uri = ""
19 | end if
20 |
21 | end sub
22 |
23 | '
24 | 'Scroll description if focused
25 | sub focusChanged()
26 |
27 | if m.top.itemHasFocus = true
28 | m.description.repeatCount = -1
29 | else
30 | m.description.repeatCount = 0
31 | end if
32 |
33 | end sub
34 |
--------------------------------------------------------------------------------
/components/SettingDialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/components/StandardButton.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/components/login/UserItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/components/ListPoster.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/components/ItemGrid/ColorOption.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/components/data/ExtrasData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/components/IconButton.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/data/HomeData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/Buttons/SlideOutButton.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/data/SearchData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/subtitle/SubtitleItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/components/extras/ExtrasItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/components/ItemGrid/MusicArtistGridItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jellyfin",
3 | "type": "module",
4 | "version": "3.1.0",
5 | "description": "Official Roku app for Jellyfin media server",
6 | "dependencies": {
7 | "@rokucommunity/bslib": "0.1.1",
8 | "brighterscript-formatter": "1.7.19"
9 | },
10 | "devDependencies": {
11 | "@rokucommunity/bslint": "0.8.38",
12 | "brighterscript": "0.70.3",
13 | "jshint": "2.13.6",
14 | "rimraf": "6.1.2",
15 | "roku-deploy": "3.16.1",
16 | "undent": "1.0.0"
17 | },
18 | "scripts": {
19 | "build": "npx rimraf build/ out/ && npx bsc --project bsconfig.json"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "https://github.com/jellyfin/jellyfin-roku.git"
24 | },
25 | "keywords": [
26 | "jellyfin",
27 | "roku",
28 | "music",
29 | "movies"
30 | ],
31 | "author": "Jellyfin",
32 | "license": "GPL-2.0",
33 | "bugs": {
34 | "url": "https://github.com/jellyfin/jellyfin-roku"
35 | },
36 | "homepage": "https://github.com/jellyfin/jellyfin-roku"
37 | }
38 |
--------------------------------------------------------------------------------
/components/data/ChannelData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/Image.bs"
2 | import "pkg:/source/api/baserequest.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 | m.top.id = json.id
8 | if isValid(json.number)
9 | m.top.title = `${tr("CH")} ${json.number} ${json.name}`
10 | else
11 | m.top.title = json.name
12 | end if
13 | m.top.live = true
14 | m.top.Type = "TvChannel"
15 | setPoster()
16 | end sub
17 |
18 | sub setPoster()
19 | if m.top.image <> invalid
20 | m.top.posterURL = m.top.image.url
21 | else if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Primary <> invalid
22 | imgParams = { "maxHeight": 60, "Tag": m.top.json.ImageTags.Primary }
23 | m.top.hdsmalliconurl = ImageURL(m.top.json.id, "Primary", imgParams)
24 |
25 | imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
26 | m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
27 | end if
28 | end sub
29 |
--------------------------------------------------------------------------------
/components/Buttons/ButtonData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/components/data/PlaylistItemData.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/config/FormElement.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ColorPalette.bs"
2 | import "pkg:/source/enums/String.bs"
3 | import "pkg:/source/utils/misc.bs"
4 |
5 | sub init()
6 | m.name = m.top.findNode("label")
7 |
8 | m.value = m.top.findNode("value")
9 | m.value.backgroundUri = "pkg:/images/transparent.png"
10 | m.value.width = 440
11 |
12 | m.name.color = ColorPalette.WHITE
13 | m.value.textColor = ColorPalette.DARKGREY
14 | m.value.hintTextColor = ColorPalette.MIDGREY
15 |
16 | m.name.width = 240
17 | m.name.height = 75
18 |
19 | m.name.vertAlign = "center"
20 | m.name.horizAlign = "center"
21 |
22 | m.value.hintText = tr("Enter a username")
23 | m.value.maxTextLength = 120
24 | end sub
25 |
26 | sub itemContentChanged()
27 | data = m.top.itemContent
28 |
29 | m.name.text = data.label
30 | if isStringEqual(data.type, "password")
31 | m.value.hintText = tr("Enter a password")
32 | m.value.secureMode = true
33 | end if
34 |
35 | m.value.text = data.value
36 | end sub
37 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "type": "brightscript",
6 | "request": "launch",
7 | "name": "Jellyfin Debug",
8 | "rootDir": "${workspaceFolder}/build/staging",
9 | "preLaunchTask": "build-dev",
10 | "stopOnEntry": false,
11 | // To enable RALE:
12 | // set "brightscript.debug.raleTrackerTaskFileLocation": "/absolute/path/to/rale/TrackerTask.xml" in your vscode user settings
13 | // set the below field to true
14 | "injectRaleTrackerTask": false,
15 | "injectRdbOnDeviceComponent": true,
16 | //WARNING: don't edit this value. Instead, set "brightscript.debug.host": "YOUR_HOST_HERE" in your vscode user settings
17 | //"host": "${promptForHost}",
18 | //WARNING: don't edit this value. Instead, set "brightscript.debug.password": "YOUR_PASSWORD_HERE" in your vscode user settings
19 | //"password": "${promptForPassword}",
20 | },
21 | ]
22 | }
--------------------------------------------------------------------------------
/components/ItemGrid/GridItemSmall.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/components/video/PreloadTrickplayImagesTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/sdk.bs"
2 | import "pkg:/source/utils/config.bs"
3 |
4 | sub init()
5 | m.top.functionName = "preloadTrickplayImagesTask"
6 | end sub
7 |
8 | sub preloadTrickplayImagesTask()
9 | fs = CreateObject("roFileSystem")
10 |
11 | if m.top.method = "ADD"
12 | for tileIndex = 0 to m.top.numImagesToLoad
13 | updatedURL = `Videos/${m.top.videoID}/Trickplay/${m.top.trickplayWidth}/${tileIndex}.jpg?api_key=${get_user_setting("token")}`
14 | if not fs.Exists(`tmp:/${m.top.videoID}-${tileIndex}.jpg`)
15 | APIRequest(updatedURL).gettofile(`tmp:/${m.top.videoID}-${tileIndex}.jpg`)
16 | end if
17 | end for
18 | return
19 | end if
20 |
21 | if m.top.method = "REMOVE"
22 | for tileIndex = 0 to m.top.numImagesToLoad
23 | if fs.Exists(`tmp:/${m.top.videoID}-${tileIndex}.jpg`)
24 | fs.Delete(`tmp:/${m.top.videoID}-${tileIndex}.jpg`)
25 | end if
26 | end for
27 | return
28 | end if
29 | end sub
30 |
--------------------------------------------------------------------------------
/components/data/SceneManager.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/components/options/OptionsSlider.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.visible = false
3 |
4 | panel = m.top.findNode("panel")
5 | panel.panelSize = "small"
6 | panel.leftPosition = 1250
7 | panel.focusable = true
8 | panel.hasNextPanel = false
9 | panel.leftOnly = true
10 |
11 | list = m.top.findNode("panelList")
12 |
13 | panel.list = list
14 | end sub
15 |
16 | sub setFields()
17 | options = m.top.options
18 | buttons = m.top.buttons
19 | row = m.top.findNode("fieldList")
20 |
21 | row.clear()
22 | row.appendChildren(options)
23 | row.appendChildren(buttons)
24 | end sub
25 |
26 | function onKeyEvent(key as string, press as boolean) as boolean
27 | if not press then return false
28 |
29 | if key = "options" or key = "back"
30 | m.top.visible = false
31 | m.top.closeSidePanel = true
32 | return true
33 | else if key = "OK"
34 | list = m.top.findNode("panelList")
35 | data = list.content.getChild(list.itemFocused)
36 | data.userMenuOptionSelected = true
37 | return true
38 | end if
39 |
40 | return false
41 | end function
42 |
--------------------------------------------------------------------------------
/components/data/OptionsData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/config.bs"
2 |
3 | sub init()
4 | m.top.value_index = 0
5 | end sub
6 |
7 | sub update_title()
8 | if m.top.choices.count() = 0
9 | m.top.title = m.top.base_title + ": "
10 | return
11 | end if
12 |
13 | for i = 0 to m.top.choices.count() - 1
14 | if m.top.choices[i].value = m.top.value
15 | m.top.value_index = i
16 | exit for
17 | end if
18 | end for
19 | m.top.title = m.top.base_title + ": " + m.top.choices[m.top.value_index].display
20 | end sub
21 |
22 | sub press()
23 | max_opt = m.top.choices.count()
24 | i = m.top.value_index + 1
25 | while i >= max_opt
26 | i = i - max_opt
27 | end while
28 |
29 | m.top.value_index = i
30 | m.top.value = m.top.choices[m.top.value_index].value
31 |
32 | if m.top.config_key = "" or m.top.config_key = invalid
33 | return
34 | end if
35 | if m.top.global_setting
36 | set_setting(m.top.config_key, m.top.value)
37 | else
38 | set_user_setting(m.top.config_key, m.top.value)
39 | end if
40 | end sub
41 |
--------------------------------------------------------------------------------
/components/music/SimilarArtistGrid.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/misc.bs"
2 |
3 | sub init()
4 | end sub
5 |
6 | function onKeyEvent(key as string, press as boolean) as boolean
7 | if not press then return false
8 |
9 | if key = "up"
10 | if m.top.itemFocused <= 5
11 | m.top.escape = key
12 | return true
13 | end if
14 | else if key = "left"
15 | if m.top.itemFocused mod 6 = 0
16 | m.top.escape = key
17 | return true
18 | end if
19 | else if key = "right"
20 | if m.top.itemFocused + 1 mod 6 = 0
21 | m.top.escape = key
22 | return true
23 | end if
24 | else if key = "down"
25 | totalCount = 0
26 | if isValid(m.top.content)
27 | totalCount = m.top.content.getChildCount()
28 | end if
29 |
30 | totalRows = div_ceiling(totalCount, 6)
31 | currentRow = fix(m.top.itemFocused / 6)
32 |
33 | if currentRow = totalRows - 1
34 | m.top.escape = key
35 | return true
36 | end if
37 | end if
38 |
39 | return false
40 | end function
41 |
--------------------------------------------------------------------------------
/components/subtitle/SubtitleItem.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/misc.bs"
2 | import "pkg:/source/enums/ColorPalette.bs"
3 |
4 | sub init()
5 | m.displayTitle = m.top.findNode("displayTitle")
6 | m.path = m.top.findNode("path")
7 | m.delete = m.top.findNode("delete")
8 |
9 | m.displayTitle.font.size = 30
10 | m.path.font.size = 28
11 |
12 | m.defaultTextColor = m.displayTitle.color
13 | end sub
14 |
15 | sub itemContentChanged()
16 | itemData = m.top.itemContent
17 | if itemData = invalid then return
18 | m.displayTitle.text = itemData.displayTitle
19 | m.path.text = itemData.path
20 | end sub
21 |
22 | sub focusChanged()
23 | color = m.defaultTextColor
24 | repeatcount = 0
25 | showDeleteIcon = false
26 |
27 | if m.top.itemHasFocus
28 | color = ColorPalette.WHITE
29 | repeatcount = -1
30 | showDeleteIcon = m.top.itemContent.canDelete
31 | end if
32 |
33 | m.displayTitle.color = color
34 | m.displayTitle.repeatCount = repeatcount
35 | m.delete.visible = showDeleteIcon
36 |
37 | m.path.color = color
38 | m.path.repeatCount = repeatcount
39 | end sub
40 |
--------------------------------------------------------------------------------
/components/Buttons/ExpandingLabel.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/components/SearchBox.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/api/Image.bs"
3 | import "pkg:/source/api/Items.bs"
4 | import "pkg:/source/utils/config.bs"
5 | import "pkg:/source/utils/deviceCapabilities.bs"
6 |
7 | sub init()
8 | m.top.layoutDirection = "vert"
9 | m.top.horizAlignment = "center"
10 | m.top.vertAlignment = "top"
11 | m.top.visible = false
12 | m.searchText = m.top.findNode("search_Key")
13 | m.searchText.textEditBox.hintText = tr("Search")
14 | m.searchText.keyGrid.keyDefinitionUri = "pkg:/components/data/CustomAddressKDF.json"
15 | m.searchText.textEditBox.voiceEnabled = true
16 | m.searchText.textEditBox.active = true
17 | m.searchText.ObserveField("text", "searchMedias")
18 | m.searchRow = m.top.findNode("searchRow")
19 |
20 | 'set lable text
21 | m.label = m.top.findNode("text")
22 | m.label.text = tr("Search now")
23 |
24 | end sub
25 |
26 | sub searchMedias()
27 | m.top.search_values = m.searchText.text
28 | if m.top.search_values.len() > 1
29 | m.searchText.textEditBox.leadingEllipsis = true
30 | else
31 | m.searchText.textEditBox.leadingEllipsis = false
32 | end if
33 | end sub
34 |
--------------------------------------------------------------------------------
/components/data/TVEpisode.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/api/Image.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 |
8 | m.top.id = json.id
9 | m.top.Description = json.overview
10 | m.top.favorite = json.UserData.isFavorite
11 | m.top.watched = chainLookup(json, "UserData.played")
12 | m.top.Type = json.Type
13 |
14 | setPoster()
15 | end sub
16 |
17 | sub setPoster()
18 | if m.top.image <> invalid
19 | m.top.posterURL = m.top.image.url
20 | else if m.top.json.ImageTags.Primary <> invalid
21 | imgParams = { "maxHeight": 440, "maxWidth": 295 }
22 | m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
23 | end if
24 | end sub
25 |
26 | sub setWatched(isWatched as boolean, unplayedItemCount = 0 as integer)
27 | if not isValid(m.top.json) then return
28 |
29 | json = m.top.json
30 |
31 | if isChainValid(json, "UserData.Played")
32 | json.UserData.AddReplace("Played", isWatched)
33 | json.UserData.AddReplace("PlaybackPositionTicks", 0)
34 | m.top.json = json
35 | end if
36 |
37 | m.top.watched = isWatched
38 | end sub
39 |
--------------------------------------------------------------------------------
/components/tvshows/TVEpisodes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/components/liveTv/schedule.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/components/ItemGrid/LoadVideoContentTask.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/components/music/Lyrics.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/components/ItemGrid/LoadItemsTask2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/components/music/PlaylistItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/components/config/JFServer.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/components/TextButton.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/components/extras/ExtrasSlider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/source/utils/parsedUrl.bs:
--------------------------------------------------------------------------------
1 | '
2 | ' Returns an object from a URL with its URI components as properties = { proto, host, port, path, query }
3 | ' If any of those URI components are missing, they will be set as empty strings = ""
4 | ' If the url param provided cannot be parsed, then invalid will be returned
5 | '
6 | ' @param url a URL string which will be parsed into URI components
7 | function ParsedUrl(url as string) as object
8 | rgx = CreateObject("roRegex", "^(?:(.*):\/\/)?([^\/:?]+)(?::(\d+))?(\/[^\r\n?]+)?(?:\?(.*))?$", "")
9 | match = rgx.Match(url)
10 | if not isValid(match) then return invalid
11 |
12 | return {
13 | proto: isValid(match[1]) ? match[1] : string.EMPTY,
14 | host: isValid(match[2]) ? match[2] : string.EMPTY,
15 | port: isValid(match[3]) ? match[3] : string.EMPTY,
16 | path: isValid(match[4]) ? match[4].endswith("/") ? match[4].left(len(match[4]) - 1) : match[4] : string.EMPTY,
17 | query: isValid(match[5]) ? match[5] : string.EMPTY,
18 | toString: __ParsedUrl_ToString
19 | }
20 | end function
21 |
22 | ' Returns the parsed URL as a complete URL string
23 | function __ParsedUrl_ToString() as string
24 | return `${m.proto = "" ? "" : `${m.proto}://`}${m.host}${m.port = "" ? "" : `:${m.port}`}${m.path}${m.query = "" ? "" : `?${m.query}`}`
25 | end function
26 |
--------------------------------------------------------------------------------
/components/Buttons/JFButtons.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/components/ItemGrid/GridItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/components/liveTv/LoadSheduleTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/utils/config.bs"
3 |
4 | sub init()
5 | m.top.functionName = "loadSchedule"
6 | end sub
7 |
8 | sub loadSchedule()
9 |
10 | results = []
11 |
12 | params = {
13 | UserId: m.global.session.user.id,
14 | SortBy: "startDate",
15 | EnableImages: false,
16 | EnableTotalRecordCount: false,
17 | EnableUserData: false,
18 | channelIds: m.top.channelIds,
19 | MaxStartDate: m.top.endTime,
20 | MinEndDate: m.top.startTime
21 | }
22 |
23 | url = "LiveTv/Programs"
24 |
25 | resp = APIRequest(url)
26 | data = postJson(resp, FormatJson(params))
27 |
28 | if data = invalid
29 | m.top.schedule = results
30 | return
31 | end if
32 |
33 | results = []
34 |
35 | for each item in data.Items
36 | program = createObject("roSGNode", "ScheduleProgramData")
37 | program.json = item
38 | ' Are we currently recording this program?
39 | if program.json.TimerId <> invalid and program.json.TimerId <> ""
40 | program.hdSmallIconUrl = "pkg:/images/red.png"
41 | else
42 | program.hdSmallIconUrl = invalid
43 | end if
44 | results.push(program)
45 | end for
46 |
47 |
48 | m.top.schedule = results
49 |
50 | end sub
51 |
--------------------------------------------------------------------------------
/components/MovieDetailButton.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/components/data/MusicAlbumSongListData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/Image.bs"
2 | import "pkg:/source/api/baserequest.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 | m.top.id = json.id
8 | m.top.favorite = json.UserData.isFavorite
9 | m.top.Type = "MusicAlbum"
10 | setPoster()
11 | end sub
12 |
13 | sub setPoster()
14 | if m.top.image <> invalid
15 | m.top.posterURL = m.top.image.url
16 | else
17 |
18 | if m.top.json.ImageTags.Primary <> invalid
19 | imgParams = { "maxHeight": 440, "maxWidth": 295 }
20 | m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
21 | else if m.top.json.BackdropImageTags[0] <> invalid
22 | imgParams = { "maxHeight": 440 }
23 | m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
24 | else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
25 | imgParams = { "maxHeight": 440, "maxWidth": 295 }
26 | m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
27 | end if
28 |
29 | ' Add Backdrop Image
30 | if m.top.json.BackdropImageTags[0] <> invalid
31 | imgParams = { "maxHeight": 720, "maxWidth": 1280 }
32 | m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
33 | end if
34 |
35 | end if
36 | end sub
37 |
--------------------------------------------------------------------------------
/components/home/HomeItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/components/liveTv/LoadProgramDetailsTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/utils/config.bs"
3 |
4 | sub init()
5 | m.top.functionName = "loadProgramDetails"
6 |
7 | end sub
8 |
9 | sub loadProgramDetails()
10 |
11 | channelIndex = m.top.ChannelIndex
12 | programIndex = m.top.ProgramIndex
13 |
14 | params = {
15 | UserId: m.global.session.user.id
16 | }
17 |
18 | url = Substitute("LiveTv/Programs/{0}", m.top.programId)
19 |
20 | resp = APIRequest(url, params)
21 | data = getJson(resp)
22 |
23 | if data = invalid
24 | m.top.programDetails = {}
25 | return
26 | end if
27 |
28 | program = createObject("roSGNode", "ScheduleProgramData")
29 | program.json = data
30 | program.channelIndex = channelIndex
31 | program.programIndex = programIndex
32 | program.fullyLoaded = true
33 | ' Are we currently recording this program?
34 | if program.json.TimerId <> invalid and program.json.TimerId <> ""
35 | ' This is needed here because the callee (onProgramDetailsLoaded) replaces the grid item with
36 | ' this newly created item from the server, without this, the red icon
37 | ' disappears when the user focuses on the program in question
38 | program.hdSmallIconUrl = "pkg:/images/red.png"
39 | else
40 | program.hdSmallIconUrl = invalid
41 | end if
42 | m.top.programDetails = program
43 |
44 | end sub
45 |
--------------------------------------------------------------------------------
/components/Libraries/LiveTVLibraryView.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/components/music/AlbumGrid.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/misc.bs"
2 |
3 | sub init()
4 | getData()
5 | end sub
6 |
7 | sub getData()
8 | ' If we have no album data, return a blank node
9 | if not isValid(m.top.MusicArtistAlbumData)
10 | m.top.content = CreateObject("roSGNode", "ContentNode")
11 | return
12 | end if
13 |
14 | m.top.content = m.top.MusicArtistAlbumData
15 | end sub
16 |
17 | function onKeyEvent(key as string, press as boolean) as boolean
18 | if not press then return false
19 |
20 | if key = "up"
21 | if m.top.itemFocused <= 4
22 | m.top.escape = key
23 | return true
24 | end if
25 | else if key = "left"
26 | if m.top.itemFocused mod 5 = 0
27 | m.top.escape = key
28 | return true
29 | end if
30 | else if key = "right"
31 | if m.top.itemFocused + 1 mod 5 = 0
32 | m.top.escape = key
33 | return true
34 | end if
35 | else if key = "down"
36 | totalCount = 0
37 | if isValid(m.top.MusicArtistAlbumData)
38 | totalCount = m.top.MusicArtistAlbumData.getChildCount()
39 | end if
40 | totalRows = div_ceiling(totalCount, 5)
41 | currentRow = div_ceiling(m.top.itemFocused + 1, 5)
42 |
43 | if currentRow = totalRows
44 | m.top.escape = key
45 | return true
46 | end if
47 | end if
48 |
49 | return false
50 | end function
51 |
--------------------------------------------------------------------------------
/components/search/SearchResults.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/components/ItemGrid/GridItemMedium.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/components/tvshows/TVListOptions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/components/data/PhotoData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/Image.bs"
2 | import "pkg:/source/api/baserequest.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 |
8 | m.top.id = json.id
9 | m.top.Title = json.name
10 | m.top.Type = "Photo"
11 |
12 | setPoster()
13 | end sub
14 |
15 | sub setPoster()
16 | if m.top.image <> invalid
17 | m.top.posterURL = m.top.image.url
18 | else
19 |
20 | if m.top.json.ImageTags.Primary <> invalid
21 | imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
22 | m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
23 | else if m.top.json.BackdropImageTags[0] <> invalid
24 | imgParams = { "maxHeight": 440, "Tag": m.top.json.BackdropImageTags[0] }
25 | m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
26 | else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
27 | imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ParentThumbImageTag }
28 | m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
29 | end if
30 |
31 | ' Add Backdrop Image
32 | if m.top.json.BackdropImageTags[0] <> invalid
33 | imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag": m.top.json.BackdropImageTags[0] }
34 | m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
35 | end if
36 |
37 | end if
38 | end sub
39 |
--------------------------------------------------------------------------------
/components/tvshows/TVSeasonRow.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ColorPalette.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub init()
5 | m.top.itemComponentName = "ListPoster"
6 | m.top.content = getData()
7 |
8 | m.top.rowFocusAnimationStyle = "fixedFocusWrap"
9 | m.top.focusBitmapBlendColor = chainLookupReturn(m.global.session, "user.settings.colorCursor", ColorPalette.HIGHLIGHT)
10 |
11 | m.top.showRowLabel = [false]
12 | m.top.showRowCounter = [true]
13 | m.top.rowLabelOffset = [0, 0]
14 |
15 | updateSize()
16 |
17 | m.top.setfocus(true)
18 | end sub
19 |
20 | sub updateSize()
21 | itemWidth = 200
22 | itemHeight = 320 ' width * 1.5 + text
23 |
24 | m.top.visible = true
25 |
26 | ' size of the whole row
27 | m.top.itemSize = [1680, (itemHeight + 40)]
28 | ' spacing between rows
29 | m.top.itemSpacing = [0, 0]
30 |
31 | ' size of the item in the row
32 | m.top.rowItemSize = [itemWidth, itemHeight]
33 | ' spacing between items in a row
34 | m.top.rowItemSpacing = [0, 0]
35 | end sub
36 |
37 | function getData()
38 | if m.top.TVSeasonData = invalid
39 | data = CreateObject("roSGNode", "ContentNode")
40 | return data
41 | end if
42 |
43 | seasonData = m.top.TVSeasonData
44 | data = CreateObject("roSGNode", "ContentNode")
45 | row = data.CreateChild("ContentNode")
46 | row.title = tr("Seasons")
47 | for each item in seasonData.items
48 | row.appendChild(item)
49 | end for
50 | m.top.content = data
51 | return data
52 | end function
53 |
--------------------------------------------------------------------------------
/components/data/PersonData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/Image.bs"
2 | import "pkg:/source/api/baserequest.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 | m.top.id = json.id
8 | m.top.favorite = json.UserData.isFavorite
9 | m.top.Type = "Person"
10 | setPoster()
11 | end sub
12 |
13 | sub setPoster()
14 | if m.top.image <> invalid
15 | m.top.posterURL = m.top.image.url
16 | else
17 |
18 | if m.top.json.ImageTags.Primary <> invalid
19 | imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ImageTags.Primary }
20 | m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
21 | else if m.top.json.BackdropImageTags[0] <> invalid
22 | imgParams = { "maxHeight": 440, "Tag": m.top.json.BackdropImageTags[0] }
23 | m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
24 | else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
25 | imgParams = { "maxHeight": 440, "maxWidth": 295, "Tag": m.top.json.ParentThumbImageTag }
26 | m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
27 | end if
28 |
29 | ' Add Backdrop Image
30 | if m.top.json.BackdropImageTags[0] <> invalid
31 | imgParams = { "maxHeight": 720, "maxWidth": 1280, "Tag": m.top.json.BackdropImageTags[0] }
32 | m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
33 | end if
34 |
35 | end if
36 | end sub
37 |
--------------------------------------------------------------------------------
/components/StandardDialog.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ColorPalette.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub init()
5 | m.content = m.top.findNode("content")
6 | m.top.observeField("contentData", "onContentDataChanged")
7 |
8 | m.top.id = "OKDialog"
9 | m.top.height = 900
10 | m.top.title = tr("What's New")+ "?"
11 | m.top.buttons = [tr("OK")]
12 |
13 | m.dialogStyles = {
14 | "default": {
15 | "fontSize": 27,
16 | "fontUri": "font:BoldSystemFontFile",
17 | "color": chainLookupReturn(m.global.session, "user.settings.colorDialogText", ColorPalette.WHITE)
18 | },
19 | "b": {
20 | "fontSize": 27,
21 | "fontUri": "font:SystemFontFile",
22 | "color": chainLookupReturn(m.global.session, "user.settings.colorDialogBoldText", ColorPalette.TRIADGREEN)
23 | },
24 | "header": {
25 | "fontSize": 35,
26 | "fontUri": "font:SystemFontFile",
27 | "color": chainLookupReturn(m.global.session, "user.settings.colorDialogText", ColorPalette.WHITE)
28 | },
29 | "p": {
30 | "fontSize": 27,
31 | "fontUri": "font:SystemFontFile",
32 | "color": chainLookupReturn(m.global.session, "user.settings.colorDialogText", ColorPalette.WHITE)
33 | }
34 | }
35 |
36 | end sub
37 |
38 | sub onContentDataChanged()
39 | for each item in m.top.contentData.data
40 | textLine = m.content.CreateChild("StdDlgMultiStyleTextItem")
41 | textLine.drawingStyles = m.dialogStyles
42 | textLine.text = item
43 | end for
44 | end sub
45 |
--------------------------------------------------------------------------------
/components/data/MusicArtistData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/api/Image.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 | m.top.id = json.id
8 | if isChainValid(json, "UserData.isFavorite")
9 | m.top.favorite = json.UserData.isFavorite
10 | end if
11 | m.top.Type = "MusicArtist"
12 | setPoster()
13 |
14 | m.top.title = json.name
15 | end sub
16 |
17 | sub setPoster()
18 | if m.top.image <> invalid
19 | m.top.posterURL = m.top.image.url
20 | else
21 |
22 | if not isChainValid(m.top.json, "ImageTags") then return
23 |
24 | ' Add Artist Image
25 | if m.top.json.ImageTags.Primary <> invalid
26 | imgParams = { "maxHeight": 440, "maxWidth": 440 }
27 | m.top.posterURL = ImageURL(m.top.json.id, "Primary", imgParams)
28 | else if m.top.json.BackdropImageTags[0] <> invalid
29 | imgParams = { "maxHeight": 440 }
30 | m.top.posterURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
31 | else if m.top.json.ParentThumbImageTag <> invalid and m.top.json.ParentThumbItemId <> invalid
32 | imgParams = { "maxHeight": 440, "maxWidth": 440 }
33 | m.top.posterURL = ImageURL(m.top.json.ParentThumbItemId, "Thumb", imgParams)
34 | end if
35 |
36 | ' Add Backdrop Image
37 | if m.top.json.BackdropImageTags[0] <> invalid
38 | imgParams = { "maxHeight": 720, "maxWidth": 1280 }
39 | m.top.backdropURL = ImageURL(m.top.json.id, "Backdrop", imgParams)
40 | end if
41 |
42 | end if
43 | end sub
44 |
--------------------------------------------------------------------------------
/components/ButtonGroupVert.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.top.layoutDirection = "vert"
3 | m.top.observeField("focusedChild", "onFocusChanged")
4 | m.top.observeField("focusButton", "onFocusButtonChanged")
5 | end sub
6 |
7 | sub onFocusChanged()
8 | if m.top.hasFocus()
9 | m.top.getChild(0).setFocus(true)
10 | group = m.global.sceneManager.callFunc("getActiveScene")
11 | group.lastFocus = m.top.getChild(0)
12 | m.top.focusButton = 0
13 | end if
14 | end sub
15 |
16 | sub onFocusButtonChanged()
17 | m.top.getChild(m.top.focusButton).setFocus(true)
18 |
19 | group = m.global.sceneManager.callFunc("getActiveScene")
20 | group.lastFocus = m.top.getChild(m.top.focusButton)
21 | end sub
22 |
23 | function onKeyEvent(key as string, press as boolean) as boolean
24 | if key = "OK"
25 | m.top.selected = m.top.focusButton
26 | return true
27 | end if
28 |
29 | if not press then return false
30 |
31 | if key = "down"
32 | i = m.top.focusButton
33 | target = i + 1
34 | if target >= m.top.getChildCount()
35 | m.top.escape = key
36 | return false
37 | end if
38 | m.top.focusButton = target
39 | return true
40 | else if key = "up"
41 | i = m.top.focusButton
42 | target = i - 1
43 | if target < 0
44 | m.top.escape = key
45 | return false
46 | end if
47 | m.top.focusButton = target
48 | return true
49 | else if key = "left" or key = "right"
50 | m.top.escape = key
51 | return true
52 | end if
53 |
54 | return false
55 | end function
56 |
--------------------------------------------------------------------------------
/components/data/UserData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/utils/config.bs"
2 |
3 | sub setDataFromJSON()
4 | json = m.top.json
5 | loadFromJSON(json)
6 | end sub
7 |
8 | sub loadFromJSON(json)
9 | m.top.id = json.User.id
10 |
11 | m.top.username = json.User.name
12 | m.top.token = json.AccessToken
13 | end sub
14 |
15 | sub loadFromRegistry(id as string)
16 | m.top.id = id
17 |
18 | m.top.username = get_user_setting("username")
19 | m.top.token = get_user_setting("token")
20 | end sub
21 |
22 | sub saveToRegistry()
23 | users = parseJson(get_setting("available_users", "[]"))
24 | this_user = invalid
25 |
26 | for each user in users
27 | if user.id = m.top.id then this_user = user
28 | end for
29 |
30 | if not isValid(this_user)
31 | users.push({
32 | id: m.top.id,
33 | username: m.top.username,
34 | server: m.global.session.server.url
35 | })
36 | set_setting("available_users", formatJson(users))
37 | end if
38 | end sub
39 |
40 | sub removeFromRegistry()
41 | new_users = []
42 | users = parseJson(get_setting("available_users", "[]"))
43 | for each user in users
44 | if m.top.id <> user.id then new_users.push(user)
45 | end for
46 |
47 | set_setting("available_users", formatJson(new_users))
48 | end sub
49 |
50 | function getPreference(key as string)
51 | return get_user_setting("pref-" + key)
52 | end function
53 |
54 | function setPreference(key as string, value as string)
55 | return set_user_setting("pref-" + key, value)
56 | end function
57 |
58 | sub setServer(hostname as string)
59 | m.top.server = hostname
60 | end sub
61 |
--------------------------------------------------------------------------------
/components/PlaystateTask.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/utils/config.bs"
3 |
4 | sub init()
5 | m.top.functionName = "PlaystateUpdate"
6 | end sub
7 |
8 | sub PlaystateUpdate()
9 | if m.top.status = "start"
10 | url = "Sessions/Playing"
11 | else if m.top.status = "stop"
12 | url = "Sessions/Playing/Stopped"
13 | else if m.top.status = "update"
14 | url = "Sessions/Playing/Progress"
15 | else
16 | ' Unknown State
17 | return
18 | end if
19 | params = PlaystateDefaults(m.top.params)
20 | resp = APIRequest(url)
21 | postJson(resp, params)
22 | end sub
23 |
24 | function PlaystateDefaults(params = {} as object)
25 | new_params = {
26 | '"CanSeek": false
27 | '"Item": "{}", ' TODO!
28 | '"NowPlayingQueue": "[]", ' TODO!
29 | '"PlaylistItemId": "",
30 | '"ItemId": id,
31 | '"SessionId": "", ' TODO!
32 | '"MediaSourceId": id,
33 | '"AudioStreamIndex": 1,
34 | '"SubtitleStreamIndex": 0,
35 | "IsPaused": false,
36 | '"IsMuted": false,
37 | "PositionTicks": 0
38 | '"PlaybackStartTimeTicks": 0,
39 | '"VolumeLevel": 100,
40 | '"Brightness": 100,
41 | '"AspectRatio": "16x9",
42 | '"PlayMethod": "DirectStream"
43 | '"LiveStreamId": "",
44 | '"PlaySessionId": "",
45 | '"RepeatMode": "RepeatNone"
46 | }
47 |
48 | paramsArray = params.items()
49 | for i = 0 to paramsArray.count() - 1
50 | item = paramsArray[i]
51 | new_params[item.key] = item.value
52 | end for
53 | return FormatJson(new_params)
54 | end function
55 |
--------------------------------------------------------------------------------
/components/login/UserRow.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ColorPalette.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub init()
5 | m.top.focusBitmapBlendColor = chainLookupReturn(m.global.session, "user.settings.colorCursor", ColorPalette.HIGHLIGHT)
6 | m.top.itemComponentName = "UserItem"
7 | m.top.content = SetData()
8 | m.top.observeField("itemSelected", "SetUser")
9 | m.top.showRowLabel = [false]
10 | updateSize()
11 | m.top.setFocus(true)
12 | end sub
13 |
14 | sub updateSize()
15 | itemWidth = 300
16 | itemHeight = 410
17 |
18 | m.top.visible = true
19 |
20 | ' Size of the individual rows
21 | m.top.itemSize = [1660, itemHeight]
22 | ' Spacing between Rows
23 | m.top.itemSpacing = [0, 40]
24 |
25 | ' Size of items in the row
26 | m.top.rowItemSize = [itemWidth, itemHeight]
27 | ' Spacing between items in the row
28 | m.top.rowItemSpacing = [40, 0]
29 | end sub
30 |
31 |
32 | function setData()
33 | if m.top.itemContent = invalid
34 | data = CreateObject("roSGNode", "ContentNode")
35 | return data
36 | end if
37 |
38 | userData = m.top.itemContent
39 | data = CreateObject("roSGNode", "ContentNode")
40 | row = data.CreateChild("ContentNode")
41 | for each item in userData
42 | row.appendChild(item)
43 | end for
44 | m.top.content = data
45 | updateSize()
46 | return data
47 | end function
48 |
49 | sub setUser()
50 | m.top.userSelected = m.top.itemContent[m.top.rowItemFocused[1]].Name
51 | end sub
52 |
53 | function onKeyEvent(key as string, press as boolean) as boolean
54 | if not press then return false
55 |
56 | return false
57 | end function
58 |
--------------------------------------------------------------------------------
/components/login/UserItem.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/ColorPalette.bs"
2 | import "pkg:/source/enums/PosterLoadStatus.bs"
3 | import "pkg:/source/utils/misc.bs"
4 |
5 | sub init()
6 | m.top.findNode("profileType").color = ColorPalette.LIGHTGREY
7 |
8 | m.profileImage = m.top.findNode("profileImage")
9 | m.profileImage.observeField("loadStatus", "onPosterLoadStatusChanged")
10 | end sub
11 |
12 | sub onPosterLoadStatusChanged()
13 | if m.profileImage.loadStatus <> PosterLoadStatus.LOADING
14 | m.profileImage.unobserveField("loadStatus")
15 | end if
16 |
17 | if isStringEqual(m.profileImage.loadStatus, PosterLoadStatus.FAILED)
18 | m.profileImage.uri = "pkg:/images/baseline_person_white_48dp.png"
19 | end if
20 | end sub
21 |
22 | sub onFocusChanged()
23 | itemData = m.top.itemContent
24 | if not isValid(itemData) then return
25 |
26 | m.top.findNode("forgetUserIcon").visible = m.top.itemHasFocus
27 |
28 | if m.top.itemHasFocus
29 | m.top.findNode("profileType").color = ColorPalette.WHITE
30 | else
31 | m.top.findNode("profileType").color = ColorPalette.LIGHTGREY
32 | end if
33 | end sub
34 |
35 | sub itemContentChanged()
36 | itemData = m.top.itemContent
37 | if not isValid(itemData) then return
38 |
39 | profileName = m.top.findNode("profileName")
40 |
41 | m.top.findNode("profileType").text = itemData.isPublic ? tr("Public Profile") : tr("Saved Profile")
42 |
43 | if itemData.imageURL = ""
44 | m.profileImage.uri = "pkg:/images/baseline_person_white_48dp.png"
45 | else
46 | m.profileImage.uri = itemData.imageURL
47 | end if
48 |
49 | profileName.text = itemData.name
50 | end sub
51 |
--------------------------------------------------------------------------------
/components/photos/PhotoDetails.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/components/music/PlaylistView.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/components/ItemGrid/AudioBookGridItem.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
17 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/components/Libraries/AudioBookLibraryView.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/components/settings/ColorGrid.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/enums/String.bs"
2 | import "pkg:/source/utils/misc.bs"
3 |
4 | sub init()
5 | m.highlightedColor = m.top.findNode("highlightedColor")
6 | m.highlightedColor.font.size = 20
7 |
8 | m.selectedColor = m.top.findNode("selectedColor")
9 | m.selectedColor.font.size = 20
10 |
11 | m.top.observeField("itemFocused", "onItemFocused")
12 | m.top.observeField("itemSelected", "onItemSelected")
13 | end sub
14 |
15 | sub onItemFocused()
16 | if not isValid(m.top.itemFocused)
17 | setHighlightedColor(string.EMPTY)
18 | return
19 | end if
20 |
21 | focusedColor = m.top.content.getChild(m.top.itemFocused)
22 |
23 | if not isChainValid(focusedColor, "colorCode")
24 | setHighlightedColor(string.EMPTY)
25 | return
26 | end if
27 |
28 | setHighlightedColor(focusedColor.colorCode)
29 | end sub
30 |
31 | sub setHighlightedColor(colorCode as string)
32 | m.highlightedColor.text = `${tr("Highlighted Color")}: ${colorCode}`
33 | end sub
34 |
35 | sub onSettingChange()
36 | m.top.selectedColor = chainLookupReturn(m.global.session, `user.settings.${m.top.setting.settingName}`, m.top.setting.default)
37 | end sub
38 |
39 | sub onSelectedColorChange()
40 | selectedColor = m.top.selectedColor
41 |
42 | if not m.top.isInFocusChain()
43 | setHighlightedColor(string.EMPTY)
44 | end if
45 |
46 | m.selectedColor.text = `${tr("Selected Color")}: ${selectedColor}`
47 |
48 | for each color in m.top.content.getChildren(-1, 0)
49 | if color.isChecked then color.isChecked = false
50 |
51 | if isStringEqual(chainLookup(color, "colorCode"), selectedColor)
52 | color.isChecked = true
53 | end if
54 | end for
55 | end sub
56 |
--------------------------------------------------------------------------------
/components/data/ScheduleProgramData.bs:
--------------------------------------------------------------------------------
1 | import "pkg:/source/api/baserequest.bs"
2 | import "pkg:/source/api/Image.bs"
3 | import "pkg:/source/utils/config.bs"
4 |
5 | sub setFields()
6 | json = m.top.json
7 |
8 | if isChainValid(json, "StartDate")
9 | startDate = createObject("roDateTime")
10 | startDate.FromISO8601String(json.StartDate)
11 | m.top.PlayStart = startDate.AsSeconds()
12 | end if
13 |
14 | if isChainValid(json, "EndDate")
15 | endDate = createObject("roDateTime")
16 | endDate.FromISO8601String(json.EndDate)
17 | m.top.PlayDuration = endDate.AsSeconds() - m.top.PlayStart
18 | end if
19 |
20 | m.top.Title = json.Name
21 | m.top.Id = json.Id
22 | m.top.Description = json.overview
23 | m.top.EpisodeTitle = json.EpisodeTitle
24 | m.top.isLive = json.isLive
25 | m.top.isRepeat = json.isRepeat
26 | m.top.startDate = json.startDate
27 | m.top.endDate = json.endDate
28 | m.top.channelId = json.channelId
29 |
30 | if json.IsSeries <> invalid and json.IsSeries = true
31 | if json.IndexNumber <> invalid
32 | m.top.episodeNumber = json.IndexNumber
33 | end if
34 |
35 | if json.ParentIndexNumber <> invalid
36 | m.top.seasonNumber = json.ParentIndexNumber
37 | end if
38 | end if
39 |
40 | setPoster()
41 | end sub
42 |
43 | sub setPoster()
44 | if m.top.image <> invalid
45 | m.top.posterURL = m.top.image.url
46 | else
47 | if m.top.json.ImageTags <> invalid and m.top.json.ImageTags.Thumb <> invalid
48 | imgParams = { "maxHeight": 500, "maxWidth": 500, "Tag": m.top.json.ImageTags.Thumb }
49 | m.top.posterURL = ImageURL(m.top.json.id, "Thumb", imgParams)
50 | end if
51 | end if
52 | end sub
53 |
--------------------------------------------------------------------------------
/components/settings/settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/components/music/AlbumView.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/components/StandardButton.bs:
--------------------------------------------------------------------------------
1 | sub init()
2 | m.buttonBackground = m.top.findNode("buttonBackground")
3 | m.buttonText = m.top.findNode("buttonText")
4 |
5 | m.top.observeField("background", "onBackgroundChanged")
6 | m.top.observeField("color", "onColorChanged")
7 | m.top.observeField("text", "onTextChanged")
8 | m.top.observeField("height", "onHeightChanged")
9 | m.top.observeField("width", "onWidthChanged")
10 | m.top.observeField("focus", "onFocusChanged")
11 | end sub
12 |
13 | sub onFocusChanged()
14 | if m.top.focus
15 | m.buttonBackground.blendColor = m.top.focusBackground
16 | m.buttonText.color = m.top.focusColor
17 | m.buttonText.font = "font:SmallBoldSystemFont"
18 | else
19 | m.buttonBackground.blendColor = m.top.background
20 | m.buttonText.color = m.top.color
21 | m.buttonText.font = "font:SmallSystemFont"
22 | end if
23 | end sub
24 |
25 | sub onBackgroundChanged()
26 | m.buttonBackground.blendColor = m.top.background
27 | m.top.unobserveField("background")
28 | end sub
29 |
30 | sub onColorChanged()
31 | m.buttonText.color = m.top.color
32 | m.top.unobserveField("color")
33 | end sub
34 |
35 | sub onTextChanged()
36 | m.buttonText.text = m.top.text
37 | end sub
38 |
39 | sub onHeightChanged()
40 | m.buttonBackground.height = m.top.height
41 | m.buttonText.height = m.top.height
42 | end sub
43 |
44 | sub onWidthChanged()
45 | m.buttonBackground.width = m.top.width
46 | m.buttonText.width = m.top.width
47 | end sub
48 |
49 | function onKeyEvent(key as string, press as boolean) as boolean
50 | if not press then return false
51 |
52 | if key = "right" and m.top.focus
53 | m.top.escape = "right"
54 | end if
55 |
56 | if key = "left" and m.top.focus
57 | m.top.escape = "left"
58 | end if
59 |
60 | return false
61 | end function
62 |
--------------------------------------------------------------------------------