Merge pull request #1279 from jimdogx/enhancement/jf-1276-tv-versions

Allow selection of TV Show versions
This commit is contained in:
Jimi 2023-06-22 07:02:58 -06:00 committed by GitHub
commit 6c80e8c923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 186 additions and 52 deletions

View File

@ -11,6 +11,7 @@
<field id="type" type="string" value="Episode" />
<field id="startingPoint" type="longinteger" value="0" />
<field id="json" type="assocarray" onChange="setFields" />
<field id="selectedVideoStreamId" type="string" />
<field id="selectedAudioStreamIndex" type="integer" />
<field id="favorite" type="boolean" />
</interface>

View File

@ -1,3 +1,5 @@
import "pkg:/source/utils/misc.brs"
sub init()
m.rows = m.top.findNode("tvEpisodeRow")
m.tvListOptions = m.top.findNode("tvListOptions")
@ -14,26 +16,55 @@ sub rowsDoneLoading()
m.top.doneLoading = true
end sub
sub SetUpAudioOptions(streams)
' List of video versions to choose from
sub SetUpVideoOptions(streams as object)
videos = []
for i = 0 to streams.Count() - 1
if LCase(streams[i].VideoType) = "videofile"
' Create options for user to switch between video tracks
videos.push({
"Title": streams[i].Name,
"Description": tr("Video"),
"Selected": m.top.objects.items[m.currentSelected].selectedVideoStreamId = streams[i].id,
"StreamID": streams[i].id,
"video_codec": streams[i].mediaStreams[0].displayTitle
})
end if
end for
if videos.count() >= 1
options = {}
options.videos = videos
m.tvListOptions.options = options
end if
end sub
' List of audio tracks to choose from
sub SetUpAudioOptions(streams as object)
tracks = []
for i = 0 to streams.Count() - 1
if streams[i].Type = "Audio"
tracks.push({ "Title": streams[i].displayTitle, "Description": streams[i].Title, "Selected": m.top.objects.items[m.currentSelected].selectedAudioStreamIndex = i, "StreamIndex": i })
tracks.push({
"Title": streams[i].displayTitle,
"Description": streams[i].Title,
"Selected": m.top.objects.items[m.currentSelected].selectedAudioStreamIndex = i,
"StreamIndex": i
})
end if
end for
if tracks.count() > 1
if tracks.count() >= 1
options = {}
if isValid(m.tvListOptions.options) and isValid(m.tvListOptions.options.videos)
options.videos = m.tvListOptions.options.videos
end if
options.audios = tracks
m.tvListOptions.options = options
m.tvListOptions.visible = true
m.tvListOptions.setFocus(true)
end if
end sub
'
'Check if options updated and any reloading required
sub audioOptionsClosed()
if m.currentSelected <> invalid
' If the user opened the audio options, we report back even if they left the selection alone.
@ -43,18 +74,43 @@ sub audioOptionsClosed()
end if
end sub
sub videoOptionsClosed()
if m.tvListOptions.videoStreamId <> m.top.objects.items[m.currentSelected].selectedVideoStreamId
m.rows.objects.items[m.currentSelected].selectedVideoStreamId = m.tvListOptions.videoStreamId
end if
end sub
function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
if key = "options" and m.rows.focusedChild <> invalid and m.rows.focusedChild.rowItemFocused <> invalid
if key = "options" and isValid(m.rows.focusedChild) and isValid(m.rows.focusedChild.rowItemFocused)
m.currentSelected = m.rows.focusedChild.rowItemFocused[0]
mediaStreams = m.rows.objects.items[m.currentSelected].json.MediaStreams
mediaSources = m.rows.objects.items[m.currentSelected].json.MediaSources
if m.rows.objects.items[m.currentSelected].selectedVideoStreamId <> ""
for each source in mediaSources
if source.id = m.rows.objects.items[m.currentSelected].selectedVideoStreamId
mediaStreams = source.MediaStreams
exit for
end if
end for
end if
if isValid(mediaSources)
SetUpVideoOptions(mediaSources)
end if
if isValid(mediaStreams)
SetUpAudioOptions(mediaStreams)
end if
if isValid(m.tvListOptions.options)
m.tvListOptions.visible = true
m.tvListOptions.setFocus(true)
end if
return true
else if m.tvListOptions.visible = true and key = "back" or key = "options"
m.tvListOptions.setFocus(false)
m.tvListOptions.visible = false
m.rows.setFocus(true)
videoOptionsClosed()
audioOptionsClosed()
return true
else if key = "up" and m.rows.hasFocus() = false

View File

@ -42,7 +42,7 @@ end sub
function onKeyEvent(key as string, press as boolean) as boolean
handled = false
if key = "left" and not m.Shuffle.hasFocus()
if key = "left" and m.tvEpisodeRow.hasFocus()
m.Shuffle.setFocus(true)
return true
end if
@ -57,7 +57,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
return true
end if
if key = "right" and not m.tvEpisodeRow.hasFocus()
if key = "right" and (m.Random.hasFocus() or m.Shuffle.hasFocus())
m.tvEpisodeRow.setFocus(true)
return true
end if

View File

@ -4,7 +4,6 @@ import "pkg:/source/utils/config.brs"
sub init()
m.title = m.top.findNode("title")
m.title.text = tr("Loading...")
m.options = m.top.findNode("tvListOptions")
m.overview = m.top.findNode("overview")
m.poster = m.top.findNode("poster")
@ -15,11 +14,19 @@ sub init()
m.playedIndicator = m.top.findNode("playedIndicator")
m.checkmark = m.top.findNode("checkmark")
m.checkmark.font.size = 35
m.videoCodec = m.top.findNode("video_codec")
end sub
sub itemContentChanged()
item = m.top.itemContent
itemData = item.json
' Set default video source if user hasn't selected one yet
if item.selectedVideoStreamId = "" and isValid(itemData.MediaSources)
item.selectedVideoStreamId = itemData.MediaSources[0].id
end if
if isValid(itemData.indexNumber)
indexNumber = itemData.indexNumber.toStr() + ". "
else
@ -85,36 +92,62 @@ sub itemContentChanged()
m.progressBar.visible = true
end if
videoIdx = invalid
audioIdx = invalid
' Display current video_codec and check if there is more than one video to choose from...
m.videoCodec.visible = false
if isValid(itemData.MediaSources)
for i = 0 to itemData.MediaSources.Count() - 1
if item.selectedVideoStreamId = itemData.MediaSources[i].id
m.videoCodec.text = tr("Video") + ": " + itemData.MediaSources[i].MediaStreams[0].DisplayTitle
SetupAudioDisplay(itemData.MediaSources[i].MediaStreams, item.selectedAudioStreamIndex)
exit for
end if
end for
m.videoCodec.visible = true
DisplayVideoAvailable(itemData.MediaSources)
end if
end sub
if isValid(itemData.MediaStreams)
for i = 0 to itemData.MediaStreams.Count() - 1
if itemData.MediaStreams[i].Type = "Video" and videoIdx = invalid
videoIdx = i
m.top.findNode("video_codec").text = tr("Video") + ": " + itemData.mediaStreams[videoIdx].DisplayTitle
else if itemData.MediaStreams[i].Type = "Audio" and audioIdx = invalid
if item.selectedAudioStreamIndex > 1
audioIdx = item.selectedAudioStreamIndex
' Display current audio_codec and check if there is more than one audio track to choose from...
sub SetupAudioDisplay(mediaStreams as object, selectedAudioStreamIndex as integer)
audioIdx = invalid
if isValid(mediaStreams)
for i = 0 to mediaStreams.Count() - 1
if LCase(mediaStreams[i].Type) = "audio" and audioIdx = invalid
if selectedAudioStreamIndex > 0 and selectedAudioStreamIndex < mediaStreams.Count()
audioIdx = selectedAudioStreamIndex
else
audioIdx = i
end if
m.top.findNode("audio_codec").text = tr("Audio") + ": " + itemData.mediaStreams[audioIdx].DisplayTitle
m.top.findNode("audio_codec").text = tr("Audio") + ": " + mediaStreams[audioIdx].DisplayTitle
end if
if isValid(videoIdx) and isValid(audioIdx) then exit for
if isValid(audioIdx) then exit for
end for
end if
m.top.findNode("video_codec").visible = isValid(videoIdx)
if isValid(audioIdx)
m.top.findNode("audio_codec").visible = true
DisplayAudioAvailable(itemData.mediaStreams)
DisplayAudioAvailable(mediaStreams)
else
m.top.findNode("audio_codec").visible = false
end if
end sub
sub DisplayAudioAvailable(streams)
' Adds "+N" (e.g. +1) if there is more than one video version to choose from
sub DisplayVideoAvailable(streams as object)
count = 0
for i = 0 to streams.Count() - 1
if LCase(streams[i].VideoType) = "videofile"
count++
end if
end for
if count > 1
m.top.findnode("video_codec_count").text = "+" + stri(count - 1).trim()
end if
end sub
' Adds "+N" (e.g. +1) if there is more than one audio track to choose from
sub DisplayAudioAvailable(streams as object)
count = 0
for i = 0 to streams.Count() - 1
if streams[i].Type = "Audio"

View File

@ -26,8 +26,9 @@
</LayoutGroup>
<Label id="overview" font="font:SmallestSystemFont" wrap="true" height="130" width="950" maxLines="3" ellipsizeOnBoundary="true" />
<LayoutGroup layoutDirection="horiz" itemSpacings="[15]">
<Label id="video_codec" font="font:SmallestSystemFont" />
<ScrollingLabel id="audio_codec" font="font:SmallestSystemFont" />
<ScrollingLabel id="video_codec" font="font:SmallestSystemFont" maxWidth="400" />
<label id="video_codec_count" font="font:smallestSystemFont" vertAlign="top" color="#ceffff" />
<ScrollingLabel id="audio_codec" font="font:SmallestSystemFont" maxWidth="400" />
<label id="audio_codec_count" font="font:smallestSystemFont" vertAlign="top" color="#ceffff" />
</LayoutGroup>
</LayoutGroup>
@ -36,6 +37,7 @@
</children>
<interface>
<field id="itemContent" type="node" onChange="itemContentChanged" />
<field id="selectedVideoStreamId" type="string" />
<field id="selectedAudioStreamIndex" type="integer" />
<field id="itemHasFocus" type="boolean" onChange="focusChanged" />
</interface>

View File

@ -1,22 +1,23 @@
' You may be wondering what's with all the array stuff (m.menus, m.buttons, m.selectedItem etc)?
' This was copied from Movie Options which has both Video and Audio options.
' At the moment we only have Audio Options for TV Episodes, but the code here
' is ready to be expanded easily in the future to add in additional options.
import "pkg:/source/utils/misc.brs"
sub init()
m.buttons = m.top.findNode("buttons")
m.buttons.buttons = [tr("Audio")]
m.buttons.buttons = [tr("Video"), tr("Audio")]
m.buttons.selectedIndex = 0
m.buttons.setFocus(true)
m.selectedItem = 0
m.selectedAudioIndex = 0
m.selectedVideoIndex = 0
m.menus = [m.top.findNode("audioMenu")]
m.menus = [m.top.findNode("videoMenu"), m.top.findNode("audioMenu")]
m.videoNames = []
m.audioNames = []
' Set button colors to global
m.top.findNode("videoMenu").focusBitmapBlendColor = m.global.constants.colors.button
m.top.findNode("audioMenu").focusBitmapBlendColor = m.global.constants.colors.button
' Animation
@ -30,8 +31,35 @@ sub init()
end sub
sub optionsSet()
' Videos Tab
if isValid(m.top.options.videos)
viewContent = CreateObject("roSGNode", "ContentNode")
index = 0
selectedViewIndex = 0
for each view in m.top.options.videos
entry = viewContent.CreateChild("VideoTrackListData")
entry.title = view.Title
entry.description = view.Description
entry.streamId = view.streamId
entry.video_codec = view.video_codec
m.videoNames.push(view.Name)
if isValid(view.Selected) and view.Selected
selectedViewIndex = index
entry.selected = true
m.top.videoStreamId = view.streamId
end if
index = index + 1
end for
m.menus[0].content = viewContent
m.menus[0].jumpToItem = selectedViewIndex
m.menus[0].checkedItem = selectedViewIndex
m.selectedVideoIndex = selectedViewIndex
end if
' audio Tab
if m.top.Options.audios <> invalid
if isValid(m.top.options.audios)
audioContent = CreateObject("roSGNode", "ContentNode")
index = 0
selectedAudioIndex = 0
@ -42,7 +70,7 @@ sub optionsSet()
entry.description = audio.Description
entry.streamIndex = audio.StreamIndex
m.audioNames.push(audio.Name)
if audio.Selected <> invalid and audio.Selected = true
if isValid(audio.Selected) and audio.Selected
selectedAudioIndex = index
entry.selected = true
m.top.audioStreamIndex = audio.streamIndex
@ -50,9 +78,9 @@ sub optionsSet()
index = index + 1
end for
m.menus[0].content = audioContent
m.menus[0].jumpToItem = selectedAudioIndex
m.menus[0].checkedItem = selectedAudioIndex
m.menus[1].content = audioContent
m.menus[1].jumpToItem = selectedAudioIndex
m.menus[1].checkedItem = selectedAudioIndex
m.selectedAudioIndex = selectedAudioIndex
end if
@ -88,17 +116,25 @@ function onKeyEvent(key as string, press as boolean) as boolean
selMenu = m.menus[m.selectedItem]
selIndex = selMenu.itemSelected
' Audio options
'if m.selectedItem = 0
if m.selectedAudioIndex = selIndex
else
'Handle Videos menu
if m.selectedItem = 0
if m.selectedVideoIndex <> selIndex
selMenu.content.GetChild(m.selectedVideoIndex).selected = false
newSelection = selMenu.content.GetChild(selIndex)
newSelection.selected = true
m.selectedVideoIndex = selIndex
m.top.videoStreamId = newSelection.streamId
end if
' Then it is Audio options
else if m.selectedItem = 1
if m.selectedAudioIndex <> selIndex
selMenu.content.GetChild(m.selectedAudioIndex).selected = false
newSelection = selMenu.content.GetChild(selIndex)
newSelection.selected = true
m.selectedAudioIndex = selIndex
m.top.audioStreamIndex = newSelection.streamIndex
end if
'end if
end if
end if
return true

View File

@ -7,7 +7,8 @@
<LayoutGroup horizAlignment="center" translation="[860,50]" itemSpacings="[50]">
<JFButtons id="buttons" />
<Group>
<RadiobuttonList id="audioMenu" itemspacing="[0,10]" opacity="1" vertFocusAnimationStyle="floatingFocus" drawFocusFeedback="false" />
<RadiobuttonList id="videoMenu" itemspacing="[0,10]" opacity="1" vertFocusAnimationStyle="floatingFocus" drawFocusFeedback="false" itemSize="[1500, 70]" numRows="8" />
<RadiobuttonList id="audioMenu" itemspacing="[0,10]" opacity="0" vertFocusAnimationStyle="floatingFocus" drawFocusFeedback="false" itemSize="[1500, 70]" numRows="8" />
</Group>
</LayoutGroup>
</Group>
@ -21,6 +22,7 @@
<interface>
<field id="buttons" type="nodearray" />
<field id="options" type="assocarray" onChange="optionsSet" />
<field id="videoStreamId" type="string" />
<field id="audioStreamIndex" type="integer" />
</interface>
</component>

View File

@ -121,6 +121,10 @@ sub Main (args as dynamic) as void
itemNode = reportingNode.quickPlayNode
if isValid(itemNode) and isValid(itemNode.id) and itemNode.id <> ""
if itemNode.type = "Episode" or itemNode.type = "Movie" or itemNode.type = "Video"
if isValid(itemNode.selectedVideoStreamId)
itemNode.id = itemNode.selectedVideoStreamId
end if
audio_stream_idx = 0
if isValid(itemNode.selectedAudioStreamIndex) and itemNode.selectedAudioStreamIndex > 0
audio_stream_idx = itemNode.selectedAudioStreamIndex

View File

@ -419,7 +419,7 @@ end function
function TVEpisodes(show_id as string, season_id as string) as dynamic
url = Substitute("Shows/{0}/Episodes", show_id)
resp = APIRequest(url, { "seasonId": season_id, "UserId": m.global.session.user.id, "fields": "MediaStreams" })
resp = APIRequest(url, { "seasonId": season_id, "UserId": m.global.session.user.id, "fields": "MediaStreams,MediaSources" })
data = getJson(resp)
' validate data