Update API docs

This commit is contained in:
jellyfin-bot 2024-01-06 01:31:17 +00:00
parent 48c19eda75
commit 9e5bdd4979
12 changed files with 282 additions and 32 deletions

File diff suppressed because one or more lines are too long

View File

@ -9,6 +9,11 @@ import "pkg:/source/api/Image.bs"
import "pkg:/source/api/userauth.bs" import "pkg:/source/api/userauth.bs"
import "pkg:/source/utils/deviceCapabilities.bs" import "pkg:/source/utils/deviceCapabilities.bs"
enum SubtitleSelection
notset = -2
none = -1
end enum
sub init() sub init()
m.user = AboutMe() m.user = AboutMe()
m.top.functionName = "loadItems" m.top.functionName = "loadItems"
@ -46,19 +51,18 @@ sub loadItems()
id = m.top.itemId id = m.top.itemId
mediaSourceId = invalid mediaSourceId = invalid
audio_stream_idx = m.top.selectedAudioStreamIndex audio_stream_idx = m.top.selectedAudioStreamIndex
subtitle_idx = m.top.selectedSubtitleIndex
forceTranscoding = false forceTranscoding = false
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding)] m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, forceTranscoding)]
end sub end sub
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean) as dynamic function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean) as dynamic
video = {} video = {}
video.id = id video.id = id
video.content = createObject("RoSGNode", "ContentNode") video.content = createObject("RoSGNode", "ContentNode")
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding) LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, forceTranscoding)
if video.content = invalid if video.content = invalid
return invalid return invalid
@ -67,9 +71,10 @@ function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic,
return video return video
end function end function
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean) sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean)
meta = ItemMetaData(video.id) meta = ItemMetaData(video.id)
subtitle_idx = m.top.selectedSubtitleIndex
if not isValid(meta) if not isValid(meta)
video.errorMsg = "Error loading metadata" video.errorMsg = "Error loading metadata"
@ -79,6 +84,21 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
videotype = LCase(meta.type) videotype = LCase(meta.type)
' Check for any Live TV streams coming from other places other than the TV Guide
if isValid(meta.json) and isValid(meta.json.ChannelId)
if isValid(meta.json.EpisodeTitle)
meta.title = meta.json.EpisodeTitle
else if isValid(meta.json.Name)
meta.title = meta.json.Name
end if
meta.live = true
if LCase(meta.json.type) = "program"
video.id = meta.json.ChannelId
else
video.id = meta.json.id
end if
end if
if videotype = "episode" or videotype = "series" if videotype = "episode" or videotype = "series"
video.content.contenttype = "episode" video.content.contenttype = "episode"
end if end if
@ -109,16 +129,41 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
if meta.live then mediaSourceId = "" if meta.live then mediaSourceId = ""
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition) m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
video.videoId = video.id
video.mediaSourceId = mediaSourceId
video.audioIndex = audio_stream_idx
if not isValid(m.playbackInfo) if not isValid(m.playbackInfo)
video.errorMsg = "Error loading playback info" video.errorMsg = "Error loading playback info"
video.content = invalid video.content = invalid
return return
end if end if
addSubtitlesToVideo(video, meta)
' Enable default subtitle track
if subtitle_idx = SubtitleSelection.notset
defaultSubtitleIndex = defaultSubtitleTrackFromVid(video.id)
if defaultSubtitleIndex <> SubtitleSelection.none
video.SelectedSubtitle = defaultSubtitleIndex
subtitle_idx = defaultSubtitleIndex
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
if not isValid(m.playbackInfo)
video.errorMsg = "Error loading playback info"
video.content = invalid
return
end if
addSubtitlesToVideo(video, meta)
else
video.SelectedSubtitle = subtitle_idx
end if
else
video.SelectedSubtitle = subtitle_idx
end if
video.videoId = video.id
video.mediaSourceId = mediaSourceId
video.audioIndex = audio_stream_idx
video.PlaySessionId = m.playbackInfo.PlaySessionId video.PlaySessionId = m.playbackInfo.PlaySessionId
if meta.live if meta.live
@ -132,8 +177,6 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
m.playbackInfo = meta.json m.playbackInfo = meta.json
end if end if
addSubtitlesToVideo(video, meta)
if meta.live if meta.live
video.transcodeParams = { video.transcodeParams = {
"MediaSourceId": m.playbackInfo.MediaSources[0].Id, "MediaSourceId": m.playbackInfo.MediaSources[0].Id,
@ -185,13 +228,106 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
setCertificateAuthority(video.content) setCertificateAuthority(video.content)
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
video.SelectedSubtitle = subtitle_idx
if not fully_external if not fully_external
video.content = authRequest(video.content) video.content = authRequest(video.content)
end if end if
end sub end sub
' defaultSubtitleTrackFromVid: Identifies the default subtitle track given video id
'
' @param {dynamic} videoID - id of video user is playing
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
function defaultSubtitleTrackFromVid(videoID) as integer
if m.global.session.user.configuration.SubtitleMode = "None"
return SubtitleSelection.none ' No subtitles desired: return none
end if
meta = ItemMetaData(videoID)
if not isValid(meta) then return SubtitleSelection.none
if not isValid(meta.json) then return SubtitleSelection.none
if not isValidAndNotEmpty(meta.json.mediaSources) then return SubtitleSelection.none
if not isValidAndNotEmpty(meta.json.MediaSources[0].MediaStreams) then return SubtitleSelection.none
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
selectedAudioLanguage = meta.json.MediaSources[0].MediaStreams[m.top.selectedAudioStreamIndex].Language ?? ""
defaultTextSubs = defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage, true) ' Find correct subtitle track (forced text)
if defaultTextSubs <> SubtitleSelection.none
return defaultTextSubs
end if
if not m.global.session.user.settings["playback.subs.onlytext"]
return defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage) ' if no appropriate text subs exist, allow non-text
end if
return SubtitleSelection.none
end function
' defaultSubtitleTrack:
'
' @param {dynamic} sortedSubtitles - array of subtitles sorted by type and language
' @param {string} selectedAudioLanguage - language for selected audio track
' @param {boolean} [requireText=false] - indicates if only text subtitles should be considered
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
function defaultSubtitleTrack(sortedSubtitles, selectedAudioLanguage as string, requireText = false as boolean) as integer
userConfig = m.global.session.user.configuration
subtitleMode = isValid(userConfig.SubtitleMode) ? LCase(userConfig.SubtitleMode) : ""
allowSmartMode = false
' Only evaluate selected audio language if we have a value
if selectedAudioLanguage <> ""
allowSmartMode = selectedAudioLanguage <> userConfig.SubtitleLanguagePreference
end if
for each item in sortedSubtitles
' Only auto-select subtitle if language matches SubtitleLanguagePreference
languageMatch = true
if userConfig.SubtitleLanguagePreference <> ""
languageMatch = (userConfig.SubtitleLanguagePreference = item.Track.Language)
end if
' Ensure textuality of subtitle matches preferenced passed as arg
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
if languageMatch and matchTextReq
if subtitleMode = "default" and (item.isForced or item.IsDefault)
' Return first forced or default subtitle track
return item.Index
else if subtitleMode = "always"
' Return the first found subtitle track
return item.Index
else if subtitleMode = "onlyforced" and item.IsForced
' Return first forced subtitle track
return item.Index
else if subtitleMode = "smart" and allowSmartMode
' Return the first found subtitle track
return item.Index
end if
end if
end for
' User has chosed smart subtitle mode
' We already attempted to load subtitles in preferred language, but none were found.
' Fall back to default behaviour while ignoring preferredlanguage
if subtitleMode = "smart" and allowSmartMode
for each item in sortedSubtitles
' Ensure textuality of subtitle matches preferenced passed as arg
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
if matchTextReq
if item.isForced or item.IsDefault
' Return first forced or default subtitle track
return item.Index
end if
end if
end for
end if
return SubtitleSelection.none ' Keep current default behavior of "None", if no correct subtitle is identified
end function
sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external) sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol) protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
if protocol <> "file" if protocol <> "file"

View File

@ -142,14 +142,12 @@ sub loadInitialItems()
m.loadItemsTask.itemId = m.top.parentItem.parentFolder m.loadItemsTask.itemId = m.top.parentItem.parentFolder
else if LCase(m.view) = "artistspresentation" or LCase(m.options.view) = "artistspresentation" else if LCase(m.view) = "artistspresentation" or LCase(m.options.view) = "artistspresentation"
m.loadItemsTask.genreIds = "" m.loadItemsTask.genreIds = ""
m.top.showItemTitles = "hidealways"
else if LCase(m.view) = "artistsgrid" or LCase(m.options.view) = "artistsgrid" else if LCase(m.view) = "artistsgrid" or LCase(m.options.view) = "artistsgrid"
m.loadItemsTask.genreIds = "" m.loadItemsTask.genreIds = ""
else if LCase(m.view) = "albumartistsgrid" or LCase(m.options.view) = "albumartistsgrid" else if LCase(m.view) = "albumartistsgrid" or LCase(m.options.view) = "albumartistsgrid"
m.loadItemsTask.genreIds = "" m.loadItemsTask.genreIds = ""
else if LCase(m.view) = "albumartistspresentation" or LCase(m.options.view) = "albumartistspresentation" else if LCase(m.view) = "albumartistspresentation" or LCase(m.options.view) = "albumartistspresentation"
m.loadItemsTask.genreIds = "" m.loadItemsTask.genreIds = ""
m.top.showItemTitles = "hidealways"
else else
m.loadItemsTask.itemId = m.top.parentItem.Id m.loadItemsTask.itemId = m.top.parentItem.Id
end if end if

View File

@ -113,19 +113,20 @@ sub processSubtitleSelection()
end if end if
if m.selectedSubtitle.IsEncoded if m.selectedSubtitle.IsEncoded
' Roku can not natively display these subtitles, so turn off the caption mode on the device
m.view.globalCaptionMode = "Off" m.view.globalCaptionMode = "Off"
else else
' Roku can natively display these subtitles, ensure the caption mode on the device is on
m.view.globalCaptionMode = "On" m.view.globalCaptionMode = "On"
end if
if m.selectedSubtitle.IsExternal ' Roku may rearrange subtitle tracks. Look up track based on name to ensure we get the correct index
availableSubtitleTrackIndex = availSubtitleTrackIdx(m.selectedSubtitle.Track.TrackName) availableSubtitleTrackIndex = availSubtitleTrackIdx(m.selectedSubtitle.Track.TrackName)
if availableSubtitleTrackIndex = -1 then return if availableSubtitleTrackIndex = -1 then return
m.view.subtitleTrack = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName m.view.subtitleTrack = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
else
m.view.selectedSubtitle = m.selectedSubtitle.Index
end if end if
m.view.selectedSubtitle = m.selectedSubtitle.Index
end sub end sub
' User requested playback info ' User requested playback info

View File

@ -95,6 +95,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
focusedItem = getFocusedItem() focusedItem = getFocusedItem()
if isValid(focusedItem) if isValid(focusedItem)
m.top.selectedItem = focusedItem m.top.selectedItem = focusedItem
'Prevent the selected item event from double firing
m.top.selectedItem = invalid
end if end if
return true return true
end if end if

View File

@ -258,6 +258,9 @@ end sub
' Event handler for when selectedSubtitle changes ' Event handler for when selectedSubtitle changes
sub onSubtitleChange() sub onSubtitleChange()
' If the global caption mode is on, that means Roku can display the subtitles natively and doesn't need a video stop/start
if LCase(m.top.globalCaptionMode) = "on" then return
' Save the current video position ' Save the current video position
m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&) m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&)
@ -334,6 +337,29 @@ sub onVideoContentLoaded()
m.top.allowCaptions = true m.top.allowCaptions = true
end if end if
' Allow default subtitles
m.top.unobserveField("selectedSubtitle")
' Set subtitleTrack property is subs are natively supported by Roku
selectedSubtitle = invalid
for each subtitle in m.top.fullSubtitleData
if subtitle.Index = videoContent[0].selectedSubtitle
selectedSubtitle = subtitle
exit for
end if
end for
if isValid(selectedSubtitle)
availableSubtitleTrackIndex = availSubtitleTrackIdx(selectedSubtitle.Track.TrackName)
if availableSubtitleTrackIndex <> -1
m.top.subtitleTrack = m.top.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
end if
end if
m.top.selectedSubtitle = videoContent[0].selectedSubtitle
m.top.observeField("selectedSubtitle", "onSubtitleChange")
if isValid(m.top.audioIndex) if isValid(m.top.audioIndex)
m.top.audioTrack = (m.top.audioIndex + 1).toStr() m.top.audioTrack = (m.top.audioIndex + 1).toStr()
else else
@ -581,6 +607,25 @@ function stateAllowsOSD() as boolean
return inArray(validStates, m.top.state) return inArray(validStates, m.top.state)
end function end function
' availSubtitleTrackIdx: Returns Roku's index for requested subtitle track
'
' @param {string} tracknameToFind - TrackName for subtitle we're looking to match
' @return {integer} indicating Roku's index for requested subtitle track. Returns -1 if not found
function availSubtitleTrackIdx(tracknameToFind as string) as integer
idx = 0
for each availTrack in m.top.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
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean
' Keypress handler while user is inside the chapter menu ' Keypress handler while user is inside the chapter menu

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -813,7 +813,7 @@ function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string) as
group.objects = TVEpisodes(seriesID, seasonID) group.objects = TVEpisodes(seriesID, seasonID)
group.episodeObjects = group.objects group.episodeObjects = group.objects
' watch for button presses ' watch for button presses
group.observeField("episodeSelected", m.port) group.observeField("selectedItem", m.port)
group.observeField("quickPlayNode", m.port) group.observeField("quickPlayNode", m.port)
' don't wait for the extras button ' don't wait for the extras button
stopLoadingSpinner() stopLoadingSpinner()

View File

@ -73,11 +73,13 @@ sub runRegistryUserMigrations()
' app versions < 2.0.0 didn't save LastRunVersion at the user level ' app versions < 2.0.0 didn't save LastRunVersion at the user level
' fall back to using the apps lastRunVersion ' fall back to using the apps lastRunVersion
lastRunVersion = m.global.app.lastRunVersion lastRunVersion = m.global.app.lastRunVersion
registry_write("LastRunVersion", lastRunVersion, section) if isValid(lastRunVersion)
registry_write("LastRunVersion", lastRunVersion, section)
end if
end if end if
' BASE_MIGRATION ' BASE_MIGRATION
if not versionChecker(lastRunVersion, CLIENT_VERSION_REQUIRING_BASE_MIGRATION) if isValid(lastRunVersion) and not versionChecker(lastRunVersion, CLIENT_VERSION_REQUIRING_BASE_MIGRATION)
m.wasMigrated = true m.wasMigrated = true
print `Running Registry Migration for ${CLIENT_VERSION_REQUIRING_BASE_MIGRATION} for userid: ${section}` print `Running Registry Migration for ${CLIENT_VERSION_REQUIRING_BASE_MIGRATION} for userid: ${section}`