' Play Audio sub CreateAudioPlayerView() m.view = CreateObject("roSGNode", "AudioPlayerView") m.view.observeField("state", "onStateChange") m.global.sceneManager.callFunc("pushScene", m.view) end sub ' Play Video sub CreateVideoPlayerView() m.playbackData = {} m.selectedSubtitle = {} m.view = CreateObject("roSGNode", "VideoPlayerView") m.view.observeField("state", "onStateChange") m.view.observeField("selectPlaybackInfoPressed", "onSelectPlaybackInfoPressed") m.view.observeField("selectSubtitlePressed", "onSelectSubtitlePressed") m.view.observeField("selectAudioPressed", "onSelectAudioPressed") mediaSourceId = m.global.queueManager.callFunc("getCurrentItem").mediaSourceId if not isValid(mediaSourceId) or mediaSourceId = "" mediaSourceId = m.global.queueManager.callFunc("getCurrentItem").id end if m.getPlaybackInfoTask = createObject("roSGNode", "GetPlaybackInfoTask") m.getPlaybackInfoTask.videoID = mediaSourceId m.getPlaybackInfoTask.observeField("data", "onPlaybackInfoLoaded") m.global.sceneManager.callFunc("pushScene", m.view) end sub ' ----------------- ' Event Handlers ' ----------------- ' onSelectAudioPressed: Display audio selection dialog ' sub onSelectAudioPressed() audioData = { data: [] } for each item in m.view.fullAudioData audioStreamItem = { "Index": item.Index, "IsExternal": item.IsExternal, "Track": { "description": item.DisplayTitle }, "Type": "audioselection" } if m.view.audioIndex = item.Index audioStreamItem.selected = true end if audioData.data.push(audioStreamItem) end for m.global.sceneManager.callFunc("radioDialog", tr("Select Audio"), audioData) m.global.sceneManager.observeField("returnData", "onSelectionMade") end sub ' User requested subtitle selection popup sub onSelectSubtitlePressed() subtitleData = { data: [] } for each item in m.view.fullSubtitleData item.type = "subtitleselection" if m.view.selectedSubtitle <> -1 ' Subtitle is a track within the file if item.index = m.view.selectedSubtitle item.selected = true end if else ' Subtitle is from an external source availableSubtitleTrackIndex = availSubtitleTrackIdx(item.track.TrackName) if availableSubtitleTrackIndex <> -1 ' Convert Jellyfin subtitle track name to Roku track name subtitleFullTrackName = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName if subtitleFullTrackName = m.view.subtitleTrack item.selected = true end if end if end if ' Put the selected item at the top of the option list if isValid(item.selected) and item.selected subtitleData.data.Unshift(item) else subtitleData.data.push(item) end if end for ' Manually create the None option and place at top subtitleData.data.Unshift({ "Index": -1, "IsExternal": false, "Track": { "description": "None" }, "Type": "subtitleselection" }) m.global.sceneManager.callFunc("radioDialog", tr("Select Subtitles"), subtitleData) m.global.sceneManager.observeField("returnData", "onSelectionMade") end sub ' User has selected something from the radioDialog popup sub onSelectionMade() m.global.sceneManager.unobserveField("returnData") if not isValid(m.global.sceneManager.returnData) then return if not isValid(m.global.sceneManager.returnData.type) then return if LCase(m.global.sceneManager.returnData.type) = "subtitleselection" processSubtitleSelection() return end if if LCase(m.global.sceneManager.returnData.type) = "audioselection" processAudioSelection() return end if end sub ' processAudioSelection: Audio track selection handler ' sub processAudioSelection() selectedAudioTrack = m.global.sceneManager.returnData if isValid(selectedAudioTrack) if isValid(selectedAudioTrack.index) m.view.audioIndex = selectedAudioTrack.index end if end if end sub sub processSubtitleSelection() m.selectedSubtitle = m.global.sceneManager.returnData ' The selected encoded subtitle did not change. if m.view.selectedSubtitle <> -1 or m.selectedSubtitle.index <> -1 if m.view.selectedSubtitle = m.selectedSubtitle.index then return end if ' The playbackData is now outdated and must be refreshed m.playbackData = invalid ' Find previously selected subtitle and identify if it was encoded for each item in m.view.fullSubtitleData if item.index = m.view.selectedSubtitle m.view.previousSubtitleWasEncoded = item.IsEncoded exit for end if end for if LCase(m.selectedSubtitle.track.description) = "none" m.view.globalCaptionMode = "Off" m.view.subtitleTrack = "" if m.view.selectedSubtitle <> -1 m.view.selectedSubtitle = -1 end if return end if if m.selectedSubtitle.IsEncoded ' Roku can not natively display these subtitles, so turn off the caption mode on the device m.view.globalCaptionMode = "Off" else ' Roku can natively display these subtitles, ensure the caption mode on the device is on m.view.globalCaptionMode = "On" ' Roku may rearrange subtitle tracks. Look up track based on name to ensure we get the correct index availableSubtitleTrackIndex = availSubtitleTrackIdx(m.selectedSubtitle.Track.TrackName) if availableSubtitleTrackIndex = -1 then return m.view.subtitleTrack = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName end if m.view.selectedSubtitle = m.selectedSubtitle.Index end sub ' User requested playback info sub onSelectPlaybackInfoPressed() ' Check if we already have playback info and show it in a popup if isValid(m.playbackData) and isValid(m.playbackData.playbackinfo) m.global.sceneManager.callFunc("standardDialog", tr("Playback Info"), m.playbackData.playbackinfo) return end if m.getPlaybackInfoTask.control = "RUN" end sub ' The playback info task has returned data sub onPlaybackInfoLoaded() m.playbackData = m.getPlaybackInfoTask.data ' Check if we have playback info and show it in a popup if isValid(m.playbackData) and isValid(m.playbackData.playbackinfo) m.global.sceneManager.callFunc("standardDialog", tr("Playback Info"), m.playbackData.playbackinfo) end if end sub ' Playback state change event handlers sub onStateChange() if LCase(m.view.state) = "finished" ' Close any open dialogs if m.global.sceneManager.callFunc("isDialogOpen") m.global.sceneManager.callFunc("dismissDialog") end if ' If there is something next in the queue, play it if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1 m.global.sceneManager.callFunc("clearPreviousScene") m.global.queueManager.callFunc("moveForward") m.global.queueManager.callFunc("playQueue") return end if ' Playback completed, return user to previous screen m.global.sceneManager.callFunc("popScene") m.global.audioPlayer.loopMode = "" end if end sub ' Roku translates the info provided in subtitleTracks into availableSubtitleTracks ' Including ignoring tracks, if they are not understood, thus making indexing unpredictable. ' This function translates between our internel selected subtitle index ' and the corresponding index in availableSubtitleTracks. function availSubtitleTrackIdx(tracknameToFind as string) as integer idx = 0 for each availTrack in m.view.availableSubtitleTracks ' The TrackName must contain the URL we supplied originally, though ' Roku mangles the name a bit, so we check if the URL is a substring, rather ' than strict equality if Instr(1, availTrack.TrackName, tracknameToFind) return idx end if idx = idx + 1 end for return -1 end function