2023-11-11 13:41:20 +00:00
<!DOCTYPE html> < html lang = "en" style = "font-size:16px" > < head > < meta charset = "utf-8" > < meta name = "viewport" content = "width=device-width,initial-scale=1" > < title > Source: components/ItemGrid/LoadVideoContentTask.bs< / title > <!-- [if lt IE 9]>
< script src = "//html5shiv.googlecode.com/svn/trunk/html5.js" > < / script >
2024-02-06 17:23:42 +00:00
<![endif]--> < script src = "scripts/third-party/hljs.js" defer = "defer" > < / script > < script src = "scripts/third-party/hljs-line-num.js" defer = "defer" > < / script > < script src = "scripts/third-party/popper.js" defer = "defer" > < / script > < script src = "scripts/third-party/tippy.js" defer = "defer" > < / script > < script src = "scripts/third-party/tocbot.min.js" > < / script > < script > var baseURL = "/" , locationPathname = "" ; baseURL = ( baseURL = ( baseURL = "https://jellyfin.github.io/jellyfin-roku/" ) . replace ( /https?:\/\//i , "" ) ) . substr ( baseURL . indexOf ( "/" ) ) < / script > < link rel = "stylesheet" href = "styles/clean-jsdoc-theme.min.css" > < svg aria-hidden = "true" version = "1.1" xmlns = "http://www.w3.org/2000/svg" xmlns:xlink = "http://www.w3.org/1999/xlink" style = "display:none" > < defs > < symbol id = "copy-icon" viewbox = "0 0 488.3 488.3" > < g > < path d = "M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z" / > < path d = "M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z" / > < / g > < / symbol > < symbol id = "search-icon" viewBox = "0 0 512 512" > < g > < g > < path d = "M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z" / > < / g > < / g > < g > < g > < path d = "M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z" / > < / g > < / g > < / symbol > < symbol id = "font-size-icon" viewBox = "0 0 24 24" > < path fill = "none" d = "M0 0h24v24H0z" / > < path d = "M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" / > < / symbol > < symbol id = "add-icon" viewBox = "0 0 24 24" > < path fill = "none" d = "M0 0h24v24H0z" / > < path d = "M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z" / > < / symbol > < symbol id = "minus-icon" viewBox = "0 0 24 24" > < path fill = "none" d = "M0 0h24v24H0z" / > < path d = "M5 11h14v2H5z" / > < / symbol > < symbol id = "dark-theme-icon" viewBox = "0 0 24 24" > < path fill = "none" d = "M0 0h24v24H0z" / > < path d = "M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z" / > < / symbol > < symbol id = "light-theme-icon" viewBox = "0 0 24 24" > < path fill = "none" d = "M0 0h24v24H0z" / > < path d = "M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z" / > < / symbol > < symbol id = "reset-icon" viewBox = "0 0 24 24" > < path fill = "none" d = "M0 0h24v24H0z" / > < path d = "M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z" / > < / symbol > < symbol id = "down-icon" viewBox = "0 0 16 16" > < path fill-rule = "evenodd" clip-rule = "evenodd" d = "M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z" > < / path > < / symbol > < symbol id = "codepen-icon" viewBox = "0 0 24 24" > < path fill = "none" d =
2023-11-11 02:08:52 +00:00
import "pkg:/source/api/Items.bs"
import "pkg:/source/api/UserLibrary.bs"
import "pkg:/source/api/baserequest.bs"
import "pkg:/source/utils/config.bs"
import "pkg:/source/api/Image.bs"
import "pkg:/source/api/userauth.bs"
import "pkg:/source/utils/deviceCapabilities.bs"
2023-10-06 03:18:36 +00:00
2024-01-06 01:31:17 +00:00
enum SubtitleSelection
notset = -2
none = -1
end enum
2023-10-06 03:18:36 +00:00
sub init()
m.user = AboutMe()
m.top.functionName = "loadItems"
end sub
sub loadItems()
' Reset intro tracker in case task gets reused
m.top.isIntro = false
' Only show preroll once per queue
if m.global.queueManager.callFunc("isPrerollActive")
' Prerolls not allowed if we're resuming video
if m.global.queueManager.callFunc("getCurrentItem").startingPoint = 0
preRoll = GetIntroVideos(m.top.itemId)
if isValid(preRoll) and preRoll.TotalRecordCount > 0 and isValid(preRoll.items[0])
' If an error is thrown in the Intros plugin, instead of passing the error they pass the entire rick roll music video.
' Bypass the music video and treat it as an error message
if lcase(preRoll.items[0].name) < > "rick roll'd"
m.global.queueManager.callFunc("push", m.global.queueManager.callFunc("getCurrentItem"))
m.top.itemId = preRoll.items[0].id
m.global.queueManager.callFunc("setPrerollStatus", false)
m.top.isIntro = true
end if
end if
end if
end if
if m.top.selectedAudioStreamIndex = 0
currentItem = m.global.queueManager.callFunc("getCurrentItem")
if isValid(currentItem) and isValid(currentItem.json)
m.top.selectedAudioStreamIndex = FindPreferredAudioStream(currentItem.json.MediaStreams)
end if
end if
id = m.top.itemId
mediaSourceId = invalid
audio_stream_idx = m.top.selectedAudioStreamIndex
forceTranscoding = false
2024-01-06 01:31:17 +00:00
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, forceTranscoding)]
2023-10-06 03:18:36 +00:00
end sub
2024-01-06 01:31:17 +00:00
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean) as dynamic
2023-10-06 03:18:36 +00:00
video = {}
video.id = id
video.content = createObject("RoSGNode", "ContentNode")
2024-01-06 01:31:17 +00:00
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, forceTranscoding)
2023-10-06 03:18:36 +00:00
if video.content = invalid
return invalid
end if
return video
end function
2024-01-06 01:31:17 +00:00
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean)
2023-10-06 03:18:36 +00:00
meta = ItemMetaData(video.id)
2024-01-06 01:31:17 +00:00
subtitle_idx = m.top.selectedSubtitleIndex
2023-10-06 03:18:36 +00:00
if not isValid(meta)
video.errorMsg = "Error loading metadata"
video.content = invalid
return
end if
videotype = LCase(meta.type)
2024-01-06 01:31:17 +00:00
' 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
2023-10-06 03:18:36 +00:00
if videotype = "episode" or videotype = "series"
video.content.contenttype = "episode"
end if
2023-11-10 23:51:00 +00:00
video.chapters = meta.json.Chapters
2023-10-06 03:18:36 +00:00
video.content.title = meta.title
video.showID = meta.showID
user = AboutMe()
if user.Configuration.EnableNextEpisodeAutoPlay
if LCase(m.top.itemType) = "episode"
addNextEpisodesToQueue(video.showID)
end if
end if
playbackPosition = 0!
currentItem = m.global.queueManager.callFunc("getCurrentItem")
if isValid(currentItem) and isValid(currentItem.startingPoint)
playbackPosition = currentItem.startingPoint
end if
' PlayStart requires the time to be in seconds
video.content.PlayStart = int(playbackPosition / 10000000)
if not isValid(mediaSourceId) then mediaSourceId = video.id
if meta.live then mediaSourceId = ""
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
2024-01-06 01:31:17 +00:00
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
2023-10-06 03:18:36 +00:00
video.PlaySessionId = m.playbackInfo.PlaySessionId
if meta.live
video.content.live = true
video.content.StreamFormat = "hls"
end if
video.container = getContainerType(meta)
if not isValid(m.playbackInfo.MediaSources[0])
m.playbackInfo = meta.json
end if
2024-01-27 17:10:18 +00:00
addAudioStreamsToVideo(video)
2023-10-06 03:18:36 +00:00
if meta.live
video.transcodeParams = {
"MediaSourceId": m.playbackInfo.MediaSources[0].Id,
"LiveStreamId": m.playbackInfo.MediaSources[0].LiveStreamId,
"PlaySessionId": video.PlaySessionId
}
end if
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay
fully_external = false
' For h264/hevc video, Roku spec states that it supports specfic encoding levels
' The device can decode content with a Higher Encoding level but may play it back with certain
' artifacts. If the user preference is set, and the only reason the server says we need to
' transcode is that the Encoding Level is not supported, then try to direct play but silently
' fall back to the transcode if that fails.
if m.playbackInfo.MediaSources[0].MediaStreams.Count() > 0 and meta.live = false
tryDirectPlay = m.global.session.user.settings["playback.tryDirect.h264ProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
tryDirectPlay = tryDirectPlay or (m.global.session.user.settings["playback.tryDirect.hevcProfileLevel"] and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "hevc")
if tryDirectPlay and isValid(m.playbackInfo.MediaSources[0].TranscodingUrl) and forceTranscoding = false
transcodingReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
if transcodingReasons.Count() = 1 and transcodingReasons[0] = "VideoLevelNotSupported"
video.directPlaySupported = true
video.transcodeAvailable = true
end if
end if
end if
if video.directPlaySupported
video.isTranscoded = false
2023-11-04 19:11:14 +00:00
addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
2023-10-06 03:18:36 +00:00
else
if m.playbackInfo.MediaSources[0].TranscodingUrl = invalid
' If server does not provide a transcode URL, display a message to the user
m.global.sceneManager.callFunc("userMessage", tr("Error Getting Playback Information"), tr("An error was encountered while playing this item. Server did not provide required transcoding data."))
video.errorMsg = "Error getting playback information"
video.content = invalid
return
end if
' Get transcoding reason
video.transcodeReasons = getTranscodeReasons(m.playbackInfo.MediaSources[0].TranscodingUrl)
video.content.url = buildURL(m.playbackInfo.MediaSources[0].TranscodingUrl)
video.isTranscoded = true
end if
setCertificateAuthority(video.content)
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
if not fully_external
video.content = authRequest(video.content)
end if
end sub
2024-01-06 01:31:17 +00:00
' 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)
2024-02-06 14:11:09 +00:00
selectedAudioLanguage = ""
audioMediaStream = meta.json.MediaSources[0].MediaStreams[m.top.selectedAudioStreamIndex]
' Ensure audio media stream is valid before using language property
if isValid(audioMediaStream)
selectedAudioLanguage = audioMediaStream.Language ?? ""
end if
2024-01-06 01:31:17 +00:00
2024-01-14 02:05:18 +00:00
defaultTextSubs = defaultSubtitleTrack(subtitles["text"], selectedAudioLanguage, true) ' Find correct subtitle track (forced text)
2024-01-06 01:31:17 +00:00
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
2023-10-06 03:18:36 +00:00
sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
if protocol < > "file"
uri = parseUrl(m.playbackInfo.MediaSources[0].Path)
if isLocalhost(uri[2])
' if the domain of the URI is local to the server,
' create a new URI by appending the received path to the server URL
' later we will substitute the users provided URL for this case
video.content.url = buildURL(uri[4])
else
fully_external = true
video.content.url = m.playbackInfo.MediaSources[0].Path
end if
2023-11-04 19:11:14 +00:00
else
params = {
2023-10-06 03:18:36 +00:00
"Static": "true",
"Container": video.container,
"PlaySessionId": video.PlaySessionId,
"AudioStreamIndex": audio_stream_idx
2023-11-04 19:11:14 +00:00
}
2023-10-06 03:18:36 +00:00
if mediaSourceId < > ""
params.MediaSourceId = mediaSourceId
end if
video.content.url = buildURL(Substitute("Videos/{0}/stream", video.id), params)
end if
end sub
2024-01-27 17:10:18 +00:00
' addAudioStreamsToVideo: Add audio stream data to video
'
' @param {dynamic} video component to add fullAudioData to
sub addAudioStreamsToVideo(video)
audioStreams = []
mediaStreams = m.playbackInfo.MediaSources[0].MediaStreams
for i = 0 to mediaStreams.Count() - 1
if LCase(mediaStreams[i].Type) = "audio"
audioStreams.push(mediaStreams[i])
end if
end for
video.fullAudioData = audioStreams
end sub
2023-10-06 03:18:36 +00:00
sub addSubtitlesToVideo(video, meta)
subtitles = sortSubtitles(meta.id, m.playbackInfo.MediaSources[0].MediaStreams)
safesubs = subtitles["all"]
subtitleTracks = []
if m.global.session.user.settings["playback.subs.onlytext"] = true
safesubs = subtitles["text"]
end if
for each subtitle in safesubs
subtitleTracks.push(subtitle.track)
end for
video.content.SubtitleTracks = subtitleTracks
video.fullSubtitleData = safesubs
end sub
'
' Extract array of Transcode Reasons from the content URL
' @returns Array of Strings
function getTranscodeReasons(url as string) as object
regex = CreateObject("roRegex", "& TranscodeReasons=([^& ]*)", "")
match = regex.Match(url)
if match.count() > 1
return match[1].Split(",")
end if
return []
end function
function directPlaySupported(meta as object) as boolean
devinfo = CreateObject("roDeviceInfo")
if isValid(meta.json.MediaSources[0]) and meta.json.MediaSources[0].SupportsDirectPlay = false
return false
end if
if meta.json.MediaStreams[0] = invalid
return false
end if
streamInfo = { Codec: meta.json.MediaStreams[0].codec }
if isValid(meta.json.MediaStreams[0].Profile) and meta.json.MediaStreams[0].Profile.len() > 0
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
end if
if isValid(meta.json.MediaSources[0].container) and meta.json.MediaSources[0].container.len() > 0
'CanDecodeVideo() requires the .container to be format: “mp4”, “hls”, “mkv”, “ism”, “dash”, “ts” if its to direct stream
if meta.json.MediaSources[0].container = "mov"
streamInfo.Container = "mp4"
else
streamInfo.Container = meta.json.MediaSources[0].container
end if
end if
decodeResult = devinfo.CanDecodeVideo(streamInfo)
return decodeResult < > invalid and decodeResult.result
end function
function getContainerType(meta as object) as string
' Determine the file type of the video file source
if meta.json.mediaSources = invalid then return ""
container = meta.json.mediaSources[0].container
if container = invalid
container = ""
else if container = "m4v" or container = "mov"
container = "mp4"
end if
return container
end function
' Add next episodes to the playback queue
sub addNextEpisodesToQueue(showID)
' Don't queue next episodes if we already have a playback queue
maxQueueCount = 1
if m.top.isIntro
maxQueueCount = 2
end if
if m.global.queueManager.callFunc("getCount") > maxQueueCount then return
videoID = m.top.itemId
' If first item is an intro video, use the next item in the queue
if m.top.isIntro
currentVideo = m.global.queueManager.callFunc("getItemByIndex", 1)
if isValid(currentVideo) and isValid(currentVideo.id)
videoID = currentVideo.id
' Override showID value since it's for the intro video
meta = ItemMetaData(videoID)
if isValid(meta)
showID = meta.showID
end if
end if
end if
url = Substitute("Shows/{0}/Episodes", showID)
urlParams = { "UserId": m.global.session.user.id }
urlParams.Append({ "StartItemId": videoID })
urlParams.Append({ "Limit": 50 })
resp = APIRequest(url, urlParams)
data = getJson(resp)
if isValid(data) and data.Items.Count() > 1
for i = 1 to data.Items.Count() - 1
m.global.queueManager.callFunc("push", data.Items[i])
end for
end if
end sub
'Checks available subtitle tracks and puts subtitles in forced, default, and non-default/forced but preferred language at the top
function sortSubtitles(id as string, MediaStreams)
tracks = { "forced": [], "default": [], "normal": [], "text": [] }
'Too many args for using substitute
prefered_lang = m.global.session.user.configuration.SubtitleLanguagePreference
for each stream in MediaStreams
if stream.type = "Subtitle"
url = ""
if isValid(stream.DeliveryUrl)
url = buildURL(stream.DeliveryUrl)
end if
stream = {
"Track": { "Language": stream.language, "Description": stream.displaytitle, "TrackName": url },
"IsTextSubtitleStream": stream.IsTextSubtitleStream,
"Index": stream.index,
"IsDefault": stream.IsDefault,
"IsForced": stream.IsForced,
"IsExternal": stream.IsExternal,
"IsEncoded": stream.DeliveryMethod = "Encode"
}
2024-01-14 02:05:18 +00:00
2023-10-06 03:18:36 +00:00
if stream.isForced
trackType = "forced"
else if stream.IsDefault
trackType = "default"
else
trackType = "normal"
end if
2024-01-14 02:05:18 +00:00
2023-10-06 03:18:36 +00:00
if prefered_lang < > "" and prefered_lang = stream.Track.Language
tracks[trackType].unshift(stream)
2024-01-14 02:05:18 +00:00
if stream.IsTextSubtitleStream
tracks["text"].unshift(stream)
end if
2023-10-06 03:18:36 +00:00
else
tracks[trackType].push(stream)
2024-01-14 02:05:18 +00:00
if stream.IsTextSubtitleStream
tracks["text"].push(stream)
end if
2023-10-06 03:18:36 +00:00
end if
end if
end for
tracks["default"].append(tracks["normal"])
tracks["forced"].append(tracks["default"])
return { "all": tracks["forced"], "text": tracks["text"] }
end function
function FindPreferredAudioStream(streams as dynamic) as integer
preferredLanguage = m.user.Configuration.AudioLanguagePreference
playDefault = m.user.Configuration.PlayDefaultAudioTrack
if playDefault < > invalid and playDefault = true
return 1
end if
' Do we already have the MediaStreams or not?
if streams = invalid
url = Substitute("Users/{0}/Items/{1}", m.user.id, m.top.itemId)
resp = APIRequest(url)
jsonResponse = getJson(resp)
if jsonResponse = invalid or jsonResponse.MediaStreams = invalid then return 1
streams = jsonResponse.MediaStreams
end if
if preferredLanguage < > invalid
for i = 0 to streams.Count() - 1
if LCase(streams[i].Type) = "audio"
if streams[i].Language < > invalid and LCase(streams[i].Language) = LCase(preferredLanguage)
return i
end if
end if
end for
end if
return 1
end function
2024-02-06 17:23:42 +00:00
< / code > < / pre > < / article > < / section > < footer class = "footer" id = "PeOAagUepe" > < div class = "wrapper" > < span class = "jsdoc-message" > Automatically generated using < a href = "https://github.com/jsdoc/jsdoc" target = "_blank" > JSDoc< / a > and the < a href = "https://github.com/ankitskvmdam/clean-jsdoc-theme" target = "_blank" > clean-jsdoc-theme< / a > .< / span > < / div > < / footer > < / div > < / div > < / div > < div class = "search-container" id = "PkfLWpAbet" style = "display:none" > < div class = "wrapper" id = "iCxFxjkHbP" > < button class = "icon-button search-close-button" id = "VjLlGakifb" aria-label = "close search" > < svg > < use xlink:href = "#close-icon" > < / use > < / svg > < / button > < div class = "search-box-c" > < svg > < use xlink:href = "#search-icon" > < / use > < / svg > < input type = "text" id = "vpcKVYIppa" class = "search-input" placeholder = "Search..." autofocus > < / div > < div class = "search-result-c" id = "fWwVHRuDuN" > < span class = "search-result-c-text" > Type anything to view search result< / span > < / div > < / div > < / div > < div class = "mobile-menu-icon-container" > < button class = "icon-button" id = "mobile-menu" data-isopen = "false" aria-label = "menu" > < svg > < use xlink:href = "#menu-icon" > < / use > < / svg > < / button > < / div > < div id = "mobile-sidebar" class = "mobile-sidebar-container" > < div class = "mobile-sidebar-wrapper" > < a href = "/" class = "sidebar-title sidebar-title-anchor" > jellyfin-roku Code Documentation< / a > < div class = "mobile-nav-links" > < div class = "external-link navbar-item" > < a id = "jellyfin-link-mobile" href = "https://jellyfin.org/" target = "_blank" > Jellyfin< / a > < / div > < div class = "external-link navbar-item" > < a id = "github-link-mobile" href = "https://github.com/jellyfin/jellyfin-roku" target = "_blank" > GitHub< / a > < / div > < div class = "external-link navbar-item" > < a id = "forum-link-mobile" href = "https://forum.jellyfin.org/f-roku-development" target = "_blank" > Forum< / a > < / div > < div class = "external-link navbar-item" > < a id = "matrix-link-mobile" href = "https://matrix.to/#/#jellyfin-dev-roku:matrix.org" target = "_blank" > Matrix< / a > < / div > < / div > < div class = "mobile-sidebar-items-c" > < div class = "sidebar-section-title with-arrow" data-isopen = "false" id = "sidebar-modules" > < div > Modules< / div > < svg > < use xlink:href = "#down-icon" > < / use > < / svg > < / div > < div class = "sidebar-section-children-container" > < div class = "sidebar-section-children" > < a href = "module-AlbumData.html" > AlbumData< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-AlbumGrid.html" > AlbumGrid< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-AlbumTrackList.html" > AlbumTrackList< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-AlbumView.html" > AlbumView< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-Alpha.html" > Alpha< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ArtistView.html" > ArtistView< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-AudioPlayer.html" > AudioPlayer< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-AudioPlayerView.html" > AudioPlayerView< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-AudioTrackListItem.html" > AudioTrackListItem< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ButtonGroupHoriz.html" > ButtonGroupHoriz< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ButtonGroupVert.html" > ButtonGroupVert< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ChannelData.html" > ChannelData< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-Clock.html" > Clock< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-CollectionData.html" > CollectionData< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ConfigData.html" > ConfigData< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ConfigItem.html" > ConfigItem< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ConfigList.html" > ConfigList< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ExtrasItem.html" > ExtrasItem< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-ExtrasRowList.html" > ExtrasRowList< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-FavoriteItemsTask.html" > FavoriteItemsTask< / a > < / div > < div class = "sidebar-section-children" > < a href = "module-Folder