mirror of
https://github.com/jellyfin/jellyfin-roku.git
synced 2024-11-23 14:19:40 +00:00
Allow selection of different audio tracks for tv shows
This commit is contained in:
parent
e9509a68c5
commit
5ffa09f602
@ -10,6 +10,7 @@
|
||||
<field id="overview" type="string" />
|
||||
<field id="type" type="string" value="Episode" />
|
||||
<field id="json" type="assocarray" onChange="setFields" />
|
||||
<field id="selectedAudioStreamIndex" type="integer" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="TVEpisodeData.brs" />
|
||||
</component>
|
||||
|
@ -52,6 +52,8 @@ function setData()
|
||||
row.appendChild(item)
|
||||
end for
|
||||
|
||||
m.top.doneLoading = true
|
||||
|
||||
return data
|
||||
end function
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
<interface>
|
||||
<field id="objects" type="assocarray" onChange="setupRows" />
|
||||
<field id="escapeButton" type="string" alwaysNotify="true" />
|
||||
<field id="doneLoading" type="boolean" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="TVEpisodeRow.brs" />
|
||||
</component>
|
||||
|
64
components/tvshows/TVEpisodeRowWithOptions.brs
Normal file
64
components/tvshows/TVEpisodeRowWithOptions.brs
Normal file
@ -0,0 +1,64 @@
|
||||
sub init()
|
||||
m.rows = m.top.findNode("tvEpisodeRow")
|
||||
m.tvListOptions = m.top.findNode("tvListOptions")
|
||||
|
||||
m.rows.observeField("doneLoading", "rowsDoneLoading")
|
||||
end sub
|
||||
|
||||
sub setupRows()
|
||||
objects = m.top.objects
|
||||
m.rows.objects = objects
|
||||
end sub
|
||||
|
||||
sub rowsDoneLoading()
|
||||
m.top.doneLoading = true
|
||||
end sub
|
||||
|
||||
sub SetUpAudioOptions(streams)
|
||||
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 })
|
||||
end if
|
||||
end for
|
||||
|
||||
if tracks.count() > 1
|
||||
options = {}
|
||||
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.tvListOptions.audioStreamIndex <> m.top.selectedAudioStreamIndex
|
||||
m.top.objects.items[m.currentSelected].selectedAudioStreamIndex = m.tvListOptions.audioStreamIndex
|
||||
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.rowItemFocused <> invalid
|
||||
m.currentSelected = m.rows.focusedChild.rowItemFocused[0]
|
||||
mediaStreams = m.rows.objects.items[m.currentSelected].json.MediaStreams
|
||||
SetUpAudioOptions(mediaStreams)
|
||||
return true
|
||||
else if key = "back" and m.tvListOptions.visible = true
|
||||
m.tvListOptions.setFocus(false)
|
||||
m.tvListOptions.visible = false
|
||||
m.rows.setFocus(true)
|
||||
audioOptionsClosed()
|
||||
return true
|
||||
else if key = "up" and m.rows.hasFocus() = false
|
||||
m.rows.setFocus(true)
|
||||
else if key = "down" and m.rows.hasFocus() = false
|
||||
m.rows.setFocus(true)
|
||||
end if
|
||||
|
||||
return false
|
||||
end function
|
13
components/tvshows/TVEpisodeRowWithOptions.xml
Normal file
13
components/tvshows/TVEpisodeRowWithOptions.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="TVEpisodeRowWithOptions" extends="Group">
|
||||
<children>
|
||||
<TVEpisodeRow id="tvEpisodeRow" visible="true" />
|
||||
<TVListOptions id="tvListOptions" visible="false" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="objects" type="assocarray" onChange="setupRows"/>
|
||||
<field id="itemSelected" type="integer" />
|
||||
<field id="doneLoading" type="boolean" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="TVEpisodeRowWithOptions.brs" />
|
||||
</component>
|
@ -1,21 +1,36 @@
|
||||
sub init()
|
||||
m.top.optionsAvailable = false
|
||||
|
||||
m.rows = m.top.findNode("picker")
|
||||
m.rows.observeField("doneLoading", "updateSeason")
|
||||
end sub
|
||||
|
||||
sub setSeason()
|
||||
sub setSeasonLoading()
|
||||
m.top.overhangTitle = tr("Loading...")
|
||||
end sub
|
||||
|
||||
sub updateSeason()
|
||||
m.top.overhangTitle = m.top.seasonData.SeriesName + " - " + m.top.seasonData.name
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
handled = false
|
||||
if press
|
||||
if key = "play"
|
||||
itemToPlay = m.top.focusedChild.content.getChild(m.top.focusedChild.rowItemFocused[0]).getChild(0)
|
||||
if itemToPlay <> invalid and itemToPlay.id <> ""
|
||||
m.top.quickPlayNode = itemToPlay
|
||||
end if
|
||||
handled = true
|
||||
|
||||
focusedChild = m.top.focusedChild.focusedChild
|
||||
if focusedChild.content = invalid then return handled
|
||||
|
||||
' OK needs to be handled on release...
|
||||
proceed = false
|
||||
if key = "OK"
|
||||
proceed = true
|
||||
end if
|
||||
|
||||
if press and key = "play" or proceed = true
|
||||
itemToPlay = focusedChild.content.getChild(focusedChild.rowItemFocused[0]).getChild(0)
|
||||
if itemToPlay <> invalid and itemToPlay.id <> ""
|
||||
m.top.quickPlayNode = itemToPlay
|
||||
end if
|
||||
handled = true
|
||||
end if
|
||||
return handled
|
||||
end function
|
||||
|
@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="TVEpisodes" extends="JFGroup">
|
||||
<children>
|
||||
<TVEpisodeRow id="picker" visible="true" />
|
||||
<TVEpisodeRowWithOptions id="picker" visible="true" />
|
||||
</children>
|
||||
<interface>
|
||||
<field id="episodeSelected" alias="picker.itemSelected" />
|
||||
<field id="quickPlayNode" type="node" alwaysNotify="true" />
|
||||
<field id="seasonData" type="assocarray" onChange="setSeason" />
|
||||
<field id="seasonData" type="assocarray" onChange="setSeasonLoading" />
|
||||
<field id="objects" alias="picker.objects" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="TVEpisodes.brs" />
|
||||
|
@ -1,6 +1,7 @@
|
||||
sub init()
|
||||
m.title = m.top.findNode("title")
|
||||
m.title.text = tr("Loading...")
|
||||
m.options = m.top.findNode("tvListOptions")
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
@ -34,7 +35,11 @@ sub itemContentChanged()
|
||||
if itemData.MediaStreams[i].Type = "Video" and videoIdx = invalid
|
||||
videoIdx = i
|
||||
else if itemData.MediaStreams[i].Type = "Audio" and audioIdx = invalid
|
||||
audioIdx = i
|
||||
if item.selectedAudioStreamIndex > 1
|
||||
audioIdx = item.selectedAudioStreamIndex
|
||||
else
|
||||
audioIdx = i
|
||||
end if
|
||||
end if
|
||||
if videoIdx <> invalid and audioIdx <> invalid then exit for
|
||||
end for
|
||||
@ -46,6 +51,23 @@ sub itemContentChanged()
|
||||
m.top.findNode("video_codec").visible = false
|
||||
m.top.findNode("audio_codec").visible = false
|
||||
end if
|
||||
|
||||
DisplayAudioAvailable(itemData.mediaStreams)
|
||||
end sub
|
||||
|
||||
sub DisplayAudioAvailable(streams)
|
||||
|
||||
count = 0
|
||||
for i = 0 to streams.Count() - 1
|
||||
if streams[i].Type = "Audio"
|
||||
count++
|
||||
end if
|
||||
end for
|
||||
|
||||
if count > 1
|
||||
m.top.findnode("audio_codec_count").text = "+" + stri(count - 1).trim()
|
||||
end if
|
||||
|
||||
end sub
|
||||
|
||||
function getRuntime() as integer
|
||||
|
@ -19,7 +19,8 @@
|
||||
<Label id="overview" wrap="true" height="170" width="1200" maxLines="4" ellipsizeOnBoundary="true"/>
|
||||
<LayoutGroup layoutDirection="horiz" itemSpacings="[15]">
|
||||
<Label id="video_codec" />
|
||||
<Label id="audio_codec" />
|
||||
<ScrollingLabel id="audio_codec" />
|
||||
<label id="audio_codec_count" font="font:smallestSystemFont" vertAlign="top" color="#ceffff" />
|
||||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
</LayoutGroup>
|
||||
@ -27,6 +28,7 @@
|
||||
</children>
|
||||
<interface>
|
||||
<field id="itemContent" type="node" onChange="itemContentChanged"/>
|
||||
<field id="selectedAudioStreamIndex" type="integer" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="TVListDetails.brs" />
|
||||
<script type="text/brightscript" uri="pkg:/source/utils/misc.brs" />
|
||||
|
118
components/tvshows/TVListOptions.brs
Normal file
118
components/tvshows/TVListOptions.brs
Normal file
@ -0,0 +1,118 @@
|
||||
' 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.
|
||||
sub init()
|
||||
|
||||
m.buttons = m.top.findNode("buttons")
|
||||
m.buttons.buttons = [tr("Audio")]
|
||||
m.buttons.selectedIndex = 0
|
||||
m.buttons.setFocus(true)
|
||||
|
||||
m.selectedItem = 0
|
||||
m.selectedAudioIndex = 0
|
||||
|
||||
m.menus = [m.top.findNode("audioMenu")]
|
||||
|
||||
m.audioNames = []
|
||||
|
||||
' Set button colors to global
|
||||
m.top.findNode("audioMenu").focusBitmapBlendColor = m.global.constants.colors.button
|
||||
|
||||
' Animation
|
||||
m.fadeAnim = m.top.findNode("fadeAnim")
|
||||
m.fadeOutAnimOpacity = m.top.findNode("outOpacity")
|
||||
m.fadeInAnimOpacity = m.top.findNode("inOpacity")
|
||||
|
||||
m.buttons.observeField("focusedIndex", "buttonFocusChanged")
|
||||
m.buttons.focusedIndex = m.selectedItem
|
||||
|
||||
end sub
|
||||
|
||||
sub optionsSet()
|
||||
' audio Tab
|
||||
if m.top.Options.audios <> invalid
|
||||
audioContent = CreateObject("roSGNode", "ContentNode")
|
||||
index = 0
|
||||
selectedAudioIndex = 0
|
||||
|
||||
for each audio in m.top.options.audios
|
||||
entry = audioContent.CreateChild("AudioTrackListData")
|
||||
entry.title = audio.Title
|
||||
entry.description = audio.Description
|
||||
entry.streamIndex = audio.StreamIndex
|
||||
m.audioNames.push(audio.Name)
|
||||
if audio.Selected <> invalid and audio.Selected = true
|
||||
selectedAudioIndex = index
|
||||
entry.selected = true
|
||||
m.top.audioStreamIndex = audio.streamIndex
|
||||
end if
|
||||
index = index + 1
|
||||
end for
|
||||
|
||||
m.menus[0].content = audioContent
|
||||
m.menus[0].jumpToItem = selectedAudioIndex
|
||||
m.menus[0].checkedItem = selectedAudioIndex
|
||||
m.selectedAudioIndex = selectedAudioIndex
|
||||
end if
|
||||
|
||||
end sub
|
||||
|
||||
' Switch menu shown when button focus changes
|
||||
sub buttonFocusChanged()
|
||||
if m.buttons.focusedIndex = m.selectedItem then return
|
||||
m.fadeOutAnimOpacity.fieldToInterp = m.menus[m.selectedItem].id + ".opacity"
|
||||
m.fadeInAnimOpacity.fieldToInterp = m.menus[m.buttons.focusedIndex].id + ".opacity"
|
||||
m.fadeAnim.control = "start"
|
||||
m.selectedItem = m.buttons.focusedIndex
|
||||
end sub
|
||||
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
||||
if key = "down" or (key = "OK" and m.top.findNode("buttons").hasFocus())
|
||||
m.top.findNode("buttons").setFocus(false)
|
||||
m.menus[m.selectedItem].setFocus(true)
|
||||
m.menus[m.selectedItem].drawFocusFeedback = true
|
||||
|
||||
'If user presses down from button menu, focus first item. If OK, focus checked item
|
||||
if key = "down"
|
||||
m.menus[m.selectedItem].jumpToItem = 0
|
||||
else
|
||||
m.menus[m.selectedItem].jumpToItem = m.menus[m.selectedItem].itemSelected
|
||||
end if
|
||||
|
||||
return true
|
||||
else if key = "OK"
|
||||
if m.menus[m.selectedItem].isInFocusChain()
|
||||
selMenu = m.menus[m.selectedItem]
|
||||
selIndex = selMenu.itemSelected
|
||||
|
||||
' Audio options
|
||||
'if m.selectedItem = 0
|
||||
if m.selectedAudioIndex = selIndex
|
||||
else
|
||||
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
|
||||
return true
|
||||
else if key = "back" or key = "up"
|
||||
if m.menus[m.selectedItem].isInFocusChain()
|
||||
m.buttons.setFocus(true)
|
||||
m.menus[m.selectedItem].drawFocusFeedback = false
|
||||
return true
|
||||
end if
|
||||
else if key = "options"
|
||||
m.menus[m.selectedItem].drawFocusFeedback = false
|
||||
return false
|
||||
end if
|
||||
|
||||
return false
|
||||
|
||||
end function
|
27
components/tvshows/TVListOptions.xml
Normal file
27
components/tvshows/TVListOptions.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<component name="TVListOptions" extends="Group">
|
||||
<children>
|
||||
<Rectangle width="1920" height="1080" color="#000000" opacity="0.75" />
|
||||
<Group translation="[100,100]">
|
||||
<Poster width="1720" height="880" uri="pkg:/images/dialog.9.png" />
|
||||
<LayoutGroup horizAlignment="center" translation="[860,50]" itemSpacings="[50]">
|
||||
<JFButtons id="buttons" />
|
||||
<Group>
|
||||
<RadiobuttonList id="audioMenu" itemspacing="[0,10]" opacity="1" vertFocusAnimationStyle="floatingFocus" drawFocusFeedback="false" />
|
||||
</Group>
|
||||
</LayoutGroup>
|
||||
</Group>
|
||||
|
||||
<Animation id="fadeAnim" duration="0.5" repeat="false">
|
||||
<FloatFieldInterpolator id="outOpacity" key="[0.0, 0.5, 1.0]" keyValue="[ 1, 0, 0 ]" fieldToInterp="focus.opacity" />
|
||||
<FloatFieldInterpolator id="inOpacity" key="[0.0, 0.5, 1.0]" keyValue="[ 0, 0, 1 ]" fieldToInterp="focus.opacity" />
|
||||
</Animation>
|
||||
|
||||
</children>
|
||||
<interface>
|
||||
<field id="buttons" type="nodearray" />
|
||||
<field id="options" type="assocarray" onChange="optionsSet" />
|
||||
<field id="audioStreamIndex" type="integer" />
|
||||
</interface>
|
||||
<script type="text/brightscript" uri="TVListOptions.brs" />
|
||||
</component>
|
@ -88,7 +88,11 @@ sub Main (args as dynamic) as void
|
||||
itemNode = reportingNode.quickPlayNode
|
||||
if itemNode = invalid or itemNode.id = "" then return
|
||||
if itemNode.type = "Episode" or itemNode.type = "Movie" or itemNode.type = "Video"
|
||||
video = CreateVideoPlayerGroup(itemNode.id)
|
||||
if itemNode.type = "Episode" and itemNode.selectedAudioStreamIndex > 1
|
||||
video = CreateVideoPlayerGroup(itemNode.id, invalid, itemNode.selectedAudioStreamIndex)
|
||||
else
|
||||
video = CreateVideoPlayerGroup(itemNode.id)
|
||||
end if
|
||||
if video <> invalid
|
||||
sceneManager.callFunc("pushScene", video)
|
||||
end if
|
||||
@ -103,7 +107,11 @@ sub Main (args as dynamic) as void
|
||||
' play episode
|
||||
' todo: create an episode page to link here
|
||||
video_id = selectedItem.id
|
||||
video = CreateVideoPlayerGroup(video_id)
|
||||
if selectedItem.selectedAudioStreamIndex > 1
|
||||
video = CreateVideoPlayerGroup(video_id, invalid, selectedItem.selectedAudioStreamIndex)
|
||||
else
|
||||
video = CreateVideoPlayerGroup(video_id)
|
||||
end if
|
||||
if video <> invalid
|
||||
sceneManager.callFunc("pushScene", video)
|
||||
end if
|
||||
@ -162,7 +170,11 @@ sub Main (args as dynamic) as void
|
||||
' If you select a TV Episode from ANYWHERE, follow this flow
|
||||
node = getMsgPicker(msg, "picker")
|
||||
video_id = node.id
|
||||
video = CreateVideoPlayerGroup(video_id)
|
||||
if node.selectedAudioStreamIndex > 1
|
||||
video = CreateVideoPlayerGroup(video_id, invalid, node.selectedAudioStreamIndex)
|
||||
else
|
||||
video = CreateVideoPlayerGroup(video_id)
|
||||
end if
|
||||
if video <> invalid
|
||||
sceneManager.callFunc("pushScene", video)
|
||||
end if
|
||||
|
Loading…
Reference in New Issue
Block a user