Merge pull request #455 from neilsb/transcoding-logic-rework
This commit is contained in:
commit
f902ae753e
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue
Block a user