From 74394b0acf7d8c2e49a17a4f307c0cd615e966a3 Mon Sep 17 00:00:00 2001 From: jellyfin-bot Date: Sun, 29 Sep 2024 15:33:10 +0000 Subject: [PATCH 01/18] Bump build version --- Makefile | 2 +- manifest | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index b22a6d4da..7cc79f175 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ # If you want to get_images, you'll also need convert from ImageMagick ########################################################################## -VERSION := 2.1.5 +VERSION := 2.1.6 ## usage diff --git a/manifest b/manifest index 47de6e805..e5d2bc832 100644 --- a/manifest +++ b/manifest @@ -3,7 +3,7 @@ title=Jellyfin major_version=2 minor_version=1 -build_version=5 +build_version=6 ### Main Menu Icons / Channel Poster Artwork diff --git a/package-lock.json b/package-lock.json index 63f2aa011..3f3ee6834 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jellyfin-roku", - "version": "2.1.5", + "version": "2.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jellyfin-roku", - "version": "2.1.5", + "version": "2.1.6", "hasInstallScript": true, "license": "GPL-2.0", "dependencies": { diff --git a/package.json b/package.json index 6ff6fed7b..b52c22585 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jellyfin-roku", "type": "module", - "version": "2.1.5", + "version": "2.1.6", "description": "Roku app for Jellyfin media server", "dependencies": { "@rokucommunity/bslib": "0.1.1", From 6d55a9e7cacc0b0ad10d2f690948f939889b8b0d Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Mon, 30 Sep 2024 08:55:16 -0400 Subject: [PATCH 02/18] revert uneeded buffer state change --- components/video/VideoPlayerView.bs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/components/video/VideoPlayerView.bs b/components/video/VideoPlayerView.bs index 5850bbd94..4e82d085a 100644 --- a/components/video/VideoPlayerView.bs +++ b/components/video/VideoPlayerView.bs @@ -610,19 +610,12 @@ sub onState(msg) ' Pass video state into OSD m.osd.playbackState = m.top.state - ' When buffering, start timer to monitor buffering process if m.top.state = "buffering" - ' start buffer timer + ' When buffering, start timer to monitor buffering process if isValid(m.bufferCheckTimer) m.bufferCheckTimer.control = "start" m.bufferCheckTimer.ObserveField("fire", "bufferCheck") end if - - ' update server if needed - if not m.playReported - m.playReported = true - ReportPlayback("start") - end if else if m.top.state = "error" m.log.error(m.top.errorCode, m.top.errorMsg, m.top.errorStr, m.top.errorCode) From b6f6728e53c54dc6e0739c031010818ab3e06d0a Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 09:08:17 -0400 Subject: [PATCH 03/18] prevent app crash when reloading movie details --- source/Main.bs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/source/Main.bs b/source/Main.bs index 33c3ba856..5865c7c94 100644 --- a/source/Main.bs +++ b/source/Main.bs @@ -248,11 +248,13 @@ sub Main (args as dynamic) as void currentScene.itemContent.json = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id) movieMetaData = ItemMetaData(currentScene.itemContent.id) - ' Redraw movie poster - currentScene.newPosterImageURI = movieMetaData.posterURL + if isValid(movieMetaData) + ' Redraw movie poster + currentScene.newPosterImageURI = movieMetaData.posterURL - ' Set updated starting point for the queue item - m.global.queueManager.callFunc("setTopStartingPoint", currentScene.itemContent.json.UserData.PlaybackPositionTicks) + ' Set updated starting point for the queue item + m.global.queueManager.callFunc("setTopStartingPoint", currentScene.itemContent.json.UserData.PlaybackPositionTicks) + end if end if stopLoadingSpinner() From a51a75b8cd67a692b06eeec2ef259f605322c23a Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 09:29:52 -0400 Subject: [PATCH 04/18] validate node refs to prevent crash --- components/home/HomeItem.bs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/components/home/HomeItem.bs b/components/home/HomeItem.bs index 797e253c1..6be223fb8 100644 --- a/components/home/HomeItem.bs +++ b/components/home/HomeItem.bs @@ -10,12 +10,12 @@ sub init() initItemPoster() m.itemProgress = m.top.findNode("progress") m.itemProgressBackground = m.top.findNode("progressBackground") - m.itemIcon = m.top.findNode("itemIcon") + initItemIcon() initItemTextExtra() m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged") m.unplayedCount = m.top.findNode("unplayedCount") m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount") - m.playedIndicator = m.top.findNode("playedIndicator") + initPlayedIndicator() m.showProgressBarAnimation = m.top.findNode("showProgressBar") m.showProgressBarField = m.top.findNode("showProgressBarField") @@ -50,6 +50,14 @@ sub initBackdrop() m.backdrop = m.top.findNode("backdrop") end sub +sub initItemIcon() + m.itemIcon = m.top.findNode("itemIcon") +end sub + +sub initPlayedIndicator() + m.playedIndicator = m.top.findNode("playedIndicator") +end sub + sub itemContentChanged() if isValid(m.unplayedCount) then m.unplayedCount.visible = false itemData = m.top.itemContent @@ -63,6 +71,8 @@ sub itemContentChanged() if not isValid(m.itemText) then initItemText() if not isValid(m.itemTextExtra) then initItemTextExtra() if not isValid(m.backdrop) then initBackdrop() + if not isValid(m.itemIcon) then initItemIcon() + if not isValid(m.playedIndicator) then initPlayedIndicator() m.itemPoster.width = itemData.imageWidth m.itemText.maxWidth = itemData.imageWidth @@ -87,7 +97,9 @@ sub itemContentChanged() if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount) if itemData.json.UserData.UnplayedItemCount > 0 if isValid(m.unplayedCount) then m.unplayedCount.visible = true - m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount + if isValid(m.unplayedEpisodeCount) + m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount + end if end if end if end if From e2080fb25a1d136e9a6118620d2cabd97aa05f88 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 11:24:49 -0400 Subject: [PATCH 05/18] validate data from api to prevent crash on episode list --- source/Main.bs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/source/Main.bs b/source/Main.bs index 33c3ba856..042ee6565 100644 --- a/source/Main.bs +++ b/source/Main.bs @@ -224,8 +224,12 @@ sub Main (args as dynamic) as void ' Find the object in the scene's data and update its json data for i = 0 to currentScene.objects.Items.count() - 1 if LCase(currentScene.objects.Items[i].id) = LCase(currentEpisode.id) - currentScene.objects.Items[i].json = api.users.GetItem(m.global.session.user.id, currentEpisode.id) - m.global.queueManager.callFunc("setTopStartingPoint", currentScene.objects.Items[i].json.UserData.PlaybackPositionTicks) + + data = api.users.GetItem(m.global.session.user.id, currentEpisode.id) + if isValid(data) + currentScene.objects.Items[i].json = data + m.global.queueManager.callFunc("setTopStartingPoint", data.UserData.PlaybackPositionTicks) + end if exit for end if end for From 4eda9edb0bc2cd2046d892124b194bde9a034a78 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 11:35:09 -0400 Subject: [PATCH 06/18] validate data from api to prevent crash when refreshing movie detail screen --- source/Main.bs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/source/Main.bs b/source/Main.bs index 33c3ba856..c449601f3 100644 --- a/source/Main.bs +++ b/source/Main.bs @@ -244,15 +244,19 @@ sub Main (args as dynamic) as void currentScene = m.global.sceneManager.callFunc("getActiveScene") if isValid(currentScene) and isValid(currentScene.itemContent) and isValid(currentScene.itemContent.id) - ' Refresh movie detail data - currentScene.itemContent.json = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id) - movieMetaData = ItemMetaData(currentScene.itemContent.id) - - ' Redraw movie poster - currentScene.newPosterImageURI = movieMetaData.posterURL - - ' Set updated starting point for the queue item - m.global.queueManager.callFunc("setTopStartingPoint", currentScene.itemContent.json.UserData.PlaybackPositionTicks) + data = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id) + if isValid(data) + currentScene.itemContent.json = data + ' Set updated starting point for the queue item + m.global.queueManager.callFunc("setTopStartingPoint", data.UserData.PlaybackPositionTicks) + + ' Refresh movie detail data + movieMetaData = ItemMetaData(currentScene.itemContent.id) + if isValid(movieMetaData) + ' Redraw movie poster + currentScene.newPosterImageURI = movieMetaData.posterURL + end if + end if end if stopLoadingSpinner() From 1088b64357a2e50d87e36894ffb91e75ff7687af Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 12:00:43 -0400 Subject: [PATCH 07/18] validate data to prevent crash --- source/Main.bs | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/source/Main.bs b/source/Main.bs index 33c3ba856..7fe2a0c1d 100644 --- a/source/Main.bs +++ b/source/Main.bs @@ -574,36 +574,41 @@ sub Main (args as dynamic) as void ' If a button is selected, we have some determining to do btn = getButton(msg) group = sceneManager.callFunc("getActiveScene") + if isValid(btn) and btn.id = "play-button" + if not isValid(group) then return + ' User chose Play button from movie detail view startLoadingSpinner() ' Check if a specific Audio Stream was selected audio_stream_idx = 0 - if isValid(group) and isValid(group.selectedAudioStreamIndex) + if isValid(group.selectedAudioStreamIndex) audio_stream_idx = group.selectedAudioStreamIndex end if - group.itemContent.selectedAudioStreamIndex = audio_stream_idx - group.itemContent.id = group.selectedVideoStreamId + if isValid(group.itemContent) + group.itemContent.selectedAudioStreamIndex = audio_stream_idx + group.itemContent.id = group.selectedVideoStreamId - ' Display playback options dialog - if group.itemContent.json.userdata.PlaybackPositionTicks > 0 - m.global.queueManager.callFunc("hold", group.itemContent) - playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json) - else - m.global.queueManager.callFunc("clear") - m.global.queueManager.callFunc("push", group.itemContent) - m.global.queueManager.callFunc("playQueue") + ' Display playback options dialog + if group.itemContent.json.userdata.PlaybackPositionTicks > 0 + m.global.queueManager.callFunc("hold", group.itemContent) + playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json) + else + m.global.queueManager.callFunc("clear") + m.global.queueManager.callFunc("push", group.itemContent) + m.global.queueManager.callFunc("playQueue") + end if end if - if isValid(group) and isValid(group.lastFocus) and isValid(group.lastFocus.id) and group.lastFocus.id = "main_group" + if isValid(group.lastFocus) and isValid(group.lastFocus.id) and group.lastFocus.id = "main_group" buttons = group.findNode("buttons") if isValid(buttons) group.lastFocus = group.findNode("buttons") end if end if - if isValid(group) and isValid(group.lastFocus) + if isValid(group.lastFocus) group.lastFocus.setFocus(true) end if From 4328318ad93b97173d1c3d438b3cf28a3da53371 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 13:58:56 -0400 Subject: [PATCH 08/18] validate api data to prevent crash when hitting shuffle play from tv show details --- components/tvshows/TVShowDetails.bs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/tvshows/TVShowDetails.bs b/components/tvshows/TVShowDetails.bs index 241845905..f7bd9ad3c 100644 --- a/components/tvshows/TVShowDetails.bs +++ b/components/tvshows/TVShowDetails.bs @@ -181,8 +181,10 @@ end function sub onShuffleEpisodeDataLoaded() m.getShuffleEpisodesTask.unobserveField("data") - m.global.queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items) - m.global.queueManager.callFunc("playQueue") + if isValid(m.getShuffleEpisodesTask.data) + m.global.queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items) + m.global.queueManager.callFunc("playQueue") + end if end sub function onKeyEvent(key as string, press as boolean) as boolean From 9a3c7a3f854fe6666c4ce1928045499ffa24aa27 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 14:14:08 -0400 Subject: [PATCH 09/18] validate user setting data to prevent crash --- components/home/HomeItem.bs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/home/HomeItem.bs b/components/home/HomeItem.bs index 797e253c1..1917c5b00 100644 --- a/components/home/HomeItem.bs +++ b/components/home/HomeItem.bs @@ -83,7 +83,8 @@ sub itemContentChanged() if LCase(itemData.type) = "series" if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings) - if not localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] + unwatchedEpisodeCountSetting = localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"] + if isValid(unwatchedEpisodeCountSetting) and not unwatchedEpisodeCountSetting if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount) if itemData.json.UserData.UnplayedItemCount > 0 if isValid(m.unplayedCount) then m.unplayedCount.visible = true From 6f708c6a90c0b8f2a81c9d66f9db12b4e6b9120f Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 18:55:50 -0400 Subject: [PATCH 10/18] validate node ref to prevent crash --- components/ItemGrid/GridItemSmall.bs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/components/ItemGrid/GridItemSmall.bs b/components/ItemGrid/GridItemSmall.bs index a197d37bc..8211e2180 100644 --- a/components/ItemGrid/GridItemSmall.bs +++ b/components/ItemGrid/GridItemSmall.bs @@ -4,7 +4,7 @@ import "pkg:/source/utils/config.bs" sub init() m.itemPoster = m.top.findNode("itemPoster") m.posterText = m.top.findNode("posterText") - m.title = m.top.findNode("title") + initTitle() m.posterText.font.size = 30 m.title.font.size = 25 m.backdrop = m.top.findNode("backdrop") @@ -23,6 +23,10 @@ sub init() end if end sub +sub initTitle() + m.title = m.top.findNode("title") +end sub + sub itemContentChanged() m.backdrop.blendColor = "#101010" @@ -54,6 +58,8 @@ sub itemContentChanged() end sub sub focusChanged() + if not isValid(m.title) then initTitle() + if m.top.itemHasFocus = true m.title.repeatCount = -1 else From e0786e23c15275f163e4fc8a4985d09ef7896138 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 19:37:26 -0400 Subject: [PATCH 11/18] remove unused code causing crash --- components/ItemGrid/MusicArtistGridItem.bs | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/ItemGrid/MusicArtistGridItem.bs b/components/ItemGrid/MusicArtistGridItem.bs index 96af114dc..446ae927b 100644 --- a/components/ItemGrid/MusicArtistGridItem.bs +++ b/components/ItemGrid/MusicArtistGridItem.bs @@ -18,10 +18,8 @@ sub init() m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode end if - m.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"] m.posterText.visible = false m.postTextBackground.visible = false - end sub sub itemContentChanged() From 3002ce0a59155cce731d31fdb2e9e508fd1340fb Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 19:48:31 -0400 Subject: [PATCH 12/18] validate we have data from api to prevent crash --- components/data/PersonData.bs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/data/PersonData.bs b/components/data/PersonData.bs index 71745b06f..48058fb06 100644 --- a/components/data/PersonData.bs +++ b/components/data/PersonData.bs @@ -4,9 +4,12 @@ import "pkg:/source/utils/config.bs" sub setFields() json = m.top.json + m.top.Type = "Person" + + if json = invalid then return + m.top.id = json.id m.top.favorite = json.UserData.isFavorite - m.top.Type = "Person" setPoster() end sub From 4ec187d17b6ab4f9cbdd6ab1ecc2cceed4354743 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Tue, 1 Oct 2024 20:04:59 -0400 Subject: [PATCH 13/18] validate data before sending to isLocalHost() to prevent app crash --- components/ItemGrid/LoadVideoContentTask.bs | 8 ++++++-- source/VideoPlayer.bs | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/components/ItemGrid/LoadVideoContentTask.bs b/components/ItemGrid/LoadVideoContentTask.bs index 643df4fe1..7a865db15 100644 --- a/components/ItemGrid/LoadVideoContentTask.bs +++ b/components/ItemGrid/LoadVideoContentTask.bs @@ -350,11 +350,15 @@ sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external) protocol = LCase(m.playbackInfo.MediaSources[0].Protocol) if protocol <> "file" uri = parseUrl(m.playbackInfo.MediaSources[0].Path) - if isLocalhost(uri[2]) + if not isValidAndNotEmpty(uri) then return + + if isValid(uri[2]) and isLocalhost(uri[2]) ' if the domain of the URI is local to the server, ' create a new URI by appending the received path to the server URL ' later we will substitute the users provided URL for this case - video.content.url = buildURL(uri[4]) + if isValid(uri[4]) + video.content.url = buildURL(uri[4]) + end if else fully_external = true video.content.url = m.playbackInfo.MediaSources[0].Path diff --git a/source/VideoPlayer.bs b/source/VideoPlayer.bs index 1c4f7e758..97a93fa62 100644 --- a/source/VideoPlayer.bs +++ b/source/VideoPlayer.bs @@ -263,16 +263,20 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx protocol = LCase(m.playbackInfo.MediaSources[0].Protocol) if protocol <> "file" uri = parseUrl(m.playbackInfo.MediaSources[0].Path) - if isLocalhost(uri[2]) + if not isValidAndNotEmpty(uri) then return + + if isValid(uri[2]) and isLocalhost(uri[2]) ' the domain of the URI is local to the server. ' create a new URI by appending the received path to the server URL ' later we will substitute the users provided URL for this case - video.content.url = buildURL(uri[4]) + if isValid(uri[4]) + video.content.url = buildURL(uri[4]) + end if else fully_external = true video.content.url = m.playbackInfo.MediaSources[0].Path end if - else: + else params.append({ "Static": "true", "Container": video.container, From 6939c1d598a6ea86e7b92c4cbc3d65650293e098 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Thu, 3 Oct 2024 13:58:59 -0400 Subject: [PATCH 14/18] tell server rokus don't support HE-AACv1 --- source/utils/deviceCapabilities.bs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/utils/deviceCapabilities.bs b/source/utils/deviceCapabilities.bs index 05bec80b5..746880cb6 100644 --- a/source/utils/deviceCapabilities.bs +++ b/source/utils/deviceCapabilities.bs @@ -451,6 +451,12 @@ function getCodecProfiles() as object "Value": "Main", "IsRequired": true }, + { + "Condition": "NotEquals", + "Property": "AudioProfile", + "Value": "HE-AAC", + "IsRequired": true + }, { "Condition": "LessThanEqual", "Property": "AudioChannels", From a5baa30e408e00df8e14bc42a6e8b6458adb90b3 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Thu, 3 Oct 2024 23:23:00 -0400 Subject: [PATCH 15/18] force aac main and he-aacv1 to be transcoded to mp3. save currently playing video meta data to global session --- components/ItemGrid/LoadVideoContentTask.bs | 7 ++--- components/video/VideoPlayerView.bs | 12 ++++++-- source/api/Items.bs | 33 +++++++++++++++++---- source/utils/session.bs | 20 ++++++++++++- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/components/ItemGrid/LoadVideoContentTask.bs b/components/ItemGrid/LoadVideoContentTask.bs index 643df4fe1..cf1f32561 100644 --- a/components/ItemGrid/LoadVideoContentTask.bs +++ b/components/ItemGrid/LoadVideoContentTask.bs @@ -6,6 +6,7 @@ import "pkg:/source/utils/config.bs" import "pkg:/source/api/Image.bs" import "pkg:/source/api/userauth.bs" import "pkg:/source/utils/deviceCapabilities.bs" +import "pkg:/source/utils/session.bs" enum SubtitleSelection notset = -2 @@ -71,14 +72,14 @@ end function sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean) meta = ItemMetaData(video.id) - subtitle_idx = m.top.selectedSubtitleIndex - if not isValid(meta) video.errorMsg = "Error loading metadata" video.content = invalid return end if + session.video.Update(meta) + subtitle_idx = m.top.selectedSubtitleIndex videotype = LCase(meta.type) ' Check for any Live TV streams or Recordings coming from other places other than the TV Guide @@ -196,12 +197,10 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s } end if - ' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay fully_external = false - ' For h264/hevc video, Roku spec states that it supports specfic encoding levels ' The device can decode content with a Higher Encoding level but may play it back with certain ' artifacts. If the user preference is set, and the only reason the server says we need to diff --git a/components/video/VideoPlayerView.bs b/components/video/VideoPlayerView.bs index 4e82d085a..553d14b43 100644 --- a/components/video/VideoPlayerView.bs +++ b/components/video/VideoPlayerView.bs @@ -1,5 +1,6 @@ import "pkg:/source/utils/misc.bs" import "pkg:/source/utils/config.bs" +import "pkg:/source/utils/session.bs" import "pkg:/source/roku_modules/log/LogMixin.brs" sub init() @@ -102,6 +103,7 @@ sub handleItemSkipAction(action as string) ' If there is something next in the queue, play it if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1 m.top.control = "stop" + session.video.Delete() m.global.sceneManager.callFunc("clearPreviousScene") m.global.queueManager.callFunc("moveForward") m.global.queueManager.callFunc("playQueue") @@ -114,6 +116,7 @@ sub handleItemSkipAction(action as string) ' If there is something previous in the queue, play it if m.global.queueManager.callFunc("getPosition") > 0 m.top.control = "stop" + session.video.Delete() m.global.sceneManager.callFunc("clearPreviousScene") m.global.queueManager.callFunc("moveBack") m.global.queueManager.callFunc("playQueue") @@ -626,10 +629,10 @@ sub onState(msg) else ' If an error was encountered, Display dialog showPlaybackErrorDialog(tr("Error During Playback")) + session.video.Delete() end if - ' Stop playback and exit player - m.top.control = "stop" + else if m.top.state = "playing" ' Check if next episode is available @@ -655,9 +658,11 @@ sub onState(msg) m.playbackTimer.control = "stop" ReportPlayback("stop") m.playReported = false + session.video.Delete() else if m.top.state = "finished" m.playbackTimer.control = "stop" ReportPlayback("finished") + session.video.Delete() else m.log.warning("Unhandled state", m.top.state, m.playReported, m.playFinished) end if @@ -716,6 +721,7 @@ sub bufferCheck(msg) ' Stop playback and exit player m.top.control = "stop" + session.video.Delete() end if end if @@ -785,6 +791,7 @@ function onKeyEvent(key as string, press as boolean) as boolean if key = "OK" and m.nextEpisodeButton.hasfocus() and not m.top.trickPlayBar.visible m.top.control = "stop" m.top.state = "finished" + session.video.Delete() hideNextEpisodeButton() return true else @@ -859,6 +866,7 @@ function onKeyEvent(key as string, press as boolean) as boolean if key = "back" m.top.control = "stop" + session.video.Delete() end if return false diff --git a/source/api/Items.bs b/source/api/Items.bs index f4262dc29..2a9f6c72b 100644 --- a/source/api/Items.bs +++ b/source/api/Items.bs @@ -1,4 +1,5 @@ import "pkg:/source/api/sdk.bs" +import "pkg:/source/utils/misc.bs" function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger) params = { @@ -13,9 +14,6 @@ function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger) end function function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger) - body = { - "DeviceProfile": getDeviceProfile() - } params = { "UserId": m.global.session.user.id, "StartTimeTicks": startTimeTicks, @@ -25,6 +23,7 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT "MaxStaticBitrate": "140000000", "SubtitleStreamIndex": subtitleTrackIndex } + deviceProfile = getDeviceProfile() ' Note: Jellyfin v10.9+ now remuxs LiveTV and does not allow DirectPlay anymore. ' Because of this, we need to tell the server "EnableDirectPlay = false" so that we receive the @@ -38,11 +37,35 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT params.EnableDirectPlay = false end if - if audioTrackIndex > -1 then params.AudioStreamIndex = audioTrackIndex + if audioTrackIndex > -1 + params.AudioStreamIndex = audioTrackIndex + + ' force the server to transcode AAC profiles we don't support to MP3 instead of the usual AAC + ' TODO: Remove this after server adds support for transcoding AAC from one profile to another + selectedAudioStream = m.global.session.video.json.MediaStreams[audioTrackIndex] + + if LCase(selectedAudioStream.Codec) = "aac" + if LCase(selectedAudioStream.Profile) = "main" or LCase(selectedAudioStream.Profile) = "he-aac" + for each rule in deviceProfile.TranscodingProfiles + if rule.Container = "ts" or rule.Container = "mp4" + if rule.AudioCodec = "aac" + rule.AudioCodec = "mp3" + else if rule.AudioCodec.Left(4) = "aac," + rule.AudioCodec = mid(rule.AudioCodec, 5) + + if rule.AudioCodec.Left(3) <> "mp3" + rule.AudioCodec = "mp3," + rule.AudioCodec + end if + end if + end if + end for + end if + end if + end if req = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params) req.SetRequest("POST") - return postJson(req, FormatJson(body)) + return postJson(req, FormatJson({ "DeviceProfile": deviceProfile })) end function ' Search across all libraries diff --git a/source/utils/session.bs b/source/utils/session.bs index 497ff310b..c82cc71e7 100644 --- a/source/utils/session.bs +++ b/source/utils/session.bs @@ -15,6 +15,9 @@ namespace session Policy: {}, settings: {}, lastRunVersion: invalid + }, + video: { + json: {} } } }) @@ -30,7 +33,7 @@ namespace session ' Update one value from the global session array (m.global.session) sub Update(key as string, value = {} as object) ' validate parameters - if key = "" or (key <> "user" and key <> "server") or value = invalid + if key = "" or (key <> "user" and key <> "server" and key <> "video") or value = invalid print "Error in session.Update(): Invalid parameters provided" return end if @@ -429,4 +432,19 @@ namespace session end sub end namespace end namespace + + namespace video + ' Return the global video session array to it's default state + sub Delete() + session.Update("video", { json: {} }) + end sub + + ' Update the global video session array (m.global.session.video) + sub Update(videoMetaData as object) + if videoMetaData = invalid then return + + session.video.Delete() + session.Update("video", videoMetaData) + end sub + end namespace end namespace From 050bdde4f22efc9669df2214467a0287e6be0752 Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Fri, 4 Oct 2024 11:07:56 -0400 Subject: [PATCH 16/18] Use the current video dimensions to set MaxVideoDecodeResolution and update video length and streamformat --- components/ItemGrid/LoadVideoContentTask.bs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/components/ItemGrid/LoadVideoContentTask.bs b/components/ItemGrid/LoadVideoContentTask.bs index bfbfb2c6a..2142477c3 100644 --- a/components/ItemGrid/LoadVideoContentTask.bs +++ b/components/ItemGrid/LoadVideoContentTask.bs @@ -79,6 +79,16 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s end if session.video.Update(meta) + + if isValid(meta.json.MediaSources[0].RunTimeTicks) + if meta.json.MediaSources[0].RunTimeTicks = 0 + video.length = 0 + else + video.length = meta.json.MediaSources[0].RunTimeTicks / 10000000 + end if + end if + video.MaxVideoDecodeResolution = [meta.json.MediaSources[0].MediaStreams[0].Width, meta.json.MediaSources[0].MediaStreams[0].Height] + subtitle_idx = m.top.selectedSubtitleIndex videotype = LCase(meta.type) @@ -183,6 +193,11 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s end if video.container = getContainerType(meta) + if video.container = "mp4" + video.content.StreamFormat = "mp4" + else if video.container = "mkv" + video.content.StreamFormat = "mkv" + end if if not isValid(m.playbackInfo.MediaSources[0]) m.playbackInfo = meta.json From 42bb0ebecbfae61528ec89b0867a4f47ec08245f Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Fri, 4 Oct 2024 11:27:45 -0400 Subject: [PATCH 17/18] validate data to prevent crash --- source/api/Items.bs | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/source/api/Items.bs b/source/api/Items.bs index 2a9f6c72b..c7ba49899 100644 --- a/source/api/Items.bs +++ b/source/api/Items.bs @@ -38,28 +38,31 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT end if if audioTrackIndex > -1 - params.AudioStreamIndex = audioTrackIndex - - ' force the server to transcode AAC profiles we don't support to MP3 instead of the usual AAC - ' TODO: Remove this after server adds support for transcoding AAC from one profile to another selectedAudioStream = m.global.session.video.json.MediaStreams[audioTrackIndex] - if LCase(selectedAudioStream.Codec) = "aac" - if LCase(selectedAudioStream.Profile) = "main" or LCase(selectedAudioStream.Profile) = "he-aac" - for each rule in deviceProfile.TranscodingProfiles - if rule.Container = "ts" or rule.Container = "mp4" - if rule.AudioCodec = "aac" - rule.AudioCodec = "mp3" - else if rule.AudioCodec.Left(4) = "aac," - rule.AudioCodec = mid(rule.AudioCodec, 5) - - if rule.AudioCodec.Left(3) <> "mp3" - rule.AudioCodec = "mp3," + rule.AudioCodec + if selectedAudioStream <> invalid + params.AudioStreamIndex = audioTrackIndex + + ' force the server to transcode AAC profiles we don't support to MP3 instead of the usual AAC + ' TODO: Remove this after server adds support for transcoding AAC from one profile to another + if LCase(selectedAudioStream.Codec) = "aac" + if LCase(selectedAudioStream.Profile) = "main" or LCase(selectedAudioStream.Profile) = "he-aac" + for each rule in deviceProfile.TranscodingProfiles + if rule.Container = "ts" or rule.Container = "mp4" + if rule.AudioCodec = "aac" + rule.AudioCodec = "mp3" + else if rule.AudioCodec.Left(4) = "aac," + rule.AudioCodec = mid(rule.AudioCodec, 5) + + if rule.AudioCodec.Left(3) <> "mp3" + rule.AudioCodec = "mp3," + rule.AudioCodec + end if end if end if - end if - end for + end for + end if end if + end if end if From b1809c081595205156c870b08eb45b0c4e4a055a Mon Sep 17 00:00:00 2001 From: Charles Ewert Date: Sat, 5 Oct 2024 11:52:36 -0400 Subject: [PATCH 18/18] use async when fetching captions to prevent crash --- components/captionTask.bs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/components/captionTask.bs b/components/captionTask.bs index 8ea8e9b4f..0dc4d99b8 100644 --- a/components/captionTask.bs +++ b/components/captionTask.bs @@ -1,7 +1,9 @@ import "pkg:/source/utils/config.bs" import "pkg:/source/api/baserequest.bs" +import "pkg:/source/roku_modules/log/LogMixin.brs" sub init() + m.log = log.Logger("captionTask") m.top.observeField("url", "fetchCaption") m.top.currentCaption = [] m.top.currentPos = 0 @@ -41,17 +43,26 @@ sub setFont() end sub sub fetchCaption() + m.log.debug("start fetchCaption()") m.captionTimer.control = "stop" re = CreateObject("roRegex", "(http.*?\.vtt)", "s") url = re.match(m.top.url)[0] + if url <> invalid + port = createObject("roMessagePort") m.reader.setUrl(url) - text = m.reader.GetToString() - m.captionList = parseVTT(text) - m.captionTimer.control = "start" + m.reader.setMessagePort(port) + if m.reader.AsyncGetToString() + msg = port.waitMessage(0) + if type(msg) = "roUrlEvent" + m.captionList = parseVTT(msg.GetString()) + m.captionTimer.control = "start" + end if + end if else m.captionTimer.control = "stop" end if + m.log.debug("end fetchCaption()", url) end sub function newlabel(txt)