diff --git a/components/.DS_Store b/components/.DS_Store
new file mode 100644
index 00000000..306a9312
Binary files /dev/null and b/components/.DS_Store differ
diff --git a/components/data/MovieData.brs b/components/data/MovieData.brs
index 2970d94c..a37bb526 100644
--- a/components/data/MovieData.brs
+++ b/components/data/MovieData.brs
@@ -8,6 +8,13 @@ sub setFields()
m.top.watched = json.UserData.played
m.top.Type = "Movie"
+ if json.MediaSourceCount <> invalid and json.MediaSourceCount > 1
+ m.top.mediaSources = []
+ for each source in json.MediaSources
+ m.top.mediaSources.push(source)
+ end for
+ end if
+
if json.ProductionYear <> invalid
m.top.SubTitle = json.ProductionYear
end if
diff --git a/components/data/MovieData.xml b/components/data/MovieData.xml
index 86aba8a0..2d73e676 100644
--- a/components/data/MovieData.xml
+++ b/components/data/MovieData.xml
@@ -5,6 +5,8 @@
+
+
diff --git a/components/movies/MovieOptions.brs b/components/movies/MovieAudioOptions.brs
similarity index 96%
rename from components/movies/MovieOptions.brs
rename to components/movies/MovieAudioOptions.brs
index 7c5acc2e..beef2e07 100644
--- a/components/movies/MovieOptions.brs
+++ b/components/movies/MovieAudioOptions.brs
@@ -41,7 +41,7 @@ sub optionsSet()
if view.Selected <> invalid and view.Selected = true
selectedViewIndex = index
entry.selected = true
- m.top.audioSteamIndex = view.streamIndex
+ m.top.audioStreamIndex = view.streamIndex
end if
index = index + 1
end for
@@ -90,7 +90,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
newSelection = selMenu.content.GetChild(selIndex)
newSelection.selected = true
m.selectedAudioIndex = selIndex
- m.top.audioSteamIndex = newSelection.streamIndex
+ m.top.audioStreamIndex = newSelection.streamIndex
end if
end if
return true
diff --git a/components/movies/MovieOptions.xml b/components/movies/MovieAudioOptions.xml
similarity index 89%
rename from components/movies/MovieOptions.xml
rename to components/movies/MovieAudioOptions.xml
index 0d77a474..5182057b 100644
--- a/components/movies/MovieOptions.xml
+++ b/components/movies/MovieAudioOptions.xml
@@ -1,5 +1,5 @@
-
+
@@ -31,7 +31,7 @@
-
+
-
+
diff --git a/components/movies/MovieDetails.brs b/components/movies/MovieDetails.brs
index bbf79b99..bbfa02ac 100644
--- a/components/movies/MovieDetails.brs
+++ b/components/movies/MovieDetails.brs
@@ -2,14 +2,21 @@ sub init()
m.extrasGrp = m.top.findnode("extrasGrp")
m.extrasGrid = m.top.findNode("extrasGrid")
m.top.optionsAvailable = false
+
+ m.audioOptions = m.top.findNode("audioOptions")
+ m.videoOptions = m.top.findNode("videoOptions")
+
m.main_group = m.top.findNode("main_group")
- m.options = m.top.findNode("options")
+
main = m.top.findNode("main_group")
main.translation = [96, 175]
overview = m.top.findNode("overview")
overview.width = 1920 - 96 - 300 - 96 - 30
+
m.buttonGrp = m.top.findNode("buttons")
m.buttonGrp.setFocus(true)
+
+ m.top.observeField("itemContent", "itemContentChanged")
end sub
sub itemContentChanged()
@@ -19,13 +26,11 @@ sub itemContentChanged()
m.top.id = itemData.id
m.top.findNode("moviePoster").uri = m.top.itemContent.posterURL
+ ' Set default video source
+ m.top.selectedVideoStreamId = itemData.MediaSources[0].id
+
' Find first Audio Stream and set that as default
- for i = 0 to itemData.mediaStreams.Count() - 1
- if itemData.mediaStreams[i].Type = "Audio"
- m.top.selectedAudioStreamIndex = i
- exit for
- end if
- end for
+ SetDefaultAudioTrack(itemData)
' Handle all "As Is" fields
m.top.overhangTitle = itemData.name
@@ -79,9 +84,6 @@ sub itemContentChanged()
if itemData.mediaStreams[0] <> invalid
setFieldText("video_codec", tr("Video") + ": " + itemData.mediaStreams[0].displayTitle)
end if
- if itemData.mediaStreams[m.top.selectedAudioStreamIndex] <> invalid
- setFieldText("audio_codec", tr("Audio") + ": " + itemData.mediaStreams[m.top.selectedAudioStreamIndex].displayTitle)
- end if
' TODO - cmon now. these are buttons, not words
if itemData.taglines.count() > 0
setFieldText("tagline", itemData.taglines[0])
@@ -89,11 +91,29 @@ sub itemContentChanged()
setFavoriteColor()
setWatchedColor()
- SetUpOptions(itemData.mediaStreams)
+ SetUpVideoOptions(itemData.mediaSources)
+ SetUpAudioOptions(itemData.mediaStreams)
end sub
-sub SetUpOptions(streams)
+sub SetUpVideoOptions(streams)
+
+ videos = []
+
+ for i = 0 to streams.Count() - 1
+ if streams[i].VideoType = "VideoFile"
+ videos.push({ "Title": streams[i].Name, "Description": tr("Video"), "Selected": m.top.selectedVideoStreamId = streams[i].id, "StreamID": streams[i].id, "video_codec": streams[i].mediaStreams[0].displayTitle })
+ end if
+ end for
+
+ options = {}
+ options.views = videos
+ m.videoOptions.options = options
+
+end sub
+
+
+sub SetUpAudioOptions(streams)
tracks = []
@@ -105,11 +125,21 @@ sub SetUpOptions(streams)
options = {}
options.views = tracks
- m.options.options = options
+ m.audioOptions.options = options
end sub
+sub SetDefaultAudioTrack(itemData)
+ for i = 0 to itemData.mediaStreams.Count() - 1
+ if itemData.mediaStreams[i].Type = "Audio"
+ m.top.selectedAudioStreamIndex = i
+ setFieldText("audio_codec", tr("Audio") + ": " + itemData.mediaStreams[i].displayTitle)
+ exit for
+ end if
+ end for
+end sub
+
sub setFieldText(field, value)
node = m.top.findNode(field)
if node = invalid or value = invalid then return
@@ -186,22 +216,50 @@ end function
'
'Check if options updated and any reloading required
-sub optionsClosed()
- if m.options.audioSteamIndex <> m.top.selectedAudioStreamIndex
- m.top.selectedAudioStreamIndex = m.options.audioSteamIndex
+sub audioOptionsClosed()
+ if m.audioOptions.audioStreamIndex <> m.top.selectedAudioStreamIndex
+ m.top.selectedAudioStreamIndex = m.audioOptions.audioStreamIndex
setFieldText("audio_codec", tr("Audio") + ": " + m.top.itemContent.json.mediaStreams[m.top.selectedAudioStreamIndex].displayTitle)
end if
m.top.findNode("buttons").setFocus(true)
end sub
+'
+' Check if options were updated and if any reloding is needed...
+sub videoOptionsClosed()
+ if m.videoOptions.videoStreamId <> m.top.selectedVideoStreamId
+ m.top.selectedVideoStreamId = m.videoOptions.videoStreamId
+ setFieldText("video_codec", tr("Video") + ": " + m.videoOptions.video_codec)
+ ' Because the video stream has changed (i.e. the actual video)... we need to reload the audio stream choices for that video
+ m.top.unobservefield("itemContent")
+ itemData = m.top.itemContent.json
+ for each mediaSource in itemData.mediaSources
+ if mediaSource.id = m.top.selectedVideoStreamId
+ itemData.mediaStreams = []
+ for i = 0 to mediaSource.mediaStreams.Count() - 1
+ itemData.mediaStreams.push(mediaSource.mediaStreams[i])
+ end for
+ SetDefaultAudioTrack(itemData)
+ SetUpAudioOptions(itemData.mediaStreams)
+ exit for
+ end if
+ end for
+ m.top.itemContent.json = itemData
+ m.top.observeField("itemContent", "itemContentChanged")
+ 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)
+ if key = "OK" and m.top.findNode("video-button").isInFocusChain()
+ m.videoOptions.visible = true
+ m.videoOptions.setFocus(true)
+ else if key = "OK" and m.top.findNode("audio-button").isInFocusChain()
+ m.audioOptions.visible = true
+ m.audioOptions.setFocus(true)
end if
if key = "down" and m.buttonGrp.isInFocusChain()
@@ -224,19 +282,14 @@ function onKeyEvent(key as string, press as boolean) as boolean
if not press then return false
- if key = "options"
- if m.options.visible = true
- m.options.visible = false
- optionsClosed()
- else
- m.options.visible = true
- m.options.setFocus(true)
- end if
- return true
- else if key = "back"
- if m.options.visible = true
- m.options.visible = false
- optionsClosed()
+ if key = "back"
+ if m.audioOptions.visible = true
+ m.audioOptions.visible = false
+ audioOptionsClosed()
+ return true
+ else if m.videoOptions.visible = true
+ m.videoOptions.visible = false
+ videoOptionsClosed()
return true
end if
end if
diff --git a/components/movies/MovieDetails.xml b/components/movies/MovieDetails.xml
index 2986dc1e..64f1409c 100644
--- a/components/movies/MovieDetails.xml
+++ b/components/movies/MovieDetails.xml
@@ -25,23 +25,25 @@
-
-
-
-
+
+
+
+
+
-
+
+
-
-
+
+
diff --git a/components/movies/MovieVideoOptions.brs b/components/movies/MovieVideoOptions.brs
new file mode 100644
index 00000000..8fb1007a
--- /dev/null
+++ b/components/movies/MovieVideoOptions.brs
@@ -0,0 +1,112 @@
+sub init()
+
+ m.buttons = m.top.findNode("buttons")
+ m.buttons.buttons = [tr("Version")]
+ m.buttons.setFocus(true)
+
+ m.selectedItem = 0
+ m.selectedVideoIndex = 0
+
+ m.menus = []
+ m.menus.push(m.top.findNode("videoMenu"))
+
+ m.viewNames = []
+
+ ' Set button color to global
+ m.top.findNode("videoMenu").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
+ viewContent = CreateObject("roSGNode", "ContentNode")
+ index = 0
+ selectedViewIndex = 0
+
+ for each view in m.top.options.views
+ entry = viewContent.CreateChild("VideoTrackListData")
+ entry.title = view.Title
+ entry.description = view.Description
+ entry.streamId = view.streamId
+ entry.video_codec = view.video_codec
+ m.viewNames.push(view.Name)
+ if view.Selected <> invalid and view.Selected = true
+ 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.selectedVideoIndex = 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())
+ 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
+
+ if m.selectedVideoIndex = selIndex
+ else
+ selMenu.content.GetChild(m.selectedVideoIndex).selected = false
+ newSelection = selMenu.content.GetChild(selIndex)
+ newSelection.selected = true
+ m.selectedVideoIndex = selIndex
+ m.top.videoStreamId = newSelection.streamId
+ m.top.video_codec = newSelection.video_codec
+ 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
\ No newline at end of file
diff --git a/components/movies/MovieVideoOptions.xml b/components/movies/MovieVideoOptions.xml
new file mode 100644
index 00000000..365d6a91
--- /dev/null
+++ b/components/movies/MovieVideoOptions.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/movies/VideoTrackListData.xml b/components/movies/VideoTrackListData.xml
new file mode 100644
index 00000000..371edcfb
--- /dev/null
+++ b/components/movies/VideoTrackListData.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/components/movies/VideoTrackListItem.brs b/components/movies/VideoTrackListItem.brs
new file mode 100644
index 00000000..9c6d5af9
--- /dev/null
+++ b/components/movies/VideoTrackListItem.brs
@@ -0,0 +1,33 @@
+sub init()
+ m.title = m.top.findNode("title")
+ m.description = m.top.findNode("description")
+ m.selectedIcon = m.top.findNode("selectedIcon")
+end sub
+
+sub itemContentChanged()
+ m.title.text = m.top.itemContent.title
+ m.description.text = m.top.itemContent.description
+
+ if m.top.itemContent.description = ""
+ m.title.translation = [50, 20]
+ end if
+
+ if m.top.itemContent.selected
+ m.selectedIcon.uri = m.global.constants.icons.check_white
+ else
+ m.selectedIcon.uri = ""
+ end if
+
+end sub
+
+'
+'Scroll description if focused
+sub focusChanged()
+
+ if m.top.itemHasFocus = true
+ m.description.repeatCount = -1
+ else
+ m.description.repeatCount = 0
+ end if
+
+end sub
\ No newline at end of file
diff --git a/components/movies/VideoTrackListItem.xml b/components/movies/VideoTrackListItem.xml
new file mode 100644
index 00000000..0e8d38bf
--- /dev/null
+++ b/components/movies/VideoTrackListItem.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/locale/en_US/translations.ts b/locale/en_US/translations.ts
index 1db137d4..c9114024 100644
--- a/locale/en_US/translations.ts
+++ b/locale/en_US/translations.ts
@@ -461,5 +461,9 @@
An error was encountered while playing this item. Server did not provide required transcoding data.
Content of message box when trying to play an item which requires transcoding, and the server did not provide transcode url
+
+
+ Version
+
diff --git a/source/Main.brs b/source/Main.brs
index dc9d8efd..20f4cebf 100644
--- a/source/Main.brs
+++ b/source/Main.brs
@@ -195,14 +195,20 @@ sub Main (args as dynamic) as void
btn = getButton(msg)
group = sceneManager.callFunc("getActiveScene")
if btn <> invalid and btn.id = "play-button"
- ' Check is a specific Audio Stream was selected
+ ' Check if a specific Audio Stream was selected
audio_stream_idx = 1
if group.selectedAudioStreamIndex <> invalid
audio_stream_idx = group.selectedAudioStreamIndex
end if
+ ' Check to see if a specific video "version" was selected
+ mediaSourceId = invalid
+ if group.selectedVideoStreamId <> invalid
+ mediaSourceId = group.selectedVideoStreamId
+ end if
video_id = group.id
- video = CreateVideoPlayerGroup(video_id, audio_stream_idx)
+
+ video = CreateVideoPlayerGroup(video_id, mediaSourceId, audio_stream_idx)
if video <> invalid
sceneManager.callFunc("pushScene", video)
end if
diff --git a/source/ShowScenes.brs b/source/ShowScenes.brs
index 6ca9b449..fbe1cb2b 100644
--- a/source/ShowScenes.brs
+++ b/source/ShowScenes.brs
@@ -373,9 +373,9 @@ sub CreateSidePanel(buttons, options)
group.options = options
end sub
-function CreateVideoPlayerGroup(video_id, audio_stream_idx = 1)
+function CreateVideoPlayerGroup(video_id, mediaSourceId = invalid, audio_stream_idx = 1)
' Video is Playing
- video = VideoPlayer(video_id, audio_stream_idx)
+ video = VideoPlayer(video_id, mediaSourceId, audio_stream_idx)
if video = invalid then return invalid
video.observeField("selectSubtitlePressed", m.port)
video.observeField("state", m.port)
diff --git a/source/VideoPlayer.brs b/source/VideoPlayer.brs
index 5de6f33b..722d8fe9 100644
--- a/source/VideoPlayer.brs
+++ b/source/VideoPlayer.brs
@@ -1,9 +1,9 @@
-function VideoPlayer(id, audio_stream_idx = 1, subtitle_idx = -1)
+function VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle_idx = -1)
' Get video controls and UI
video = CreateObject("roSGNode", "JFVideo")
video.id = id
- AddVideoContent(video, audio_stream_idx, subtitle_idx)
+ AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx)
if video.content = invalid
return invalid
@@ -16,7 +16,7 @@ function VideoPlayer(id, audio_stream_idx = 1, subtitle_idx = -1)
return video
end function
-sub AddVideoContent(video, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1)
+sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1)
video.content = createObject("RoSGNode", "ContentNode")
@@ -64,12 +64,15 @@ sub AddVideoContent(video, audio_stream_idx = 1, subtitle_idx = -1, playbackPosi
video.content.PlayStart = int(playbackPosition / 10000000)
' Call PlayInfo from server
- mediaSourceId = video.id
+ if mediaSourceId = invalid
+ mediaSourceId = video.id
+ end if
if meta.live then mediaSourceId = "" ' Don't send mediaSourceId for Live media
+
playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
video.videoId = video.id
- video.mediaSourceId = video.id
+ video.mediaSourceId = mediaSourceId
video.audioIndex = audio_stream_idx
if playbackInfo = invalid
@@ -112,6 +115,9 @@ sub AddVideoContent(video, audio_stream_idx = 1, subtitle_idx = -1, playbackPosi
"PlaySessionId": video.PlaySessionId,
"AudioStreamIndex": audio_stream_idx
})
+ if mediaSourceId <> ""
+ params.MediaSourceId = mediaSourceId
+ end if
video.content.url = buildURL(Substitute("Videos/{0}/stream", video.id), params)
video.isTranscoded = false
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
diff --git a/source/utils/Subtitles.brs b/source/utils/Subtitles.brs
index 6a6f352d..12479d7f 100644
--- a/source/utils/Subtitles.brs
+++ b/source/utils/Subtitles.brs
@@ -49,7 +49,7 @@ sub changeSubtitleDuringPlayback(newid)
' Switching to Encoded Subtitle stream
video.control = "stop"
- AddVideoContent(video, video.audioIndex, newSubtitles.Index, video.position * 10000000)
+ AddVideoContent(video, video.mediaSourceId, video.audioIndex, newSubtitles.Index, video.position * 10000000)
video.control = "play"
video.globalCaptionMode = "Off" ' Using encoded subtitles - so turn off text subtitles
@@ -57,7 +57,7 @@ sub changeSubtitleDuringPlayback(newid)
' Switching from an Encoded stream to a text stream
video.control = "stop"
- AddVideoContent(video, video.audioIndex, -1, video.position * 10000000)
+ AddVideoContent(video, video.mediaSourceId, video.audioIndex, -1, video.position * 10000000)
video.control = "play"
video.globalCaptionMode = "On"
video.subtitleTrack = video.availableSubtitleTracks[newSubtitles.TextIndex].TrackName
@@ -82,7 +82,7 @@ sub turnoffSubtitles()
' Check if Enoded subtitles are being displayed, and turn off
if current > -1 and video.Subtitles[current].IsEncoded
video.control = "stop"
- AddVideoContent(video, video.audioIndex, -1, video.position * 10000000)
+ AddVideoContent(video, video.mediaSourceId, video.audioIndex, -1, video.position * 10000000)
video.control = "play"
end if
end sub