1265 lines
43 KiB
Plaintext
1265 lines
43 KiB
Plaintext
sub init()
|
|
m.top.functionName = "loadItems"
|
|
|
|
m.top.limit = 60
|
|
usersettingLimit = get_user_setting("itemgrid.Limit")
|
|
|
|
if isValid(usersettingLimit)
|
|
m.top.limit = usersettingLimit
|
|
end if
|
|
end sub
|
|
|
|
sub loadItems()
|
|
m.top.content = [LoadItems_VideoPlayer(m.top.itemId)]
|
|
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, showIntro = true as boolean, allowResumeDialog = true as boolean) as dynamic
|
|
|
|
video = {}
|
|
video.id = id
|
|
video.content = createObject("RoSGNode", "ContentNode")
|
|
|
|
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, -1, forceTranscoding, showIntro, allowResumeDialog)
|
|
|
|
if video.errorMsg = "introaborted"
|
|
return video
|
|
end if
|
|
|
|
if video.content = invalid
|
|
return invalid
|
|
end if
|
|
|
|
return video
|
|
end function
|
|
|
|
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, playbackPosition = -1 as integer, forceTranscoding = false as boolean, showIntro = true as boolean, allowResumeDialog = true as boolean)
|
|
|
|
meta = ItemMetaData(video.id)
|
|
|
|
if not isValid(meta)
|
|
video.content = invalid
|
|
return
|
|
end if
|
|
|
|
videotype = LCase(meta.type)
|
|
|
|
if videotype = "episode" or videotype = "series"
|
|
video.content.contenttype = "episode"
|
|
end if
|
|
|
|
video.content.title = meta.title
|
|
video.showID = meta.showID
|
|
|
|
if playbackPosition = -1
|
|
playbackPosition = meta.json.UserData.PlaybackPositionTicks
|
|
if allowResumeDialog
|
|
if playbackPosition > 0
|
|
dialogResult = startPlayBackOver(playbackPosition)
|
|
|
|
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
|
|
if dialogResult.indexselected = -1
|
|
'User pressed back, return invalid and don't load video
|
|
video.content = invalid
|
|
return
|
|
else if dialogResult.indexselected = 1
|
|
'Start Over selected, change position to 0
|
|
playbackPosition = 0
|
|
end if
|
|
end if
|
|
end if
|
|
end if
|
|
|
|
' For phase 1 of playlist support, we don't support intros yet
|
|
showIntro = false
|
|
|
|
' Don't attempt to play an intro for an intro video
|
|
if showIntro
|
|
' Do not play intros when resuming playback
|
|
if playbackPosition = 0
|
|
if not PlayIntroVideo(video.id, audio_stream_idx)
|
|
video.errorMsg = "introaborted"
|
|
return
|
|
end if
|
|
end if
|
|
end if
|
|
|
|
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)
|
|
video.videoId = video.id
|
|
video.mediaSourceId = mediaSourceId
|
|
video.audioIndex = audio_stream_idx
|
|
|
|
if not isValid(m.playbackInfo)
|
|
video.content = invalid
|
|
return
|
|
end if
|
|
|
|
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
|
|
|
|
addSubtitlesToVideo(video, meta)
|
|
|
|
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 = get_user_setting("playback.tryDirect.h264ProfileLevel") = "true" and m.playbackInfo.MediaSources[0].MediaStreams[0].codec = "h264"
|
|
tryDirectPlay = tryDirectPlay or (get_user_setting("playback.tryDirect.hevcProfileLevel") = "true" 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
|
|
addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
|
video.isTranscoded = false
|
|
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.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
|
|
|
|
video.content.setCertificatesFile("common:/certs/ca-bundle.crt")
|
|
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
|
|
|
|
' Perform relevant setup work for selected subtitle, and return the index of the subtitle
|
|
' is enabled/will be enabled, indexed on the provided list of subtitles
|
|
video.SelectedSubtitle = setupSubtitle(video, video.Subtitles, subtitle_idx)
|
|
|
|
if not fully_external
|
|
video.content = authorize_request(video.content)
|
|
end if
|
|
|
|
end sub
|
|
|
|
sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
|
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
|
if protocol <> "file"
|
|
uriRegex = CreateObject("roRegex", "^(.*:)//([A-Za-z0-9\-\.]+)(:[0-9]+)?(.*)$", "")
|
|
uri = uriRegex.Match(m.playbackInfo.MediaSources[0].Path)
|
|
' proto $1, host $2, port $3, the-rest $4
|
|
localhost = CreateObject("roRegex", "^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$", "i")
|
|
' https://stackoverflow.com/questions/8426171/what-regex-will-match-all-loopback-addresses
|
|
if localhost.isMatch(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
|
|
else:
|
|
params = {}
|
|
|
|
params.append({
|
|
"Static": "true",
|
|
"Container": video.container,
|
|
"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)
|
|
end if
|
|
end sub
|
|
|
|
sub addSubtitlesToVideo(video, meta)
|
|
subtitles = sortSubtitles(meta.id, m.playbackInfo.MediaSources[0].MediaStreams)
|
|
if get_user_setting("playback.subs.onlytext") = "true"
|
|
safesubs = []
|
|
for each subtitle in subtitles["all"]
|
|
if subtitle["IsTextSubtitleStream"]
|
|
safesubs.push(subtitle)
|
|
end if
|
|
end for
|
|
video.Subtitles = safesubs
|
|
else
|
|
video.Subtitles = subtitles["all"]
|
|
end if
|
|
video.content.SubtitleTracks = subtitles["text"]
|
|
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
|
|
|
|
'Opens dialog asking user if they want to resume video or start playback over only on the home screen
|
|
function startPlayBackOver(time as longinteger)
|
|
|
|
' If we're inside a play queue, start the episode from the beginning
|
|
if m.global.queueManager.callFunc("getCount") > 1 then return { indexselected: 1 }
|
|
|
|
resumeData = [
|
|
"Resume playing at " + ticksToHuman(time) + ".",
|
|
"Start over from the beginning."
|
|
]
|
|
|
|
m.global.sceneManager.callFunc("optionDialog", tr("Playback Options"), ["Choose an option"], resumeData)
|
|
|
|
while not isValid(m.global.sceneManager.returnData)
|
|
|
|
end while
|
|
|
|
return m.global.sceneManager.returnData
|
|
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
|
|
|
|
function getAudioFormat(meta as object) as string
|
|
' Determine the codec of the audio file source
|
|
if meta.json.mediaSources = invalid then return ""
|
|
|
|
audioInfo = getAudioInfo(meta)
|
|
if audioInfo.count() = 0 or audioInfo[0].codec = invalid then return ""
|
|
return audioInfo[0].codec
|
|
end function
|
|
|
|
function getAudioInfo(meta as object) as object
|
|
' Return audio metadata for a given stream
|
|
results = []
|
|
for each source in meta.json.mediaSources[0].mediaStreams
|
|
if source["type"] = "Audio"
|
|
results.push(source)
|
|
end if
|
|
end for
|
|
return results
|
|
end function
|
|
|
|
sub autoPlayNextEpisode(videoID as string, showID as string)
|
|
' use web client setting
|
|
if m.user.Configuration.EnableNextEpisodeAutoPlay
|
|
' query API for next episode ID
|
|
url = Substitute("Shows/{0}/Episodes", showID)
|
|
urlParams = { "UserId": get_setting("active_user") }
|
|
urlParams.Append({ "StartItemId": videoID })
|
|
urlParams.Append({ "Limit": 2 })
|
|
resp = APIRequest(url, urlParams)
|
|
data = getJson(resp)
|
|
|
|
if isValid(data) and data.Items.Count() = 2
|
|
' setup new video node
|
|
nextVideo = invalid
|
|
' remove last videoplayer scene
|
|
m.global.sceneManager.callFunc("clearPreviousScene")
|
|
if isValid(nextVideo)
|
|
m.global.sceneManager.callFunc("pushScene", nextVideo)
|
|
else
|
|
m.global.sceneManager.callFunc("popScene")
|
|
end if
|
|
else
|
|
' can't play next episode
|
|
m.global.sceneManager.callFunc("popScene")
|
|
end if
|
|
else
|
|
m.global.sceneManager.callFunc("popScene")
|
|
end if
|
|
end sub
|
|
|
|
' Returns an array of playback info to be displayed during playback.
|
|
' In the future, with a custom playback info view, we can return an associated array.
|
|
function GetPlaybackInfo()
|
|
sessions = api_API().sessions.get()
|
|
if isValid(sessions) and sessions.Count() > 0
|
|
return GetTranscodingStats(sessions[0])
|
|
end if
|
|
|
|
errMsg = tr("Unable to get playback information")
|
|
return [errMsg]
|
|
end function
|
|
|
|
function GetTranscodingStats(session)
|
|
sessionStats = []
|
|
|
|
if isValid(session.TranscodingInfo) and session.TranscodingInfo.Count() > 0
|
|
transcodingReasons = session.TranscodingInfo.TranscodeReasons
|
|
videoCodec = session.TranscodingInfo.VideoCodec
|
|
audioCodec = session.TranscodingInfo.AudioCodec
|
|
totalBitrate = session.TranscodingInfo.Bitrate
|
|
audioChannels = session.TranscodingInfo.AudioChannels
|
|
|
|
if isValid(transcodingReasons) and transcodingReasons.Count() > 0
|
|
sessionStats.push("** " + tr("Transcoding Information") + " **")
|
|
for each item in transcodingReasons
|
|
sessionStats.push(tr("Reason") + ": " + item)
|
|
end for
|
|
end if
|
|
|
|
if isValid(videoCodec)
|
|
data = tr("Video Codec") + ": " + videoCodec
|
|
if session.TranscodingInfo.IsVideoDirect
|
|
data = data + " (" + tr("direct") + ")"
|
|
end if
|
|
sessionStats.push(data)
|
|
end if
|
|
|
|
if isValid(audioCodec)
|
|
data = tr("Audio Codec") + ": " + audioCodec
|
|
if session.TranscodingInfo.IsAudioDirect
|
|
data = data + " (" + tr("direct") + ")"
|
|
end if
|
|
sessionStats.push(data)
|
|
end if
|
|
|
|
if isValid(totalBitrate)
|
|
data = tr("Total Bitrate") + ": " + getDisplayBitrate(totalBitrate)
|
|
sessionStats.push(data)
|
|
end if
|
|
|
|
if isValid(audioChannels)
|
|
data = tr("Audio Channels") + ": " + Str(audioChannels)
|
|
sessionStats.push(data)
|
|
end if
|
|
end if
|
|
|
|
if havePlaybackInfo()
|
|
stream = m.playbackInfo.mediaSources[0].MediaStreams[0]
|
|
sessionStats.push("** " + tr("Stream Information") + " **")
|
|
if isValid(stream.Container)
|
|
data = tr("Container") + ": " + stream.Container
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.Size)
|
|
data = tr("Size") + ": " + stream.Size
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.BitRate)
|
|
data = tr("Bit Rate") + ": " + getDisplayBitrate(stream.BitRate)
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.Codec)
|
|
data = tr("Codec") + ": " + stream.Codec
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.CodecTag)
|
|
data = tr("Codec Tag") + ": " + stream.CodecTag
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.VideoRangeType)
|
|
data = tr("Video range type") + ": " + stream.VideoRangeType
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.PixelFormat)
|
|
data = tr("Pixel format") + ": " + stream.PixelFormat
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.Width) and isValid(stream.Height)
|
|
data = tr("WxH") + ": " + Str(stream.Width) + " x " + Str(stream.Height)
|
|
sessionStats.push(data)
|
|
end if
|
|
if isValid(stream.Level)
|
|
data = tr("Level") + ": " + Str(stream.Level)
|
|
sessionStats.push(data)
|
|
end if
|
|
end if
|
|
|
|
return sessionStats
|
|
end function
|
|
|
|
function havePlaybackInfo()
|
|
if not isValid(m.playbackInfo)
|
|
return false
|
|
end if
|
|
|
|
if not isValid(m.playbackInfo.mediaSources)
|
|
return false
|
|
end if
|
|
|
|
if m.playbackInfo.mediaSources.Count() <= 0
|
|
return false
|
|
end if
|
|
|
|
if not isValid(m.playbackInfo.mediaSources[0].MediaStreams)
|
|
return false
|
|
end if
|
|
|
|
if m.playbackInfo.mediaSources[0].MediaStreams.Count() <= 0
|
|
return false
|
|
end if
|
|
|
|
return true
|
|
end function
|
|
|
|
function getDisplayBitrate(bitrate)
|
|
if bitrate > 1000000
|
|
return Str(Fix(bitrate / 1000000)) + " Mbps"
|
|
else
|
|
return Str(Fix(bitrate / 1000)) + " Kbps"
|
|
end if
|
|
end function
|
|
|
|
' 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(video, sub_idx) as integer
|
|
url = video.Subtitles[sub_idx].Track.TrackName
|
|
idx = 0
|
|
for each availTrack in video.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, url)
|
|
return idx
|
|
end if
|
|
idx = idx + 1
|
|
end for
|
|
return -1
|
|
end function
|
|
|
|
' Identify the default subtitle track for a given video id
|
|
' returns the server-side track index for the appriate subtitle
|
|
function defaultSubtitleTrackFromVid(video_id) as integer
|
|
meta = ItemMetaData(video_id)
|
|
if isValid(meta) and isValid(meta.json) and isValid(meta.json.mediaSources)
|
|
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
|
|
default_text_subs = defaultSubtitleTrack(subtitles["all"], true) ' Find correct subtitle track (forced text)
|
|
if default_text_subs <> -1
|
|
return default_text_subs
|
|
else
|
|
if get_user_setting("playback.subs.onlytext") = "false"
|
|
return defaultSubtitleTrack(subtitles["all"]) ' if no appropriate text subs exist, allow non-text
|
|
else
|
|
return -1
|
|
end if
|
|
end if
|
|
end if
|
|
' No valid mediaSources (i.e. LiveTV)
|
|
return -1
|
|
end function
|
|
|
|
|
|
' Identify the default subtitle track
|
|
' if "requires_text" is true, only return a track if it is textual
|
|
' This allows forcing text subs, since roku requires transcoding of non-text subs
|
|
' returns the server-side track index for the appriate subtitle
|
|
function defaultSubtitleTrack(sorted_subtitles, require_text = false) as integer
|
|
if m.user.Configuration.SubtitleMode = "None"
|
|
return -1 ' No subtitles desired: select none
|
|
end if
|
|
|
|
for each item in sorted_subtitles
|
|
' Only auto-select subtitle if language matches preference
|
|
languageMatch = (m.user.Configuration.SubtitleLanguagePreference = item.Track.Language)
|
|
' Ensure textuality of subtitle matches preferenced passed as arg
|
|
matchTextReq = ((require_text and item.IsTextSubtitleStream) or not require_text)
|
|
if languageMatch and matchTextReq
|
|
if m.user.Configuration.SubtitleMode = "Default" and (item.isForced or item.IsDefault or item.IsExternal)
|
|
return item.Index ' Finds first forced, or default, or external subs in sorted list
|
|
else if m.user.Configuration.SubtitleMode = "Always" and not item.IsForced
|
|
return item.Index ' Select the first non-forced subtitle option in the sorted list
|
|
else if m.user.Configuration.SubtitleMode = "OnlyForced" and item.IsForced
|
|
return item.Index ' Select the first forced subtitle option in the sorted list
|
|
else if m.user.Configuration.SubtitlePlaybackMode = "Smart" and (item.isForced or item.IsDefault or item.IsExternal)
|
|
' Simplified "Smart" logic here mimics Default (as that is fallback behavior normally)
|
|
' Avoids detecting preferred audio language (as is utilized in main client)
|
|
return item.Index
|
|
end if
|
|
end if
|
|
end for
|
|
return -1 ' Keep current default behavior of "None", if no correct subtitle is identified
|
|
end function
|
|
|
|
' Given a set of subtitles, and a subtitle index (the index on the server, not in the list provided)
|
|
' this will set all relevant settings for roku (mainly closed captions) and return the index of the
|
|
' subtitle track specified, but indexed based on the provided list of subtitles
|
|
function setupSubtitle(video, subtitles, subtitle_idx = -1) as integer
|
|
if subtitle_idx = -1
|
|
' If we are not using text-based subtitles, turn them off
|
|
video.globalCaptionMode = "Off"
|
|
return -1
|
|
end if
|
|
|
|
' Translate the raw index to one relative to the provided list
|
|
subtitleSelIdx = getSubtitleSelIdxFromSubIdx(subtitles, subtitle_idx)
|
|
|
|
selectedSubtitle = subtitles[subtitleSelIdx]
|
|
|
|
if selectedSubtitle.IsEncoded
|
|
' With encoded subtitles, turn off captions
|
|
video.globalCaptionMode = "Off"
|
|
else
|
|
' If this is a text-based subtitle, set relevant settings for roku captions
|
|
video.globalCaptionMode = "On"
|
|
video.subtitleTrack = video.availableSubtitleTracks[availSubtitleTrackIdx(video, subtitleSelIdx)].TrackName
|
|
end if
|
|
|
|
return subtitleSelIdx
|
|
|
|
end function
|
|
|
|
' The subtitle index on the server differs from the index we track locally
|
|
' This function converts the former into the latter
|
|
function getSubtitleSelIdxFromSubIdx(subtitles, sub_idx) as integer
|
|
selIdx = 0
|
|
if sub_idx = -1 then return -1
|
|
for each item in subtitles
|
|
if item.Index = sub_idx
|
|
return selIdx
|
|
end if
|
|
selIdx = selIdx + 1
|
|
end for
|
|
return -1
|
|
end function
|
|
|
|
'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)
|
|
m.user = AboutMe()
|
|
tracks = { "forced": [], "default": [], "normal": [] }
|
|
'Too many args for using substitute
|
|
prefered_lang = m.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"
|
|
}
|
|
if stream.isForced
|
|
trackType = "forced"
|
|
else if stream.IsDefault
|
|
trackType = "default"
|
|
else
|
|
trackType = "normal"
|
|
end if
|
|
if prefered_lang <> "" and prefered_lang = stream.Track.Language
|
|
tracks[trackType].unshift(stream)
|
|
else
|
|
tracks[trackType].push(stream)
|
|
end if
|
|
end if
|
|
end for
|
|
|
|
tracks["default"].append(tracks["normal"])
|
|
tracks["forced"].append(tracks["default"])
|
|
|
|
textTracks = []
|
|
for i = 0 to tracks["forced"].count() - 1
|
|
if tracks["forced"][i].IsTextSubtitleStream
|
|
textTracks.push(tracks["forced"][i].Track)
|
|
end if
|
|
end for
|
|
return { "all": tracks["forced"], "text": textTracks }
|
|
end function
|
|
|
|
function getSubtitleLanguages()
|
|
return {
|
|
"aar": "Afar",
|
|
"abk": "Abkhazian",
|
|
"ace": "Achinese",
|
|
"ach": "Acoli",
|
|
"ada": "Adangme",
|
|
"ady": "Adyghe; Adygei",
|
|
"afa": "Afro-Asiatic languages",
|
|
"afh": "Afrihili",
|
|
"afr": "Afrikaans",
|
|
"ain": "Ainu",
|
|
"aka": "Akan",
|
|
"akk": "Akkadian",
|
|
"alb": "Albanian",
|
|
"ale": "Aleut",
|
|
"alg": "Algonquian languages",
|
|
"alt": "Southern Altai",
|
|
"amh": "Amharic",
|
|
"ang": "English, Old (ca.450-1100)",
|
|
"anp": "Angika",
|
|
"apa": "Apache languages",
|
|
"ara": "Arabic",
|
|
"arc": "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)",
|
|
"arg": "Aragonese",
|
|
"arm": "Armenian",
|
|
"arn": "Mapudungun; Mapuche",
|
|
"arp": "Arapaho",
|
|
"art": "Artificial languages",
|
|
"arw": "Arawak",
|
|
"asm": "Assamese",
|
|
"ast": "Asturian; Bable; Leonese; Asturleonese",
|
|
"ath": "Athapascan languages",
|
|
"aus": "Australian languages",
|
|
"ava": "Avaric",
|
|
"ave": "Avestan",
|
|
"awa": "Awadhi",
|
|
"aym": "Aymara",
|
|
"aze": "Azerbaijani",
|
|
"bad": "Banda languages",
|
|
"bai": "Bamileke languages",
|
|
"bak": "Bashkir",
|
|
"bal": "Baluchi",
|
|
"bam": "Bambara",
|
|
"ban": "Balinese",
|
|
"baq": "Basque",
|
|
"bas": "Basa",
|
|
"bat": "Baltic languages",
|
|
"bej": "Beja; Bedawiyet",
|
|
"bel": "Belarusian",
|
|
"bem": "Bemba",
|
|
"ben": "Bengali",
|
|
"ber": "Berber languages",
|
|
"bho": "Bhojpuri",
|
|
"bih": "Bihari languages",
|
|
"bik": "Bikol",
|
|
"bin": "Bini; Edo",
|
|
"bis": "Bislama",
|
|
"bla": "Siksika",
|
|
"bnt": "Bantu (Other)",
|
|
"bos": "Bosnian",
|
|
"bra": "Braj",
|
|
"bre": "Breton",
|
|
"btk": "Batak languages",
|
|
"bua": "Buriat",
|
|
"bug": "Buginese",
|
|
"bul": "Bulgarian",
|
|
"bur": "Burmese",
|
|
"byn": "Blin; Bilin",
|
|
"cad": "Caddo",
|
|
"cai": "Central American Indian languages",
|
|
"car": "Galibi Carib",
|
|
"cat": "Catalan; Valencian",
|
|
"cau": "Caucasian languages",
|
|
"ceb": "Cebuano",
|
|
"cel": "Celtic languages",
|
|
"cha": "Chamorro",
|
|
"chb": "Chibcha",
|
|
"che": "Chechen",
|
|
"chg": "Chagatai",
|
|
"chi": "Chinese",
|
|
"chk": "Chuukese",
|
|
"chm": "Mari",
|
|
"chn": "Chinook jargon",
|
|
"cho": "Choctaw",
|
|
"chp": "Chipewyan; Dene Suline",
|
|
"chr": "Cherokee",
|
|
"chu": "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic",
|
|
"chv": "Chuvash",
|
|
"chy": "Cheyenne",
|
|
"cmc": "Chamic languages",
|
|
"cop": "Coptic",
|
|
"cor": "Cornish",
|
|
"cos": "Corsican",
|
|
"cpe": "Creoles and pidgins, English based",
|
|
"cpf": "Creoles and pidgins, French-based ",
|
|
"cpp": "Creoles and pidgins, Portuguese-based ",
|
|
"cre": "Cree",
|
|
"crh": "Crimean Tatar; Crimean Turkish",
|
|
"crp": "Creoles and pidgins ",
|
|
"csb": "Kashubian",
|
|
"cus": "Cushitic languages",
|
|
"cze": "Czech",
|
|
"dak": "Dakota",
|
|
"dan": "Danish",
|
|
"dar": "Dargwa",
|
|
"day": "Land Dayak languages",
|
|
"del": "Delaware",
|
|
"den": "Slave (Athapascan)",
|
|
"dgr": "Dogrib",
|
|
"din": "Dinka",
|
|
"div": "Divehi; Dhivehi; Maldivian",
|
|
"doi": "Dogri",
|
|
"dra": "Dravidian languages",
|
|
"dsb": "Lower Sorbian",
|
|
"dua": "Duala",
|
|
"dum": "Dutch, Middle (ca.1050-1350)",
|
|
"dut": "Dutch; Flemish",
|
|
"dyu": "Dyula",
|
|
"dzo": "Dzongkha",
|
|
"efi": "Efik",
|
|
"egy": "Egyptian (Ancient)",
|
|
"eka": "Ekajuk",
|
|
"elx": "Elamite",
|
|
"eng": "English",
|
|
"enm": "English, Middle (1100-1500)",
|
|
"epo": "Esperanto",
|
|
"est": "Estonian",
|
|
"ewe": "Ewe",
|
|
"ewo": "Ewondo",
|
|
"fan": "Fang",
|
|
"fao": "Faroese",
|
|
"fat": "Fanti",
|
|
"fij": "Fijian",
|
|
"fil": "Filipino; Pilipino",
|
|
"fin": "Finnish",
|
|
"fiu": "Finno-Ugrian languages",
|
|
"fon": "Fon",
|
|
"fre": "French",
|
|
"frm": "French, Middle (ca.1400-1600)",
|
|
"fro": "French, Old (842-ca.1400)",
|
|
"frc": "French (Canada)",
|
|
"frr": "Northern Frisian",
|
|
"frs": "Eastern Frisian",
|
|
"fry": "Western Frisian",
|
|
"ful": "Fulah",
|
|
"fur": "Friulian",
|
|
"gaa": "Ga",
|
|
"gay": "Gayo",
|
|
"gba": "Gbaya",
|
|
"gem": "Germanic languages",
|
|
"geo": "Georgian",
|
|
"ger": "German",
|
|
"gez": "Geez",
|
|
"gil": "Gilbertese",
|
|
"gla": "Gaelic; Scottish Gaelic",
|
|
"gle": "Irish",
|
|
"glg": "Galician",
|
|
"glv": "Manx",
|
|
"gmh": "German, Middle High (ca.1050-1500)",
|
|
"goh": "German, Old High (ca.750-1050)",
|
|
"gon": "Gondi",
|
|
"gor": "Gorontalo",
|
|
"got": "Gothic",
|
|
"grb": "Grebo",
|
|
"grc": "Greek, Ancient (to 1453)",
|
|
"gre": "Greek, Modern (1453-)",
|
|
"grn": "Guarani",
|
|
"gsw": "Swiss German; Alemannic; Alsatian",
|
|
"guj": "Gujarati",
|
|
"gwi": "Gwich'in",
|
|
"hai": "Haida",
|
|
"hat": "Haitian; Haitian Creole",
|
|
"hau": "Hausa",
|
|
"haw": "Hawaiian",
|
|
"heb": "Hebrew",
|
|
"her": "Herero",
|
|
"hil": "Hiligaynon",
|
|
"him": "Himachali languages; Western Pahari languages",
|
|
"hin": "Hindi",
|
|
"hit": "Hittite",
|
|
"hmn": "Hmong; Mong",
|
|
"hmo": "Hiri Motu",
|
|
"hrv": "Croatian",
|
|
"hsb": "Upper Sorbian",
|
|
"hun": "Hungarian",
|
|
"hup": "Hupa",
|
|
"iba": "Iban",
|
|
"ibo": "Igbo",
|
|
"ice": "Icelandic",
|
|
"ido": "Ido",
|
|
"iii": "Sichuan Yi; Nuosu",
|
|
"ijo": "Ijo languages",
|
|
"iku": "Inuktitut",
|
|
"ile": "Interlingue; Occidental",
|
|
"ilo": "Iloko",
|
|
"ina": "Interlingua (International Auxiliary Language Association)",
|
|
"inc": "Indic languages",
|
|
"ind": "Indonesian",
|
|
"ine": "Indo-European languages",
|
|
"inh": "Ingush",
|
|
"ipk": "Inupiaq",
|
|
"ira": "Iranian languages",
|
|
"iro": "Iroquoian languages",
|
|
"ita": "Italian",
|
|
"jav": "Javanese",
|
|
"jbo": "Lojban",
|
|
"jpn": "Japanese",
|
|
"jpr": "Judeo-Persian",
|
|
"jrb": "Judeo-Arabic",
|
|
"kaa": "Kara-Kalpak",
|
|
"kab": "Kabyle",
|
|
"kac": "Kachin; Jingpho",
|
|
"kal": "Kalaallisut; Greenlandic",
|
|
"kam": "Kamba",
|
|
"kan": "Kannada",
|
|
"kar": "Karen languages",
|
|
"kas": "Kashmiri",
|
|
"kau": "Kanuri",
|
|
"kaw": "Kawi",
|
|
"kaz": "Kazakh",
|
|
"kbd": "Kabardian",
|
|
"kha": "Khasi",
|
|
"khi": "Khoisan languages",
|
|
"khm": "Central Khmer",
|
|
"kho": "Khotanese; Sakan",
|
|
"kik": "Kikuyu; Gikuyu",
|
|
"kin": "Kinyarwanda",
|
|
"kir": "Kirghiz; Kyrgyz",
|
|
"kmb": "Kimbundu",
|
|
"kok": "Konkani",
|
|
"kom": "Komi",
|
|
"kon": "Kongo",
|
|
"kor": "Korean",
|
|
"kos": "Kosraean",
|
|
"kpe": "Kpelle",
|
|
"krc": "Karachay-Balkar",
|
|
"krl": "Karelian",
|
|
"kro": "Kru languages",
|
|
"kru": "Kurukh",
|
|
"kua": "Kuanyama; Kwanyama",
|
|
"kum": "Kumyk",
|
|
"kur": "Kurdish",
|
|
"kut": "Kutenai",
|
|
"lad": "Ladino",
|
|
"lah": "Lahnda",
|
|
"lam": "Lamba",
|
|
"lao": "Lao",
|
|
"lat": "Latin",
|
|
"lav": "Latvian",
|
|
"lez": "Lezghian",
|
|
"lim": "Limburgan; Limburger; Limburgish",
|
|
"lin": "Lingala",
|
|
"lit": "Lithuanian",
|
|
"lol": "Mongo",
|
|
"loz": "Lozi",
|
|
"ltz": "Luxembourgish; Letzeburgesch",
|
|
"lua": "Luba-Lulua",
|
|
"lub": "Luba-Katanga",
|
|
"lug": "Ganda",
|
|
"lui": "Luiseno",
|
|
"lun": "Lunda",
|
|
"luo": "Luo (Kenya and Tanzania)",
|
|
"lus": "Lushai",
|
|
"mac": "Macedonian",
|
|
"mad": "Madurese",
|
|
"mag": "Magahi",
|
|
"mah": "Marshallese",
|
|
"mai": "Maithili",
|
|
"mak": "Makasar",
|
|
"mal": "Malayalam",
|
|
"man": "Mandingo",
|
|
"mao": "Maori",
|
|
"map": "Austronesian languages",
|
|
"mar": "Marathi",
|
|
"mas": "Masai",
|
|
"may": "Malay",
|
|
"mdf": "Moksha",
|
|
"mdr": "Mandar",
|
|
"men": "Mende",
|
|
"mga": "Irish, Middle (900-1200)",
|
|
"mic": "Mi'kmaq; Micmac",
|
|
"min": "Minangkabau",
|
|
"mis": "Uncoded languages",
|
|
"mkh": "Mon-Khmer languages",
|
|
"mlg": "Malagasy",
|
|
"mlt": "Maltese",
|
|
"mnc": "Manchu",
|
|
"mni": "Manipuri",
|
|
"mno": "Manobo languages",
|
|
"moh": "Mohawk",
|
|
"mon": "Mongolian",
|
|
"mos": "Mossi",
|
|
"mul": "Multiple languages",
|
|
"mun": "Munda languages",
|
|
"mus": "Creek",
|
|
"mwl": "Mirandese",
|
|
"mwr": "Marwari",
|
|
"myn": "Mayan languages",
|
|
"myv": "Erzya",
|
|
"nah": "Nahuatl languages",
|
|
"nai": "North American Indian languages",
|
|
"nap": "Neapolitan",
|
|
"nau": "Nauru",
|
|
"nav": "Navajo; Navaho",
|
|
"nbl": "Ndebele, South; South Ndebele",
|
|
"nde": "Ndebele, North; North Ndebele",
|
|
"ndo": "Ndonga",
|
|
"nds": "Low German; Low Saxon; German, Low; Saxon, Low",
|
|
"nep": "Nepali",
|
|
"new": "Nepal Bhasa; Newari",
|
|
"nia": "Nias",
|
|
"nic": "Niger-Kordofanian languages",
|
|
"niu": "Niuean",
|
|
"nno": "Norwegian Nynorsk; Nynorsk, Norwegian",
|
|
"nob": "Bokmål, Norwegian; Norwegian Bokmål",
|
|
"nog": "Nogai",
|
|
"non": "Norse, Old",
|
|
"nor": "Norwegian",
|
|
"nqo": "N'Ko",
|
|
"nso": "Pedi; Sepedi; Northern Sotho",
|
|
"nub": "Nubian languages",
|
|
"nwc": "Classical Newari; Old Newari; Classical Nepal Bhasa",
|
|
"nya": "Chichewa; Chewa; Nyanja",
|
|
"nym": "Nyamwezi",
|
|
"nyn": "Nyankole",
|
|
"nyo": "Nyoro",
|
|
"nzi": "Nzima",
|
|
"oci": "Occitan (post 1500); Provençal",
|
|
"oji": "Ojibwa",
|
|
"ori": "Oriya",
|
|
"orm": "Oromo",
|
|
"osa": "Osage",
|
|
"oss": "Ossetian; Ossetic",
|
|
"ota": "Turkish, Ottoman (1500-1928)",
|
|
"oto": "Otomian languages",
|
|
"paa": "Papuan languages",
|
|
"pag": "Pangasinan",
|
|
"pal": "Pahlavi",
|
|
"pam": "Pampanga; Kapampangan",
|
|
"pan": "Panjabi; Punjabi",
|
|
"pap": "Papiamento",
|
|
"pau": "Palauan",
|
|
"peo": "Persian, Old (ca.600-400 B.C.)",
|
|
"per": "Persian",
|
|
"phi": "Philippine languages",
|
|
"phn": "Phoenician",
|
|
"pli": "Pali",
|
|
"pol": "Polish",
|
|
"pon": "Pohnpeian",
|
|
"por": "Portuguese",
|
|
"pob": "Portuguese (Brazil)",
|
|
"pra": "Prakrit languages",
|
|
"pro": "Provençal, Old (to 1500)",
|
|
"pus": "Pushto; Pashto",
|
|
"qaa-qtz": "Reserved for local use",
|
|
"que": "Quechua",
|
|
"raj": "Rajasthani",
|
|
"rap": "Rapanui",
|
|
"rar": "Rarotongan; Cook Islands Maori",
|
|
"roa": "Romance languages",
|
|
"roh": "Romansh",
|
|
"rom": "Romany",
|
|
"rum": "Romanian; Moldavian; Moldovan",
|
|
"run": "Rundi",
|
|
"rup": "Aromanian; Arumanian; Macedo-Romanian",
|
|
"rus": "Russian",
|
|
"sad": "Sandawe",
|
|
"sag": "Sango",
|
|
"sah": "Yakut",
|
|
"sai": "South American Indian (Other)",
|
|
"sal": "Salishan languages",
|
|
"sam": "Samaritan Aramaic",
|
|
"san": "Sanskrit",
|
|
"sas": "Sasak",
|
|
"sat": "Santali",
|
|
"scn": "Sicilian",
|
|
"sco": "Scots",
|
|
"sel": "Selkup",
|
|
"sem": "Semitic languages",
|
|
"sga": "Irish, Old (to 900)",
|
|
"sgn": "Sign Languages",
|
|
"shn": "Shan",
|
|
"sid": "Sidamo",
|
|
"sin": "Sinhala; Sinhalese",
|
|
"sio": "Siouan languages",
|
|
"sit": "Sino-Tibetan languages",
|
|
"sla": "Slavic languages",
|
|
"slo": "Slovak",
|
|
"slv": "Slovenian",
|
|
"sma": "Southern Sami",
|
|
"sme": "Northern Sami",
|
|
"smi": "Sami languages",
|
|
"smj": "Lule Sami",
|
|
"smn": "Inari Sami",
|
|
"smo": "Samoan",
|
|
"sms": "Skolt Sami",
|
|
"sna": "Shona",
|
|
"snd": "Sindhi",
|
|
"snk": "Soninke",
|
|
"sog": "Sogdian",
|
|
"som": "Somali",
|
|
"son": "Songhai languages",
|
|
"sot": "Sotho, Southern",
|
|
"spa": "Spanish; Latin",
|
|
"spa": "Spanish; Castilian",
|
|
"srd": "Sardinian",
|
|
"srn": "Sranan Tongo",
|
|
"srp": "Serbian",
|
|
"srr": "Serer",
|
|
"ssa": "Nilo-Saharan languages",
|
|
"ssw": "Swati",
|
|
"suk": "Sukuma",
|
|
"sun": "Sundanese",
|
|
"sus": "Susu",
|
|
"sux": "Sumerian",
|
|
"swa": "Swahili",
|
|
"swe": "Swedish",
|
|
"syc": "Classical Syriac",
|
|
"syr": "Syriac",
|
|
"tah": "Tahitian",
|
|
"tai": "Tai languages",
|
|
"tam": "Tamil",
|
|
"tat": "Tatar",
|
|
"tel": "Telugu",
|
|
"tem": "Timne",
|
|
"ter": "Tereno",
|
|
"tet": "Tetum",
|
|
"tgk": "Tajik",
|
|
"tgl": "Tagalog",
|
|
"tha": "Thai",
|
|
"tib": "Tibetan",
|
|
"tig": "Tigre",
|
|
"tir": "Tigrinya",
|
|
"tiv": "Tiv",
|
|
"tkl": "Tokelau",
|
|
"tlh": "Klingon; tlhIngan-Hol",
|
|
"tli": "Tlingit",
|
|
"tmh": "Tamashek",
|
|
"tog": "Tonga (Nyasa)",
|
|
"ton": "Tonga (Tonga Islands)",
|
|
"tpi": "Tok Pisin",
|
|
"tsi": "Tsimshian",
|
|
"tsn": "Tswana",
|
|
"tso": "Tsonga",
|
|
"tuk": "Turkmen",
|
|
"tum": "Tumbuka",
|
|
"tup": "Tupi languages",
|
|
"tur": "Turkish",
|
|
"tut": "Altaic languages",
|
|
"tvl": "Tuvalu",
|
|
"twi": "Twi",
|
|
"tyv": "Tuvinian",
|
|
"udm": "Udmurt",
|
|
"uga": "Ugaritic",
|
|
"uig": "Uighur; Uyghur",
|
|
"ukr": "Ukrainian",
|
|
"umb": "Umbundu",
|
|
"und": "Undetermined",
|
|
"urd": "Urdu",
|
|
"uzb": "Uzbek",
|
|
"vai": "Vai",
|
|
"ven": "Venda",
|
|
"vie": "Vietnamese",
|
|
"vol": "Volapük",
|
|
"vot": "Votic",
|
|
"wak": "Wakashan languages",
|
|
"wal": "Walamo",
|
|
"war": "Waray",
|
|
"was": "Washo",
|
|
"wel": "Welsh",
|
|
"wen": "Sorbian languages",
|
|
"wln": "Walloon",
|
|
"wol": "Wolof",
|
|
"xal": "Kalmyk; Oirat",
|
|
"xho": "Xhosa",
|
|
"yao": "Yao",
|
|
"yap": "Yapese",
|
|
"yid": "Yiddish",
|
|
"yor": "Yoruba",
|
|
"ypk": "Yupik languages",
|
|
"zap": "Zapotec",
|
|
"zbl": "Blissymbols; Blissymbolics; Bliss",
|
|
"zen": "Zenaga",
|
|
"zgh": "Standard Moroccan Tamazight",
|
|
"zha": "Zhuang; Chuang",
|
|
"znd": "Zande languages",
|
|
"zul": "Zulu",
|
|
"zun": "Zuni",
|
|
"zxx": "No linguistic content; Not applicable",
|
|
"zza": "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki"
|
|
}
|
|
end function
|
|
|
|
function CreateSeasonDetailsGroup(series, season)
|
|
group = CreateObject("roSGNode", "TVEpisodes")
|
|
group.optionsAvailable = false
|
|
m.global.sceneManager.callFunc("pushScene", group)
|
|
|
|
group.seasonData = ItemMetaData(season.id).json
|
|
group.objects = TVEpisodes(series.id, season.id)
|
|
|
|
group.observeField("episodeSelected", m.port)
|
|
group.observeField("quickPlayNode", m.port)
|
|
|
|
return group
|
|
end function
|
|
|
|
function PlayIntroVideo(video_id, audio_stream_idx) as boolean
|
|
' Intro videos only play if user has cinema mode setting enabled
|
|
if get_user_setting("playback.cinemamode") = "true"
|
|
|
|
' Check if server has intro videos setup and available
|
|
introVideos = GetIntroVideos(video_id)
|
|
|
|
if introVideos = invalid then return true
|
|
|
|
if introVideos.TotalRecordCount > 0
|
|
' Bypass joke pre-roll
|
|
if lcase(introVideos.items[0].name) = "rick roll'd" then return true
|
|
|
|
introVideo = LoadItems_VideoPlayer(introVideos.items[0].id, introVideos.items[0].id, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), false, false)
|
|
|
|
port = CreateObject("roMessagePort")
|
|
introVideo.observeField("state", port)
|
|
m.global.sceneManager.callFunc("pushScene", introVideo)
|
|
introPlaying = true
|
|
|
|
while introPlaying
|
|
msg = wait(0, port)
|
|
if type(msg) = "roSGNodeEvent"
|
|
if msg.GetData() = "finished"
|
|
m.global.sceneManager.callFunc("clearPreviousScene")
|
|
introPlaying = false
|
|
else if msg.GetData() = "stopped"
|
|
introPlaying = false
|
|
return false
|
|
end if
|
|
end if
|
|
end while
|
|
end if
|
|
end if
|
|
return true
|
|
end function
|
|
|
|
function CreateMovieDetailsGroup(movie)
|
|
group = CreateObject("roSGNode", "MovieDetails")
|
|
group.overhangTitle = movie.title
|
|
group.optionsAvailable = false
|
|
m.global.sceneManager.callFunc("pushScene", group)
|
|
|
|
movieMetaData = ItemMetaData(movie.id)
|
|
group.itemContent = movieMetaData
|
|
group.trailerAvailable = false
|
|
|
|
activeUser = get_setting("active_user")
|
|
trailerData = invalid
|
|
if isValid(activeUser) and isValid(movie.id)
|
|
trailerData = api_API().users.getlocaltrailers(activeUser, movie.id)
|
|
end if
|
|
if isValid(trailerData)
|
|
group.trailerAvailable = trailerData.Count() > 0
|
|
end if
|
|
|
|
buttons = group.findNode("buttons")
|
|
for each b in buttons.getChildren(-1, 0)
|
|
b.observeField("buttonSelected", m.port)
|
|
end for
|
|
|
|
extras = group.findNode("extrasGrid")
|
|
extras.observeField("selectedItem", m.port)
|
|
extras.callFunc("loadParts", movieMetaData.json)
|
|
|
|
return group
|
|
end function
|
|
|
|
function CreateSeriesDetailsGroup(series)
|
|
' Get season data early in the function so we can check number of seasons.
|
|
seasonData = TVSeasons(series.id)
|
|
' Divert to season details if user setting goStraightToEpisodeListing is enabled and only one season exists.
|
|
if get_user_setting("ui.tvshows.goStraightToEpisodeListing") = "true" and seasonData.Items.Count() = 1
|
|
return CreateSeasonDetailsGroupByID(series.id, seasonData.Items[0].id)
|
|
end if
|
|
group = CreateObject("roSGNode", "TVShowDetails")
|
|
group.optionsAvailable = false
|
|
m.global.sceneManager.callFunc("pushScene", group)
|
|
|
|
group.itemContent = ItemMetaData(series.id)
|
|
group.seasonData = seasonData ' Re-use variable from beginning of function
|
|
|
|
group.observeField("seasonSelected", m.port)
|
|
|
|
extras = group.findNode("extrasGrid")
|
|
extras.observeField("selectedItem", m.port)
|
|
extras.callFunc("loadParts", group.itemcontent.json)
|
|
|
|
return group
|
|
end function
|
|
|
|
function CreateSeasonDetailsGroupByID(seriesID, seasonID)
|
|
group = CreateObject("roSGNode", "TVEpisodes")
|
|
group.optionsAvailable = false
|
|
m.global.sceneManager.callFunc("pushScene", group)
|
|
|
|
group.seasonData = ItemMetaData(seasonID).json
|
|
group.objects = TVEpisodes(seriesID, seasonID)
|
|
|
|
group.observeField("episodeSelected", m.port)
|
|
group.observeField("quickPlayNode", m.port)
|
|
|
|
return group
|
|
end function
|