Merge pull request #455 from neilsb/transcoding-logic-rework

This commit is contained in:
Anthony Lavado 2021-06-20 22:11:35 -04:00 committed by GitHub
commit f902ae753e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 251 additions and 397 deletions

View File

@ -1,6 +1,8 @@
sub init()
m.top.observeField("state", "onState")
m.bufferPercentage = 0 ' Track whether content is being loaded
m.top.transcodeReasons = []
end sub

View File

@ -7,13 +7,19 @@
<field id="Subtitles" type="array" />
<field id="SelectedSubtitle" type="integer" />
<field id="captionMode" type="string" />
<field id="transcodeParams" type="assocarray" />
<field id="container" type="string" />
<field id="directPlaySupported" type="boolean" />
<field id="decodeAudioSupported" type="boolean" />
<field id="isTranscoded" type="boolean" />
<field id="systemOverlay" type="boolean" value="false" />
<field id="showID" type="string" />
<field id="transcodeParams" type="assocarray" />
<field id="isTranscoded" type="boolean" />
<field id="transcodeReasons" type="array" />
<field id="videoId" type="string" />
<field id="mediaSourceId" type="string" />
<field id="audioIndex" type="integer" />
</interface>
<script type="text/brightscript" uri="JFVideo.brs" />
<children>

View File

@ -386,7 +386,7 @@ function Main (args as Dynamic) as Void
node = m.scene.focusedChild
if node.isSubType("JFVideo") then
trackSelected = selectSubtitleTrack(node.Subtitles, node.SelectedSubtitle)
if trackSelected <> invalid and trackSelected <> node.SelectedSubtitle then
if trackSelected <> invalid and trackSelected <> -2 then
changeSubtitleDuringPlayback(trackSelected)
end if
end if
@ -407,22 +407,7 @@ function Main (args as Dynamic) as Void
end if
else if type(msg) = "roDeviceInfoEvent" then
event = msg.GetInfo()
if event.appFocused <> invalid then
child = m.scene.focusedChild
if child <> invalid and child.isSubType("JFVideo") then
child.systemOverlay = not event.appFocused
if event.AppFocused = true then
systemOverlayClosed()
end if
end if
else if event.Mute <> invalid then
m.mute = event.Mute
child = m.scene.focusedChild
if child <> invalid and child.isSubType("JFVideo") and areSubtitlesDisplayed() and child.systemOverlay = false then
'Event will be called on caption change which includes the current mute status, but we do not want to call until the overlay is closed
reviewSubtitleDisplay()
end if
else if event.exitedScreensaver = true then
if event.exitedScreensaver = true then
m.overhang.callFunc("resetTime")
if group.subtype() = "Home" then
currentTime = CreateObject("roDateTime").AsSeconds()

View File

@ -1,9 +1,11 @@
function VideoPlayer(id, audio_stream_idx = 1)
function VideoPlayer(id, audio_stream_idx = 1, subtitle_idx = -1)
' Get video controls and UI
video = CreateObject("roSGNode", "JFVideo")
video.id = id
video = VideoContent(video, audio_stream_idx)
if video = invalid
AddVideoContent(video, audio_stream_idx, subtitle_idx)
if video.content = invalid
return invalid
end if
jellyfin_blue = "#00a4dcFF"
@ -14,223 +16,126 @@ function VideoPlayer(id, audio_stream_idx = 1)
return video
end function
function VideoContent(video, audio_stream_idx = 1) as object
' Get video stream
sub AddVideoContent(video, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1)
video.content = createObject("RoSGNode", "ContentNode")
params = {}
meta = ItemMetaData(video.id)
if meta = invalid return invalid
if meta = invalid then
video.content = invalid
return
end if
video.content.title = meta.title
video.showID = meta.showID
' If there is a last playback positon, ask user if they want to resume
position = meta.json.UserData.PlaybackPositionTicks
if position > 0 then
dialogResult = startPlayBackOver(position)
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
if dialogResult = -1 then
'User pressed back, return invalid and don't load video
return invalid
else if dialogResult = 1 then
'Start Over selected, change position to 0
position = 0
else if dialogResult = 2 then
'Mark this item as watched, refresh the page, and return invalid so we don't load the video
MarkItemWatched(video.id)
video.content.watched = not video.content.watched
group = m.scene.focusedChild
group.timeLastRefresh = CreateObject("roDateTime").AsSeconds()
group.callFunc("refresh")
return invalid
if playbackPosition = -1 then
playbackPosition = meta.json.UserData.PlaybackPositionTicks
if playbackPosition > 0 then
dialogResult = startPlayBackOver(playbackPosition)
'Dialog returns -1 when back pressed, 0 for resume, and 1 for start over
if dialogResult = -1 then
'User pressed back, return invalid and don't load video
video.content = invalid
return
else if dialogResult = 1 then
'Start Over selected, change position to 0
playbackPosition = 0
else if dialogResult = 2 then
'Mark this item as watched, refresh the page, and return invalid so we don't load the video
MarkItemWatched(video.id)
video.content.watched = not video.content.watched
group = m.scene.focusedChild
group.timeLastRefresh = CreateObject("roDateTime").AsSeconds()
group.callFunc("refresh")
video.content = invalid
return
end if
end if
end if
video.content.PlayStart = int(position/10000000)
video.content.PlayStart = int(playbackPosition / 10000000)
playbackInfo = ItemPostPlaybackInfo(video.id, position)
' Call PlayInfo from server
mediaSourceId = video.id
if meta.live then mediaSourceId = "" ' Don't send mediaSourceId for Live media
playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
video.videoId = video.id
video.mediaSourceId = video.id
video.audioIndex = audio_stream_idx
if playbackInfo = invalid then
return invalid
video.content = invalid
return
end if
params = {}
video.PlaySessionId = playbackInfo.PlaySessionId
if meta.live then
video.content.live = true
video.content.StreamFormat = "hls"
'Original MediaSource seems to be a placeholder and real stream data is available
'after POSTing to PlaybackInfo
json = meta.json
json.AddReplace("MediaSources", playbackInfo.MediaSources)
json.AddReplace("MediaStreams", playbackInfo.MediaSources[0].MediaStreams)
meta.json = json
end if
container = getContainerType(meta)
video.container = container
video.container = getContainerType(meta)
transcodeParams = getTranscodeParameters(meta, audio_stream_idx)
transcodeParams.append({"PlaySessionId": video.PlaySessionId})
subtitles = sortSubtitles(meta.id, playbackInfo.MediaSources[0].MediaStreams)
video.Subtitles = subtitles["all"]
if meta.live then
_livestream_params = {
video.transcodeParams = {
"MediaSourceId": playbackInfo.MediaSources[0].Id,
"LiveStreamId": playbackInfo.MediaSources[0].LiveStreamId,
"MinSegments": 2 'This is a guess about initial buffer size, segments are 3s each
"PlaySessionId": video.PlaySessionId
}
params.append(_livestream_params)
transcodeParams.append(_livestream_params)
end if
subtitles = sortSubtitles(meta.id,meta.json.MediaStreams)
video.Subtitles = subtitles["all"]
video.content.SubtitleTracks = subtitles["text"]
'TODO: allow user selection of subtitle track before playback initiated, for now set to first track
if video.Subtitles.count() then
video.SelectedSubtitle = 0
else
video.SelectedSubtitle = -1
end if
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
video.SelectedSubtitle = -1
if video.SelectedSubtitle <> -1 and displaySubtitlesByUserConfig(video.Subtitles[video.SelectedSubtitle], meta.json.MediaStreams[audio_stream_idx]) then
if video.Subtitles[0].IsTextSubtitleStream then
video.subtitleTrack = video.availableSubtitleTracks[video.Subtitles[0].TextIndex].TrackName
video.suppressCaptions = false
else
video.suppressCaptions = true
'Watch to see if system overlay opened/closed to change transcoding if caption mode changed
m.device.EnableAppFocusEvent(True)
video.captionMode = video.globalCaptionMode
if video.globalCaptionMode = "On" or (video.globalCaptionMode = "When mute" and m.mute = true) then
'Only transcode if subtitles are turned on
transcodeParams.append({"SubtitleStreamIndex" : video.Subtitles[0].index })
end if
end if
else
video.suppressCaptions = true
video.SelectedSubtitle = -1
end if
video.directPlaySupported = playbackInfo.MediaSources[0].SupportsDirectPlay
video.directPlaySupported = directPlaySupported(meta)
video.decodeAudioSupported = decodeAudioSupported(meta, audio_stream_idx)
video.transcodeParams = transcodeParams
if video.directPlaySupported and video.decodeAudioSupported and transcodeParams.SubtitleStreamIndex = invalid then
if video.directPlaySupported then
params.append({
"Static": "true",
"Container": container,
"Container": video.container,
"PlaySessionId": video.PlaySessionId,
"AudioStreamIndex": audio_stream_idx
})
video.content.url = buildURL(Substitute("Videos/{0}/stream", video.id), params)
video.content.streamformat = container
video.content.switchingStrategy = ""
video.isTranscode = False
video.audioTrack = audio_stream_idx + 1 ' Tell Roku what Audio Track to play (convert from 0 based index for roku)
video.isTranscoded = false
else
video.content.url = buildURL(Substitute("Videos/{0}/master.m3u8", video.id), transcodeParams)
' Get transcoding reason
video.transcodeReasons = getTranscodeReasons(playbackInfo.MediaSources[0].TranscodingUrl)
video.content.url = buildURL(playbackInfo.MediaSources[0].TranscodingUrl)
video.isTranscoded = true
end if
video.content = authorize_request(video.content)
' todo - audioFormat is read only
video.content.audioFormat = getAudioFormat(meta)
video.content.setCertificatesFile("common:/certs/ca-bundle.crt")
return video
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 getTranscodeParameters(meta as object, audio_stream_idx = 1)
params = {"AudioStreamIndex": audio_stream_idx}
if decodeAudioSupported(meta, audio_stream_idx) and meta.json.MediaStreams[audio_stream_idx] <> invalid and meta.json.MediaStreams[audio_stream_idx].Type = "Audio" then
audioCodec = meta.json.MediaStreams[audio_stream_idx].codec
audioChannels = meta.json.MediaStreams[audio_stream_idx].channels
else
params.Append({"AudioCodec": "aac"})
' If 5.1 Audio Output is connected then allow transcoding to 5.1
di = CreateObject("roDeviceInfo")
if di.GetAudioOutputChannel() = "5.1 surround" and di.CanDecodeAudio({ Codec: "aac", ChCnt: 6 }).result then
params.Append({"MaxAudioChannels": "6"})
else
params.Append({"MaxAudioChannels": "2"})
end if
end if
streamInfo = {}
if meta.json.MediaStreams[0] <> invalid and meta.json.MediaStreams[0].codec <> invalid then
streamInfo.Codec = meta.json.MediaStreams[0].codec
end if
if meta.json.MediaStreams[0] <> invalid and meta.json.MediaStreams[0].Profile <> invalid and meta.json.MediaStreams[0].Profile.len() > 0 then
streamInfo.Profile = LCase(meta.json.MediaStreams[0].Profile)
end if
if meta.json.MediaSources[0] <> invalid and meta.json.MediaSources[0].container <> invalid and meta.json.MediaSources[0].container.len() > 0 then
streamInfo.Container = meta.json.MediaSources[0].container
end if
devinfo = CreateObject("roDeviceInfo")
res = devinfo.CanDecodeVideo(streamInfo)
if res = invalid or res.result = invalid or res.result = false then
params.Append({"VideoCodec": "h264"})
streamInfo.Profile = "h264"
streamInfo.Container = "ts"
end if
params.Append({"MediaSourceId": meta.id})
params.Append({"DeviceId": devinfo.getChannelClientID()})
return params
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)
tracks = { "forced": [], "default": [], "normal": [] }
'Too many args for using substitute
dashedid = id.left(8) + "-" + id.mid(8,4) + "-" + id.mid(12,4) + "-" + id.mid(16,4) + "-" + id.right(12)
prefered_lang = m.user.Configuration.SubtitleLanguagePreference
for each stream in MediaStreams
if stream.type = "Subtitle" then
'Documentation lists that srt, ttml, and dfxp can be sideloaded but only srt was working in my testing,
'forcing srt for all text subtitles
url = Substitute("{0}/Videos/{1}/{2}/Subtitles/{3}/0/", get_url(), dashedid, id, stream.index.tostr())
url = url + Substitute("Stream.srt?api_key={0}", get_setting("active_user"))
stream = {
"Track": { "Language" : stream.language, "Description": stream.displaytitle , "TrackName": url },
"IsTextSubtitleStream": stream.IsTextSubtitleStream,
"Index": stream.index,
"TextIndex": -1,
"IsDefault": stream.IsDefault,
"IsForced": stream.IsForced
}
if stream.isForced then
trackType = "forced"
else if stream.IsDefault then
trackType = "default"
else
trackType = "normal"
end if
if prefered_lang <> "" and prefered_lang = stream.Track.Language then
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 then tracks["forced"][i].TextIndex = textTracks.count()
textTracks.push(tracks["forced"][i].Track)
end for
return { "all" : tracks["forced"], "text": textTracks }
end function
'Opens dialog asking user if they want to resume video or start playback over
function startPlayBackOver(time as LongInteger) as integer
@ -269,20 +174,6 @@ function directPlaySupported(meta as object) as boolean
end function
function decodeAudioSupported(meta as object, audio_stream_idx = 1) as boolean
'Check for missing audio and allow playing
if meta.json.MediaStreams[audio_stream_idx] = invalid or meta.json.MediaStreams[audio_stream_idx].Type <> "Audio" then return true
devinfo = CreateObject("roDeviceInfo")
codec = meta.json.MediaStreams[audio_stream_idx].codec
streamInfo = { Codec: codec, ChCnt: meta.json.MediaStreams[audio_stream_idx].channels }
'Otherwise check Roku can decode stream and channels
canDecode = devinfo.CanDecodeAudio(streamInfo)
return canDecode.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 ""
@ -319,7 +210,7 @@ end function
sub ReportPlayback(video, state = "update" as string)
if video = invalid or video.position = invalid then return
if video = invalid or video.position = invalid then return invalid
params = {
"PlaySessionId": video.PlaySessionId,
@ -343,25 +234,6 @@ function StopPlayback()
ReportPlayback(video, "stop")
end function
function displaySubtitlesByUserConfig(subtitleTrack, audioTrack)
subtitleMode = m.user.Configuration.SubtitleMode
audioLanguagePreference = m.user.Configuration.AudioLanguagePreference
subtitleLanguagePreference = m.user.Configuration.SubtitleLanguagePreference
if subtitleMode = "Default"
return (subtitleTrack.isForced or subtitleTrack.isDefault)
else if subtitleMode = "Smart"
return (audioLanguagePreference <> "" and audioTrack.Language <> invalid and subtitleLanguagePreference <> "" and subtitleTrack.Track.Language <> invalid and subtitleLanguagePreference = subtitleTrack.Track.Language and audioLanguagePreference <> audioTrack.Language)
else if subtitleMode = "OnlyForced"
return subtitleTrack.IsForced
else if subtitleMode = "Always"
return true
else if subtitleMode = "None"
return false
else
return false
end if
end function
function autoPlayNextEpisode(videoID as string, showID as string)
' use web client setting
if m.user.Configuration.EnableNextEpisodeAutoPlay then

View File

@ -10,7 +10,7 @@ function ItemGetPlaybackInfo(id as string, StartTimeTicks = 0 as longinteger)
return getJson(resp)
end function
function ItemPostPlaybackInfo(id as string, StartTimeTicks = 0 as longinteger)
function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string , audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger)
body = {
"DeviceProfile": getDeviceProfile()
}
@ -19,8 +19,15 @@ function ItemPostPlaybackInfo(id as string, StartTimeTicks = 0 as longinteger)
"StartTimeTicks": StartTimeTicks,
"IsPlayback": true,
"AutoOpenLiveStream": true,
"MaxStreamingBitrate": "140000000"
"MaxStreamingBitrate": "140000000",
"MaxStaticBitrate": "140000000",
"SubtitleStreamIndex": subtitleTrackIndex
}
if mediaSourceId <> "" then params.MediaSourceId = mediaSourceId
if audioTrackIndex > -1 then params.AudioStreamIndex = audioTrackIndex
req = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params)
req.SetRequest("POST")
return postJson(req, FormatJson(body))

View File

@ -37,8 +37,14 @@ function buildParams(params={} as Object) as string
end function
function buildURL(path as String, params={} as Object) as string
full_url = get_url() + "/" + path
' Add intial '/' if path does not start with one
if path.Left(1) = "/"
full_url = get_url() + path
else
full_url = get_url() + "/" + path
end if
if params.count() > 0
full_url = full_url + "?" + buildParams(params)
end if

View File

@ -1,3 +1,143 @@
function selectSubtitleTrack(tracks, current = -1) as integer
video = m.scene.focusedChild
trackSelected = selectSubtitleTrackDialog(video.Subtitles, video.SelectedSubtitle)
if trackSelected = invalid or tackSelected = -1 then ' back pressed in Dialog - no selection made
return -2
else
return trackSelected - 1
end if
end function
' Present Dialog to user to select subtitle track
function selectSubtitleTrackDialog(tracks, currentTrack = -1)
iso6392 = getSubtitleLanguages()
options = ["None"]
for each item in tracks
forced = ""
default = ""
if item.IsForced then forced = " [Forced]"
if item.IsDefault then default = " - Default"
if item.Track.Language <> invalid then
language = iso6392.lookup(item.Track.Language)
if language = invalid then language = item.Track.Language
else
language = "Undefined"
end if
options.push(language + forced + default)
end for
return option_dialog(options, "Select a subtitle track", currentTrack + 1)
end function
sub changeSubtitleDuringPlayback(newid)
' If no subtitles set
if newid = invalid or newid = -1 then
turnoffSubtitles()
return
end if
video = m.scene.focusedChild
' If no change of subtitle track, return
if newId = video.SelectedSubtitle then return
currentSubtitles = video.Subtitles[video.SelectedSubtitle]
newSubtitles = video.Subtitles[newid]
if newSubtitles.IsEncoded then
' Switching to Encoded Subtitle stream
video.control = "stop"
AddVideoContent(video, video.audioIndex, newSubtitles.Index, video.position * 10000000)
video.control = "play"
video.globalCaptionMode = "Off" ' Using encoded subtitles - so turn off text subtitles
else if (currentSubtitles <> invalid AND currentSubtitles.IsEncoded) then
' Switching from an Encoded stream to a text stream
video.control = "stop"
AddVideoContent(video, video.audioIndex, -1, video.position * 10000000)
video.control = "play"
video.globalCaptionMode = "On"
video.subtitleTrack = video.availableSubtitleTracks[newSubtitles.TextIndex].TrackName
else
' Switch to Text Subtitle Track
video.globalCaptionMode = "On"
video.subtitleTrack = video.availableSubtitleTracks[newSubtitles.TextIndex].TrackName
end if
video.SelectedSubtitle = newId
end sub
function turnoffSubtitles()
video = m.scene.focusedChild
current = video.SelectedSubtitle
video.SelectedSubtitle = -1
video.globalCaptionMode = "Off"
m.device.EnableAppFocusEvent(false)
' Check if Enoded subtitles are being displayed, and turn off
if current > -1 and video.Subtitles[current].IsEncoded then
video.control = "stop"
AddVideoContent(video, video.audioIndex, -1, video.position * 10000000)
video.control = "play"
end if
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)
tracks = { "forced": [], "default": [], "normal": [] }
'Too many args for using substitute
dashedid = id.left(8) + "-" + id.mid(8,4) + "-" + id.mid(12,4) + "-" + id.mid(16,4) + "-" + id.right(12)
prefered_lang = m.user.Configuration.SubtitleLanguagePreference
for each stream in MediaStreams
if stream.type = "Subtitle" then
url = ""
if(stream.DeliveryUrl <> invalid) then
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 then
trackType = "forced"
else if stream.IsDefault then
trackType = "default"
else
trackType = "normal"
end if
if prefered_lang <> "" and prefered_lang = stream.Track.Language then
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 then
tracks["forced"][i].TextIndex = textTracks.count()
textTracks.push(tracks["forced"][i].Track)
end if
end for
return { "all" : tracks["forced"], "text": textTracks }
end function
function getSubtitleLanguages()
return {
"aar": "Afar",

View File

@ -1,164 +0,0 @@
function selectSubtitleTrack(tracks, current = -1)
video = m.scene.focusedChild
trackSelected = selectSubtitleTrackDialog(video.Subtitles, video.SelectedSubtitle)
if trackSelected = -1 then
return invalid
else
return trackSelected - 1
end if
end function
function selectSubtitleTrackDialog(tracks, currentTrack = -1)
iso6392 = getSubtitleLanguages()
options = ["None"]
for each item in tracks
forced = ""
default = ""
if item.IsForced then forced = " [Forced]"
if item.IsDefault then default = " - Default"
if item.Track.Language <> invalid then
language = iso6392.lookup(item.Track.Language)
if language = invalid then language = item.Track.Language
else
language = "Undefined"
end if
options.push(language + forced + default)
end for
return option_dialog(options, "Select a subtitle track", currentTrack + 1)
end function
sub changeSubtitleDuringPlayback(newid)
if newid = invalid then return
if newid = -1 then
turnoffSubtitles()
return
end if
video = m.scene.focusedChild
oldTrack = video.Subtitles[video.SelectedSubtitle]
newTrack = video.Subtitles[newid]
video.captionMode = video.globalCaptionMode
m.device.EnableAppFocusEvent(not newTrack.IsTextSubtitleStream)
video.SelectedSubtitle = newid
if newTrack.IsTextSubtitleStream then
if video.content.PlayStart > video.position
'User has rewinded to before playback was initiated. The Roku never loaded this portion of the text subtitle
'Changing the track will cause plaback to jump to initial bookmark position.
video.suppressCaptions = true
rebuildURL(false)
end if
video.subtitleTrack = video.availableSubtitleTracks[newTrack.TextIndex].TrackName
video.suppressCaptions = false
else
video.suppressCaptions = true
end if
'Rebuild URL if subtitle track is video or if changed from video subtitle to text subtitle.
if not newTrack.IsTextSubtitleStream then
rebuildURL(true)
else if oldTrack <> invalid and not oldTrack.IsTextSubtitleStream then
rebuildURL(false)
if newTrack.TextIndex > 0 then video.subtitleTrack = video.availableSubtitleTracks[newTrack.TextIndex].TrackName
end if
end sub
function turnoffSubtitles()
video = m.scene.focusedChild
current = video.SelectedSubtitle
video.SelectedSubtitle = -1
video.suppressCaptions = true
m.device.EnableAppFocusEvent(false)
if current > -1 and not video.Subtitles[current].IsTextSubtitleStream then
rebuildURL(false)
end if
end function
function systemOverlayClosed()
video = m.scene.focusedChild
if video.globalCaptionMode <> video.captionMode then
video.captionMode = video.globalCaptionMode
reviewSubtitleDisplay()
end if
end function
function reviewSubtitleDisplay()
'TODO handle changing subtitles tracks during playback
displayed = areSubtitlesDisplayed()
needed = areSubtitlesNeeded()
print "displayed: " displayed " needed: " needed
if areSubtitlesNeeded() and (not areSubtitlesDisplayed()) then
rebuildURL(true)
else if areSubtitlesDisplayed() and (not areSubtitlesNeeded()) then
rebuildURL(false)
end if
end function
function areSubtitlesDisplayed()
index = m.scene.focusedChild.transcodeParams.lookup("SubtitleStreamIndex")
if index <> invalid and index <> -1 then
return true
else
return false
end if
end function
function areSubtitlesNeeded()
captions = m.scene.focusedChild.globalCaptionMode
if captions = "On"
return true
else if captions = "Off"
return false
else if captions = "When mute"
return m.mute
else if captions = "Instant replay"
'Unsupported. Do we want to do this? Is it worth transcoding for rewinded content and then untranscoding?
return false
end if
end function
sub rebuildURL(captions as boolean)
playBackBuffer = -5
video = m.scene.focusedChild
video.control = "pause"
tmpParams = video.transcodeParams
if captions = false then
tmpParams.delete("SubtitleStreamIndex")
else
if video.Subtitles[video.SelectedSubtitle] <> invalid then
tmpParams.addreplace("SubtitleStreamIndex", int(video.Subtitles[video.SelectedSubtitle].Index))
end if
end if
if video.isTranscoded then
deleteTranscode(video.PlaySessionId)
end if
video.PlaySessionId = ItemGetPlaybackInfo(video.id, int(video.position) + playBackBuffer).PlaySessionId
tmpParams.PlaySessionId = video.PlaySessionId
video.transcodeParams = tmpParams
if video.directPlaySupported and video.decodeAudioSupported and not captions then
'Captions are off and we do not need to transcode video or audo
base = Substitute("Videos/{0}/stream", video.id)
params = {
"Static": "true",
"Container": video.container
"PlaySessionId": video.PlaySessionId
}
video.isTranscoded = false
video.content.streamformat = video.container
else
'Captions are on or we need to transcode for any other reason
video.content.streamformat = "hls"
base = Substitute("Videos/{0}/master.m3u8", video.id)
video.isTranscoded = true
params = video.transcodeParams
end if
video.content.url = buildURL(base, params)
video.content.PlayStart = int(video.position + playBackBuffer)
video.control = "play"
end sub