diff --git a/components/Buttons/JFButtons.brs b/components/Buttons/JFButtons.brs
index ec92399ee..1afcc6e8d 100644
--- a/components/Buttons/JFButtons.brs
+++ b/components/Buttons/JFButtons.brs
@@ -15,7 +15,7 @@ sub init()
m.focusRing.color = m.global.constants.colors.button
m.buttonCount = 0
- m.selectedFocusedIndex = 1
+ m.selectedFocusedIndex = 0
m.textSizeTask = createObject("roSGNode", "TextSizeTask")
@@ -25,6 +25,12 @@ sub init()
end sub
+'
+' When Selected Index set, ensure it is the one Focused
+sub selectedIndexChanged()
+ m.selectedFocusedIndex = m.top.selectedIndex
+end sub
+
'
' When options are fully displayed, set focus and selected option
sub renderChanged()
diff --git a/components/Buttons/JFButtons.xml b/components/Buttons/JFButtons.xml
index b7fdde607..55c0814b3 100644
--- a/components/Buttons/JFButtons.xml
+++ b/components/Buttons/JFButtons.xml
@@ -23,7 +23,7 @@
-
+
diff --git a/components/ItemGrid2/ItemGridOptions.brs b/components/ItemGrid2/ItemGridOptions.brs
index cc54a1ffe..44f66a3f3 100644
--- a/components/ItemGrid2/ItemGridOptions.brs
+++ b/components/ItemGrid2/ItemGridOptions.brs
@@ -2,6 +2,7 @@ sub init()
m.buttons = m.top.findNode("buttons")
m.buttons.buttons = [tr("TAB_VIEW"), tr("TAB_SORT"), tr("TAB_FILTER")]
+ m.buttons.selectedIndex = 1
m.buttons.setFocus(true)
m.selectedSortIndex = 0
diff --git a/components/movies/AudioTrackListData.xml b/components/movies/AudioTrackListData.xml
new file mode 100644
index 000000000..a8a7cb851
--- /dev/null
+++ b/components/movies/AudioTrackListData.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/components/movies/AudioTrackListItem.brs b/components/movies/AudioTrackListItem.brs
new file mode 100644
index 000000000..bc4461ec4
--- /dev/null
+++ b/components/movies/AudioTrackListItem.brs
@@ -0,0 +1,33 @@
+function init()
+ m.title = m.top.findNode("title")
+ m.description = m.top.findNode("description")
+ m.selectedIcon = m.top.findNode("selectedIcon")
+end function
+
+function itemContentChanged()
+ m.title.text = m.top.itemContent.title
+ m.description.text = m.top.itemContent.description
+
+ if m.top.itemContent.description = "" then
+ m.title.translation = [50, 20]
+ end if
+
+ if m.top.itemContent.selected then
+ m.selectedIcon.uri = m.global.constants.icons.check_white
+ else
+ m.selectedIcon.uri = ""
+ end if
+
+end function
+
+'
+'Scroll description if focused
+sub focusChanged()
+
+ if m.top.itemHasFocus = true then
+ m.description.repeatCount = -1
+ else
+ m.description.repeatCount = 0
+ end if
+
+end sub
\ No newline at end of file
diff --git a/components/movies/AudioTrackListItem.xml b/components/movies/AudioTrackListItem.xml
new file mode 100644
index 000000000..5e2cf51a9
--- /dev/null
+++ b/components/movies/AudioTrackListItem.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/movies/MovieDetails.brs b/components/movies/MovieDetails.brs
index f18b8dfe9..d980c47e9 100644
--- a/components/movies/MovieDetails.brs
+++ b/components/movies/MovieDetails.brs
@@ -1,5 +1,6 @@
sub init()
m.top.optionsAvailable = false
+ m.options = m.top.findNode("options")
main = m.top.findNode("main_group")
main.translation = [96, 175]
@@ -18,6 +19,14 @@ sub itemContentChanged()
m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL
+ ' Find first Audio Stream and set that as default
+ For i=0 To itemData.mediaStreams.Count() - 1
+ if itemData.mediaStreams[i].Type = "Audio" then
+ m.top.selectedAudioStreamIndex = i
+ exit for
+ end if
+ End For
+
' Handle all "As Is" fields
m.top.overhangTitle = itemData.name
setFieldText("releaseYear", itemData.productionYear)
@@ -56,15 +65,34 @@ sub itemContentChanged()
setFieldText("director", tr("Director") + ": " + director)
end if
setFieldText("video_codec", tr("Video") + ": " + itemData.mediaStreams[0].displayTitle)
- setFieldText("audio_codec", tr("Audio") + ": " + itemData.mediaStreams[1].displayTitle)
+ setFieldText("audio_codec", tr("Audio") + ": " + itemData.mediaStreams[m.top.selectedAudioStreamIndex].displayTitle)
' TODO - cmon now. these are buttons, not words
if itemData.taglines.count() > 0
setFieldText("tagline", itemData.taglines[0])
end if
setFavoriteColor()
setWatchedColor()
+ SetUpOptions(itemData.mediaStreams)
+end sub
+
+
+sub SetUpOptions(streams)
+
+ tracks = []
+
+ for i=0 To streams.Count() - 1
+ if streams[i].Type = "Audio" then
+ tracks.push({"Title": streams[i].displayTitle, "Description" : streams[i].Title, "Selected" : m.top.selectedAudioStreamIndex = i, "StreamIndex" : i})
+ end if
+ end for
+
+ options = {}
+ options.views = tracks
+ m.options.options = options
+
end sub
+
sub setFieldText(field, value)
node = m.top.findNode(field)
if node = invalid or value = invalid then return
@@ -137,3 +165,44 @@ function round(f as float) as integer
return n
end if
end function
+
+'
+'Check if options updated and any reloading required
+sub optionsClosed()
+ if m.options.audioSteamIndex <> m.top.selectedAudioStreamIndex then
+ m.top.selectedAudioStreamIndex = m.options.audioSteamIndex
+ setFieldText("audio_codec", tr("Audio") + ": " + m.top.itemContent.json.mediaStreams[m.top.selectedAudioStreamIndex].displayTitle)
+ end if
+ m.top.findNode("buttons").setFocus(true)
+end sub
+
+
+function onKeyEvent(key as string, press as boolean) as boolean
+
+ ' Due to the way the button pressed event works, need to catch the release for the button as the press is being sent
+ ' directly to the main loop. Will get this sorted in the layout update for Movie Details
+ if (key = "OK" and m.top.findNode("audio-button").isInFocusChain())
+ m.options.visible = true
+ m.options.setFocus(true)
+ end if
+
+ if not press then return false
+
+ if key = "options"
+ if m.options.visible = true then
+ m.options.visible = false
+ optionsClosed()
+ else
+ m.options.visible = true
+ m.options.setFocus(true)
+ end if
+ return true
+ else if key = "back" then
+ if m.options.visible = true then
+ m.options.visible = false
+ optionsClosed()
+ return true
+ end if
+ end if
+ return false
+end function
\ No newline at end of file
diff --git a/components/movies/MovieDetails.xml b/components/movies/MovieDetails.xml
index 7a2b0472f..3e318a943 100644
--- a/components/movies/MovieDetails.xml
+++ b/components/movies/MovieDetails.xml
@@ -25,18 +25,21 @@
-
-
-
+
+
+
+
+
+
diff --git a/components/movies/MovieOptions.brs b/components/movies/MovieOptions.brs
new file mode 100644
index 000000000..9a7ee4ee9
--- /dev/null
+++ b/components/movies/MovieOptions.brs
@@ -0,0 +1,111 @@
+sub init()
+
+ m.buttons = m.top.findNode("buttons")
+ m.buttons.buttons = [tr("Audio")]
+ m.buttons.setFocus(true)
+
+ m.selectedItem = 0
+ m.selectedAudioIndex = 0
+
+ m.menus = []
+ m.menus.push(m.top.findNode("audioMenu"))
+
+ m.viewNames = []
+
+ ' Set button color 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")
+
+end sub
+
+sub optionsSet()
+
+ ' Views Tab
+ if m.top.options.views <> invalid then
+ viewContent = CreateObject("roSGNode", "ContentNode")
+ index = 0
+ selectedViewIndex = 0
+
+ for each view in m.top.options.views
+ entry = viewContent.CreateChild("AudioTrackListData")
+ entry.title = view.Title
+ entry.description = view.Description
+ entry.streamIndex = view.StreamIndex
+ m.viewNames.push(view.Name)
+ if view.Selected <> invalid and view.Selected = true then
+ selectedViewIndex = index
+ entry.selected = true
+ m.top.audioSteamIndex = view.streamIndex
+ end if
+ index = index + 1
+ end for
+
+ m.menus[0].content = viewContent
+ m.menus[0].jumpToItem = selectedViewIndex
+ m.selectedAudioIndex = selectedViewIndex
+ 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()) then
+ 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" then
+ 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()) then
+
+ selMenu = m.menus[m.selectedItem]
+ selIndex = selMenu.itemSelected
+ child = selMenu.content.GetChild(selIndex)
+
+ if m.selectedAudioIndex = selIndex then
+ else
+ selMenu.content.GetChild(m.selectedAudioIndex).selected = false
+ newSelection = selMenu.content.GetChild(selIndex)
+ newSelection.selected = true
+ m.selectedAudioIndex = selIndex
+ m.top.audioSteamIndex = newSelection.streamIndex
+ end if
+ end if
+ return true
+ else if key = "back" or key = "up"
+ if m.menus[m.selectedItem].isInFocusChain() then
+ 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
\ No newline at end of file
diff --git a/components/movies/MovieOptions.xml b/components/movies/MovieOptions.xml
new file mode 100644
index 000000000..fc31e711e
--- /dev/null
+++ b/components/movies/MovieOptions.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/images/icons/check_black.png b/images/icons/check_black.png
new file mode 100644
index 000000000..9b1abb526
Binary files /dev/null and b/images/icons/check_black.png differ
diff --git a/images/icons/check_white.png b/images/icons/check_white.png
new file mode 100644
index 000000000..6ea514690
Binary files /dev/null and b/images/icons/check_white.png differ
diff --git a/source/Main.brs b/source/Main.brs
index e9b039b55..3a52333e1 100644
--- a/source/Main.brs
+++ b/source/Main.brs
@@ -334,10 +334,16 @@ sub Main()
' If a button is selected, we have some determining to do
btn = getButton(msg)
if btn.id = "play-button"
+ ' Check is a specific Audio Stream was selected
+ audio_stream_idx = 1
+ if group.selectedAudioStreamIndex <> invalid
+ audio_stream_idx = group.selectedAudioStreamIndex
+ end if
+
' TODO - Do a better job of picking the last focus
' This is currently page layout Group, button Group, then button
video_id = group.id
- video = CreateVideoPlayerGroup(video_id)
+ video = CreateVideoPlayerGroup(video_id, audio_stream_idx)
if video <> invalid then
group.lastFocus = group.focusedChild.focusedChild.focusedChild
group.setFocus(false)
diff --git a/source/ShowScenes.brs b/source/ShowScenes.brs
index f89b75755..1c3f4b905 100644
--- a/source/ShowScenes.brs
+++ b/source/ShowScenes.brs
@@ -357,9 +357,9 @@ function CreatePaginator()
return group
end function
-function CreateVideoPlayerGroup(video_id)
+function CreateVideoPlayerGroup(video_id, audio_stream_idx = 1)
' Video is Playing
- video = VideoPlayer(video_id)
+ video = VideoPlayer(video_id, audio_stream_idx)
if video = invalid return invalid
timer = video.findNode("playbackTimer")
diff --git a/source/VideoPlayer.brs b/source/VideoPlayer.brs
index 0e8182ca9..e8ac6359a 100644
--- a/source/VideoPlayer.brs
+++ b/source/VideoPlayer.brs
@@ -1,8 +1,8 @@
-function VideoPlayer(id)
+function VideoPlayer(id, audio_stream_idx = 1)
' Get video controls and UI
video = CreateObject("roSGNode", "JFVideo")
video.id = id
- video = VideoContent(video)
+ video = VideoContent(video, audio_stream_idx)
if video = invalid
return invalid
end if
@@ -14,7 +14,7 @@ function VideoPlayer(id)
return video
end function
-function VideoContent(video) as object
+function VideoContent(video, audio_stream_idx = 1) as object
' Get video stream
video.content = createObject("RoSGNode", "ContentNode")
params = {}
@@ -60,7 +60,7 @@ function VideoContent(video) as object
container = getContainerType(meta)
video.container = container
- transcodeParams = getTranscodeParameters(meta)
+ transcodeParams = getTranscodeParameters(meta, audio_stream_idx)
transcodeParams.append({"PlaySessionId": video.PlaySessionId})
if meta.live then
@@ -84,7 +84,7 @@ function VideoContent(video) as object
video.SelectedSubtitle = -1
end if
- if video.SelectedSubtitle <> -1 and displaySubtitlesByUserConfig(video.Subtitles[video.SelectedSubtitle], meta.json.MediaStreams[1]) then
+ if video.SelectedSubtitle <> -1 and displaySubtitlesByUserConfig(video.Subtitles[video.SelectedSubtitle], meta.json.MediaStreams[audio_stream_idx]) then
if video.Subtitles[0].IsTextSubtitleStream then
video.subtitleTrack = video.availableSubtitleTracks[video.Subtitles[0].TextIndex].TrackName
video.suppressCaptions = false
@@ -104,14 +104,15 @@ function VideoContent(video) as object
end if
video.directPlaySupported = directPlaySupported(meta)
- video.decodeAudioSupported = decodeAudioSupported(meta)
+ video.decodeAudioSupported = decodeAudioSupported(meta, audio_stream_idx)
video.transcodeParams = transcodeParams
if video.directPlaySupported and video.decodeAudioSupported and transcodeParams.SubtitleStreamIndex = invalid then
params.append({
"Static": "true",
- "Container": container
- "PlaySessionId": video.PlaySessionId
+ "Container": container,
+ "PlaySessionId": video.PlaySessionId,
+ "AudioStreamIndex": audio_stream_idx
})
video.content.url = buildURL(Substitute("Videos/{0}/stream", video.id), params)
video.content.streamformat = container
@@ -130,13 +131,12 @@ function VideoContent(video) as object
end function
-function getTranscodeParameters(meta as object)
+function getTranscodeParameters(meta as object, audio_stream_idx = 1)
- params = {}
-
- if decodeAudioSupported(meta) and meta.json.MediaStreams[1] <> invalid and meta.json.MediaStreams[1].Type = "Audio" then
- audioCodec = meta.json.MediaStreams[1].codec
- audioChannels = meta.json.MediaStreams[1].channels
+ params = {"AudioStreamIndex": audio_stream_idx}
+ if decodeAudioSupported(meta, audio_stream_idx) and meta.json.MediaStreams[audio_stream_idx] <> invalid and meta.json.MediaStreams[audio_stream_idx].Type = "Audio" then
+ audioCodec = meta.json.MediaStreams[audio_stream_idx].codec
+ audioChannels = meta.json.MediaStreams[audio_stream_idx].channels
else
params.Append({"AudioCodec": "aac"})
@@ -235,14 +235,14 @@ function directPlaySupported(meta as object) as boolean
return devinfo.CanDecodeVideo(streamInfo).result
end function
-function decodeAudioSupported(meta as object) as boolean
+function decodeAudioSupported(meta as object, audio_stream_idx = 1) as boolean
'Check for missing audio and allow playing
- if meta.json.MediaStreams[1] = invalid or meta.json.MediaStreams[1].Type <> "Audio" then return true
+ if meta.json.MediaStreams[audio_stream_idx] = invalid or meta.json.MediaStreams[audio_stream_idx].Type <> "Audio" then return true
devinfo = CreateObject("roDeviceInfo")
- codec = meta.json.MediaStreams[1].codec
- streamInfo = { Codec: codec, ChCnt: meta.json.MediaStreams[1].channels }
+ codec = meta.json.MediaStreams[audio_stream_idx].codec
+ streamInfo = { Codec: codec, ChCnt: meta.json.MediaStreams[audio_stream_idx].channels }
'Otherwise check Roku can decode stream and channels
canDecode = devinfo.CanDecodeAudio(streamInfo)
diff --git a/source/api/constants.brs b/source/api/constants.brs
index 453c1af85..d7f5d3506 100644
--- a/source/api/constants.brs
+++ b/source/api/constants.brs
@@ -18,6 +18,8 @@ sub setConstants()
ascending_white: "pkg:/images/icons/up_white.png",
descending_black: "pkg:/images/icons/down_black.png",
descending_white: "pkg:/images/icons/down_white.png"
+ check_black: "pkg:/images/icons/check_black.png",
+ check_white: "pkg:/images/icons/check_white.png"
}