Merge pull request #1617 from jellyfin/2.0.z
This commit is contained in:
commit
48c19eda75
2
Makefile
2
Makefile
|
@ -3,7 +3,7 @@
|
||||||
# If you want to get_images, you'll also need convert from ImageMagick
|
# If you want to get_images, you'll also need convert from ImageMagick
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
VERSION := 2.0.0
|
VERSION := 2.0.1
|
||||||
|
|
||||||
## usage
|
## usage
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
import "pkg:/source/utils/misc.bs"
|
import "pkg:/source/utils/misc.bs"
|
||||||
|
|
||||||
|
' @fileoverview Clock component to display current time formatted based on user's chosen 12 or 24 hour setting
|
||||||
|
|
||||||
|
' Possible clock formats
|
||||||
|
enum ClockFormat
|
||||||
|
h12 = "12h"
|
||||||
|
h24 = "24h"
|
||||||
|
end enum
|
||||||
|
|
||||||
sub init()
|
sub init()
|
||||||
|
|
||||||
' If hideclick setting is checked, exit without setting any variables
|
' If hideclick setting is enabled, exit without setting any variables
|
||||||
if m.global.session.user.settings["ui.design.hideclock"]
|
if m.global.session.user.settings["ui.design.hideclock"]
|
||||||
return
|
return
|
||||||
end if
|
end if
|
||||||
|
@ -16,11 +24,11 @@ sub init()
|
||||||
m.currentTimeTimer.control = "start"
|
m.currentTimeTimer.control = "start"
|
||||||
|
|
||||||
' Default to 12 hour clock
|
' Default to 12 hour clock
|
||||||
m.format = "short-h12"
|
m.format = ClockFormat.h12
|
||||||
|
|
||||||
' If user has selected a 24 hour clock, update date display format
|
' If user has selected a 24 hour clock, update date display format
|
||||||
if LCase(m.global.device.clockFormat) = "24h"
|
if LCase(m.global.device.clockFormat) = ClockFormat.h24
|
||||||
m.format = "short-h24"
|
m.format = ClockFormat.h24
|
||||||
end if
|
end if
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
@ -34,6 +42,64 @@ sub onCurrentTimeTimerFire()
|
||||||
' Convert to local time zone
|
' Convert to local time zone
|
||||||
m.dateTimeObject.ToLocalTime()
|
m.dateTimeObject.ToLocalTime()
|
||||||
|
|
||||||
' Format time as requested
|
' Format time for display - based on 12h/24h setting
|
||||||
m.clockTime.text = m.dateTimeObject.asTimeStringLoc(m.format)
|
formattedTime = formatTimeAsString()
|
||||||
|
|
||||||
|
' Display time
|
||||||
|
m.clockTime.text = formattedTime
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
' formatTimeAsString: Returns a string with the current time formatted for either a 12 or 24 hour clock
|
||||||
|
'
|
||||||
|
' @return {string} current time formatted for either a 12 hour or 24 hour clock
|
||||||
|
function formatTimeAsString() as string
|
||||||
|
return m.format = ClockFormat.h12 ? format12HourTime() : format24HourTime()
|
||||||
|
end function
|
||||||
|
|
||||||
|
' format12HourTime: Returns a string with the current time formatted for a 12 hour clock
|
||||||
|
'
|
||||||
|
' @return {string} current time formatted for a 12 hour clock
|
||||||
|
function format12HourTime() as string
|
||||||
|
currentHour = m.dateTimeObject.GetHours()
|
||||||
|
currentMinute = m.dateTimeObject.GetMinutes()
|
||||||
|
|
||||||
|
displayedHour = StrI(currentHour).trim()
|
||||||
|
displayedMinute = StrI(currentMinute).trim()
|
||||||
|
meridian = currentHour < 12 ? "am" : "pm"
|
||||||
|
|
||||||
|
if currentHour = 0
|
||||||
|
displayedHour = "12"
|
||||||
|
end if
|
||||||
|
|
||||||
|
if currentHour > 12
|
||||||
|
correctedHour = currentHour - 12
|
||||||
|
displayedHour = StrI(correctedHour).trim()
|
||||||
|
end if
|
||||||
|
|
||||||
|
if currentMinute < 10
|
||||||
|
displayedMinute = `0${displayedMinute}`
|
||||||
|
end if
|
||||||
|
|
||||||
|
return `${displayedHour}:${displayedMinute} ${meridian}`
|
||||||
|
end function
|
||||||
|
|
||||||
|
' format24HourTime: Returns a string with the current time formatted for a 24 hour clock
|
||||||
|
'
|
||||||
|
' @return {string} current time formatted for a 24 hour clock
|
||||||
|
function format24HourTime() as string
|
||||||
|
currentHour = m.dateTimeObject.GetHours()
|
||||||
|
currentMinute = m.dateTimeObject.GetMinutes()
|
||||||
|
|
||||||
|
displayedHour = StrI(currentHour).trim()
|
||||||
|
displayedMinute = StrI(currentMinute).trim()
|
||||||
|
|
||||||
|
if currentHour < 10
|
||||||
|
displayedHour = `0${displayedHour}`
|
||||||
|
end if
|
||||||
|
|
||||||
|
if currentMinute < 10
|
||||||
|
displayedMinute = `0${displayedMinute}`
|
||||||
|
end if
|
||||||
|
|
||||||
|
return `${displayedHour}:${displayedMinute}`
|
||||||
|
end function
|
||||||
|
|
|
@ -7,6 +7,11 @@ import "pkg:/source/api/Image.bs"
|
||||||
import "pkg:/source/api/userauth.bs"
|
import "pkg:/source/api/userauth.bs"
|
||||||
import "pkg:/source/utils/deviceCapabilities.bs"
|
import "pkg:/source/utils/deviceCapabilities.bs"
|
||||||
|
|
||||||
|
enum SubtitleSelection
|
||||||
|
notset = -2
|
||||||
|
none = -1
|
||||||
|
end enum
|
||||||
|
|
||||||
sub init()
|
sub init()
|
||||||
m.user = AboutMe()
|
m.user = AboutMe()
|
||||||
m.top.functionName = "loadItems"
|
m.top.functionName = "loadItems"
|
||||||
|
@ -44,19 +49,18 @@ sub loadItems()
|
||||||
id = m.top.itemId
|
id = m.top.itemId
|
||||||
mediaSourceId = invalid
|
mediaSourceId = invalid
|
||||||
audio_stream_idx = m.top.selectedAudioStreamIndex
|
audio_stream_idx = m.top.selectedAudioStreamIndex
|
||||||
subtitle_idx = m.top.selectedSubtitleIndex
|
|
||||||
forceTranscoding = false
|
forceTranscoding = false
|
||||||
|
|
||||||
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding)]
|
m.top.content = [LoadItems_VideoPlayer(id, mediaSourceId, audio_stream_idx, forceTranscoding)]
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean) as dynamic
|
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean) as dynamic
|
||||||
|
|
||||||
video = {}
|
video = {}
|
||||||
video.id = id
|
video.id = id
|
||||||
video.content = createObject("RoSGNode", "ContentNode")
|
video.content = createObject("RoSGNode", "ContentNode")
|
||||||
|
|
||||||
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, subtitle_idx, forceTranscoding)
|
LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx, forceTranscoding)
|
||||||
|
|
||||||
if video.content = invalid
|
if video.content = invalid
|
||||||
return invalid
|
return invalid
|
||||||
|
@ -65,9 +69,10 @@ function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic,
|
||||||
return video
|
return video
|
||||||
end function
|
end function
|
||||||
|
|
||||||
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean)
|
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean)
|
||||||
|
|
||||||
meta = ItemMetaData(video.id)
|
meta = ItemMetaData(video.id)
|
||||||
|
subtitle_idx = m.top.selectedSubtitleIndex
|
||||||
|
|
||||||
if not isValid(meta)
|
if not isValid(meta)
|
||||||
video.errorMsg = "Error loading metadata"
|
video.errorMsg = "Error loading metadata"
|
||||||
|
@ -77,6 +82,21 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||||
|
|
||||||
videotype = LCase(meta.type)
|
videotype = LCase(meta.type)
|
||||||
|
|
||||||
|
' Check for any Live TV streams coming from other places other than the TV Guide
|
||||||
|
if isValid(meta.json) and isValid(meta.json.ChannelId)
|
||||||
|
if isValid(meta.json.EpisodeTitle)
|
||||||
|
meta.title = meta.json.EpisodeTitle
|
||||||
|
else if isValid(meta.json.Name)
|
||||||
|
meta.title = meta.json.Name
|
||||||
|
end if
|
||||||
|
meta.live = true
|
||||||
|
if LCase(meta.json.type) = "program"
|
||||||
|
video.id = meta.json.ChannelId
|
||||||
|
else
|
||||||
|
video.id = meta.json.id
|
||||||
|
end if
|
||||||
|
end if
|
||||||
|
|
||||||
if videotype = "episode" or videotype = "series"
|
if videotype = "episode" or videotype = "series"
|
||||||
video.content.contenttype = "episode"
|
video.content.contenttype = "episode"
|
||||||
end if
|
end if
|
||||||
|
@ -107,16 +127,41 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||||
if meta.live then mediaSourceId = ""
|
if meta.live then mediaSourceId = ""
|
||||||
|
|
||||||
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
|
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
|
||||||
video.videoId = video.id
|
|
||||||
video.mediaSourceId = mediaSourceId
|
|
||||||
video.audioIndex = audio_stream_idx
|
|
||||||
|
|
||||||
if not isValid(m.playbackInfo)
|
if not isValid(m.playbackInfo)
|
||||||
video.errorMsg = "Error loading playback info"
|
video.errorMsg = "Error loading playback info"
|
||||||
video.content = invalid
|
video.content = invalid
|
||||||
return
|
return
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
addSubtitlesToVideo(video, meta)
|
||||||
|
|
||||||
|
' Enable default subtitle track
|
||||||
|
if subtitle_idx = SubtitleSelection.notset
|
||||||
|
defaultSubtitleIndex = defaultSubtitleTrackFromVid(video.id)
|
||||||
|
|
||||||
|
if defaultSubtitleIndex <> SubtitleSelection.none
|
||||||
|
video.SelectedSubtitle = defaultSubtitleIndex
|
||||||
|
subtitle_idx = defaultSubtitleIndex
|
||||||
|
|
||||||
|
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition)
|
||||||
|
if not isValid(m.playbackInfo)
|
||||||
|
video.errorMsg = "Error loading playback info"
|
||||||
|
video.content = invalid
|
||||||
|
return
|
||||||
|
end if
|
||||||
|
|
||||||
|
addSubtitlesToVideo(video, meta)
|
||||||
|
else
|
||||||
|
video.SelectedSubtitle = subtitle_idx
|
||||||
|
end if
|
||||||
|
else
|
||||||
|
video.SelectedSubtitle = subtitle_idx
|
||||||
|
end if
|
||||||
|
|
||||||
|
video.videoId = video.id
|
||||||
|
video.mediaSourceId = mediaSourceId
|
||||||
|
video.audioIndex = audio_stream_idx
|
||||||
|
|
||||||
video.PlaySessionId = m.playbackInfo.PlaySessionId
|
video.PlaySessionId = m.playbackInfo.PlaySessionId
|
||||||
|
|
||||||
if meta.live
|
if meta.live
|
||||||
|
@ -130,8 +175,6 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||||
m.playbackInfo = meta.json
|
m.playbackInfo = meta.json
|
||||||
end if
|
end if
|
||||||
|
|
||||||
addSubtitlesToVideo(video, meta)
|
|
||||||
|
|
||||||
if meta.live
|
if meta.live
|
||||||
video.transcodeParams = {
|
video.transcodeParams = {
|
||||||
"MediaSourceId": m.playbackInfo.MediaSources[0].Id,
|
"MediaSourceId": m.playbackInfo.MediaSources[0].Id,
|
||||||
|
@ -183,13 +226,106 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||||
setCertificateAuthority(video.content)
|
setCertificateAuthority(video.content)
|
||||||
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
|
video.audioTrack = (audio_stream_idx + 1).ToStr() ' Roku's track indexes count from 1. Our index is zero based
|
||||||
|
|
||||||
video.SelectedSubtitle = subtitle_idx
|
|
||||||
|
|
||||||
if not fully_external
|
if not fully_external
|
||||||
video.content = authRequest(video.content)
|
video.content = authRequest(video.content)
|
||||||
end if
|
end if
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
|
' defaultSubtitleTrackFromVid: Identifies the default subtitle track given video id
|
||||||
|
'
|
||||||
|
' @param {dynamic} videoID - id of video user is playing
|
||||||
|
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
|
||||||
|
function defaultSubtitleTrackFromVid(videoID) as integer
|
||||||
|
if m.global.session.user.configuration.SubtitleMode = "None"
|
||||||
|
return SubtitleSelection.none ' No subtitles desired: return none
|
||||||
|
end if
|
||||||
|
|
||||||
|
meta = ItemMetaData(videoID)
|
||||||
|
|
||||||
|
if not isValid(meta) then return SubtitleSelection.none
|
||||||
|
if not isValid(meta.json) then return SubtitleSelection.none
|
||||||
|
if not isValidAndNotEmpty(meta.json.mediaSources) then return SubtitleSelection.none
|
||||||
|
if not isValidAndNotEmpty(meta.json.MediaSources[0].MediaStreams) then return SubtitleSelection.none
|
||||||
|
|
||||||
|
subtitles = sortSubtitles(meta.id, meta.json.MediaSources[0].MediaStreams)
|
||||||
|
selectedAudioLanguage = meta.json.MediaSources[0].MediaStreams[m.top.selectedAudioStreamIndex].Language ?? ""
|
||||||
|
|
||||||
|
defaultTextSubs = defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage, true) ' Find correct subtitle track (forced text)
|
||||||
|
if defaultTextSubs <> SubtitleSelection.none
|
||||||
|
return defaultTextSubs
|
||||||
|
end if
|
||||||
|
|
||||||
|
if not m.global.session.user.settings["playback.subs.onlytext"]
|
||||||
|
return defaultSubtitleTrack(subtitles["all"], selectedAudioLanguage) ' if no appropriate text subs exist, allow non-text
|
||||||
|
end if
|
||||||
|
|
||||||
|
return SubtitleSelection.none
|
||||||
|
end function
|
||||||
|
|
||||||
|
' defaultSubtitleTrack:
|
||||||
|
'
|
||||||
|
' @param {dynamic} sortedSubtitles - array of subtitles sorted by type and language
|
||||||
|
' @param {string} selectedAudioLanguage - language for selected audio track
|
||||||
|
' @param {boolean} [requireText=false] - indicates if only text subtitles should be considered
|
||||||
|
' @return {integer} indicating the default track's server-side index. Defaults to {SubtitleSelection.none} is one is not found
|
||||||
|
function defaultSubtitleTrack(sortedSubtitles, selectedAudioLanguage as string, requireText = false as boolean) as integer
|
||||||
|
userConfig = m.global.session.user.configuration
|
||||||
|
|
||||||
|
subtitleMode = isValid(userConfig.SubtitleMode) ? LCase(userConfig.SubtitleMode) : ""
|
||||||
|
|
||||||
|
allowSmartMode = false
|
||||||
|
|
||||||
|
' Only evaluate selected audio language if we have a value
|
||||||
|
if selectedAudioLanguage <> ""
|
||||||
|
allowSmartMode = selectedAudioLanguage <> userConfig.SubtitleLanguagePreference
|
||||||
|
end if
|
||||||
|
|
||||||
|
for each item in sortedSubtitles
|
||||||
|
' Only auto-select subtitle if language matches SubtitleLanguagePreference
|
||||||
|
languageMatch = true
|
||||||
|
if userConfig.SubtitleLanguagePreference <> ""
|
||||||
|
languageMatch = (userConfig.SubtitleLanguagePreference = item.Track.Language)
|
||||||
|
end if
|
||||||
|
|
||||||
|
' Ensure textuality of subtitle matches preferenced passed as arg
|
||||||
|
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
|
||||||
|
|
||||||
|
if languageMatch and matchTextReq
|
||||||
|
if subtitleMode = "default" and (item.isForced or item.IsDefault)
|
||||||
|
' Return first forced or default subtitle track
|
||||||
|
return item.Index
|
||||||
|
else if subtitleMode = "always"
|
||||||
|
' Return the first found subtitle track
|
||||||
|
return item.Index
|
||||||
|
else if subtitleMode = "onlyforced" and item.IsForced
|
||||||
|
' Return first forced subtitle track
|
||||||
|
return item.Index
|
||||||
|
else if subtitleMode = "smart" and allowSmartMode
|
||||||
|
' Return the first found subtitle track
|
||||||
|
return item.Index
|
||||||
|
end if
|
||||||
|
end if
|
||||||
|
end for
|
||||||
|
|
||||||
|
' User has chosed smart subtitle mode
|
||||||
|
' We already attempted to load subtitles in preferred language, but none were found.
|
||||||
|
' Fall back to default behaviour while ignoring preferredlanguage
|
||||||
|
if subtitleMode = "smart" and allowSmartMode
|
||||||
|
for each item in sortedSubtitles
|
||||||
|
' Ensure textuality of subtitle matches preferenced passed as arg
|
||||||
|
matchTextReq = ((requireText and item.IsTextSubtitleStream) or not requireText)
|
||||||
|
if matchTextReq
|
||||||
|
if item.isForced or item.IsDefault
|
||||||
|
' Return first forced or default subtitle track
|
||||||
|
return item.Index
|
||||||
|
end if
|
||||||
|
end if
|
||||||
|
end for
|
||||||
|
end if
|
||||||
|
|
||||||
|
return SubtitleSelection.none ' Keep current default behavior of "None", if no correct subtitle is identified
|
||||||
|
end function
|
||||||
|
|
||||||
sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
||||||
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
||||||
if protocol <> "file"
|
if protocol <> "file"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<interface>
|
<interface>
|
||||||
<field id="itemId" type="string" />
|
<field id="itemId" type="string" />
|
||||||
<field id="selectedAudioStreamIndex" type="integer" value="0" />
|
<field id="selectedAudioStreamIndex" type="integer" value="0" />
|
||||||
<field id="selectedSubtitleIndex" type="integer" value="-1" />
|
<field id="selectedSubtitleIndex" type="integer" value="-2" />
|
||||||
<field id="isIntro" type="boolean" />
|
<field id="isIntro" type="boolean" />
|
||||||
<field id="startIndex" type="integer" value="0" />
|
<field id="startIndex" type="integer" value="0" />
|
||||||
<field id="itemType" type="string" value="" />
|
<field id="itemType" type="string" value="" />
|
||||||
|
|
|
@ -140,14 +140,12 @@ sub loadInitialItems()
|
||||||
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
|
m.loadItemsTask.itemId = m.top.parentItem.parentFolder
|
||||||
else if LCase(m.view) = "artistspresentation" or LCase(m.options.view) = "artistspresentation"
|
else if LCase(m.view) = "artistspresentation" or LCase(m.options.view) = "artistspresentation"
|
||||||
m.loadItemsTask.genreIds = ""
|
m.loadItemsTask.genreIds = ""
|
||||||
m.top.showItemTitles = "hidealways"
|
|
||||||
else if LCase(m.view) = "artistsgrid" or LCase(m.options.view) = "artistsgrid"
|
else if LCase(m.view) = "artistsgrid" or LCase(m.options.view) = "artistsgrid"
|
||||||
m.loadItemsTask.genreIds = ""
|
m.loadItemsTask.genreIds = ""
|
||||||
else if LCase(m.view) = "albumartistsgrid" or LCase(m.options.view) = "albumartistsgrid"
|
else if LCase(m.view) = "albumartistsgrid" or LCase(m.options.view) = "albumartistsgrid"
|
||||||
m.loadItemsTask.genreIds = ""
|
m.loadItemsTask.genreIds = ""
|
||||||
else if LCase(m.view) = "albumartistspresentation" or LCase(m.options.view) = "albumartistspresentation"
|
else if LCase(m.view) = "albumartistspresentation" or LCase(m.options.view) = "albumartistspresentation"
|
||||||
m.loadItemsTask.genreIds = ""
|
m.loadItemsTask.genreIds = ""
|
||||||
m.top.showItemTitles = "hidealways"
|
|
||||||
else
|
else
|
||||||
m.loadItemsTask.itemId = m.top.parentItem.Id
|
m.loadItemsTask.itemId = m.top.parentItem.Id
|
||||||
end if
|
end if
|
||||||
|
|
|
@ -111,19 +111,20 @@ sub processSubtitleSelection()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
if m.selectedSubtitle.IsEncoded
|
if m.selectedSubtitle.IsEncoded
|
||||||
|
' Roku can not natively display these subtitles, so turn off the caption mode on the device
|
||||||
m.view.globalCaptionMode = "Off"
|
m.view.globalCaptionMode = "Off"
|
||||||
else
|
else
|
||||||
|
' Roku can natively display these subtitles, ensure the caption mode on the device is on
|
||||||
m.view.globalCaptionMode = "On"
|
m.view.globalCaptionMode = "On"
|
||||||
end if
|
|
||||||
|
|
||||||
if m.selectedSubtitle.IsExternal
|
' Roku may rearrange subtitle tracks. Look up track based on name to ensure we get the correct index
|
||||||
availableSubtitleTrackIndex = availSubtitleTrackIdx(m.selectedSubtitle.Track.TrackName)
|
availableSubtitleTrackIndex = availSubtitleTrackIdx(m.selectedSubtitle.Track.TrackName)
|
||||||
if availableSubtitleTrackIndex = -1 then return
|
if availableSubtitleTrackIndex = -1 then return
|
||||||
|
|
||||||
m.view.subtitleTrack = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
|
m.view.subtitleTrack = m.view.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
|
||||||
else
|
|
||||||
m.view.selectedSubtitle = m.selectedSubtitle.Index
|
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
m.view.selectedSubtitle = m.selectedSubtitle.Index
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
' User requested playback info
|
' User requested playback info
|
||||||
|
|
|
@ -93,6 +93,8 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||||
focusedItem = getFocusedItem()
|
focusedItem = getFocusedItem()
|
||||||
if isValid(focusedItem)
|
if isValid(focusedItem)
|
||||||
m.top.selectedItem = focusedItem
|
m.top.selectedItem = focusedItem
|
||||||
|
'Prevent the selected item event from double firing
|
||||||
|
m.top.selectedItem = invalid
|
||||||
end if
|
end if
|
||||||
return true
|
return true
|
||||||
end if
|
end if
|
||||||
|
|
|
@ -256,6 +256,9 @@ end sub
|
||||||
|
|
||||||
' Event handler for when selectedSubtitle changes
|
' Event handler for when selectedSubtitle changes
|
||||||
sub onSubtitleChange()
|
sub onSubtitleChange()
|
||||||
|
' If the global caption mode is on, that means Roku can display the subtitles natively and doesn't need a video stop/start
|
||||||
|
if LCase(m.top.globalCaptionMode) = "on" then return
|
||||||
|
|
||||||
' Save the current video position
|
' Save the current video position
|
||||||
m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&)
|
m.global.queueManager.callFunc("setTopStartingPoint", int(m.top.position) * 10000000&)
|
||||||
|
|
||||||
|
@ -332,6 +335,29 @@ sub onVideoContentLoaded()
|
||||||
m.top.allowCaptions = true
|
m.top.allowCaptions = true
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
' Allow default subtitles
|
||||||
|
m.top.unobserveField("selectedSubtitle")
|
||||||
|
|
||||||
|
' Set subtitleTrack property is subs are natively supported by Roku
|
||||||
|
selectedSubtitle = invalid
|
||||||
|
for each subtitle in m.top.fullSubtitleData
|
||||||
|
if subtitle.Index = videoContent[0].selectedSubtitle
|
||||||
|
selectedSubtitle = subtitle
|
||||||
|
exit for
|
||||||
|
end if
|
||||||
|
end for
|
||||||
|
|
||||||
|
if isValid(selectedSubtitle)
|
||||||
|
availableSubtitleTrackIndex = availSubtitleTrackIdx(selectedSubtitle.Track.TrackName)
|
||||||
|
if availableSubtitleTrackIndex <> -1
|
||||||
|
m.top.subtitleTrack = m.top.availableSubtitleTracks[availableSubtitleTrackIndex].TrackName
|
||||||
|
end if
|
||||||
|
end if
|
||||||
|
|
||||||
|
m.top.selectedSubtitle = videoContent[0].selectedSubtitle
|
||||||
|
|
||||||
|
m.top.observeField("selectedSubtitle", "onSubtitleChange")
|
||||||
|
|
||||||
if isValid(m.top.audioIndex)
|
if isValid(m.top.audioIndex)
|
||||||
m.top.audioTrack = (m.top.audioIndex + 1).toStr()
|
m.top.audioTrack = (m.top.audioIndex + 1).toStr()
|
||||||
else
|
else
|
||||||
|
@ -579,6 +605,25 @@ function stateAllowsOSD() as boolean
|
||||||
return inArray(validStates, m.top.state)
|
return inArray(validStates, m.top.state)
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
||||||
|
' availSubtitleTrackIdx: Returns Roku's index for requested subtitle track
|
||||||
|
'
|
||||||
|
' @param {string} tracknameToFind - TrackName for subtitle we're looking to match
|
||||||
|
' @return {integer} indicating Roku's index for requested subtitle track. Returns -1 if not found
|
||||||
|
function availSubtitleTrackIdx(tracknameToFind as string) as integer
|
||||||
|
idx = 0
|
||||||
|
for each availTrack in m.top.availableSubtitleTracks
|
||||||
|
' The TrackName must contain the URL we supplied originally, though
|
||||||
|
' Roku mangles the name a bit, so we check if the URL is a substring, rather
|
||||||
|
' than strict equality
|
||||||
|
if Instr(1, availTrack.TrackName, tracknameToFind)
|
||||||
|
return idx
|
||||||
|
end if
|
||||||
|
idx = idx + 1
|
||||||
|
end for
|
||||||
|
return -1
|
||||||
|
end function
|
||||||
|
|
||||||
function onKeyEvent(key as string, press as boolean) as boolean
|
function onKeyEvent(key as string, press as boolean) as boolean
|
||||||
|
|
||||||
' Keypress handler while user is inside the chapter menu
|
' Keypress handler while user is inside the chapter menu
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<field id="selectPlaybackInfoPressed" type="boolean" alwaysNotify="true" />
|
<field id="selectPlaybackInfoPressed" type="boolean" alwaysNotify="true" />
|
||||||
<field id="PlaySessionId" type="string" />
|
<field id="PlaySessionId" type="string" />
|
||||||
<field id="Subtitles" type="array" />
|
<field id="Subtitles" type="array" />
|
||||||
<field id="SelectedSubtitle" type="integer" value="-1" alwaysNotify="true" />
|
<field id="SelectedSubtitle" type="integer" value="-2" alwaysNotify="true" />
|
||||||
<field id="container" type="string" />
|
<field id="container" type="string" />
|
||||||
<field id="directPlaySupported" type="boolean" />
|
<field id="directPlaySupported" type="boolean" />
|
||||||
<field id="systemOverlay" type="boolean" value="false" />
|
<field id="systemOverlay" type="boolean" value="false" />
|
||||||
|
|
2
manifest
2
manifest
|
@ -3,7 +3,7 @@
|
||||||
title=Jellyfin
|
title=Jellyfin
|
||||||
major_version=2
|
major_version=2
|
||||||
minor_version=0
|
minor_version=0
|
||||||
build_version=0
|
build_version=1
|
||||||
|
|
||||||
### Main Menu Icons / Channel Poster Artwork
|
### Main Menu Icons / Channel Poster Artwork
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "jellyfin-roku",
|
"name": "jellyfin-roku",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"description": "Roku app for Jellyfin media server",
|
"description": "Roku app for Jellyfin media server",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rokucommunity/bslib": "0.1.1",
|
"@rokucommunity/bslib": "0.1.1",
|
||||||
|
|
|
@ -811,7 +811,7 @@ function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string) as
|
||||||
group.objects = TVEpisodes(seriesID, seasonID)
|
group.objects = TVEpisodes(seriesID, seasonID)
|
||||||
group.episodeObjects = group.objects
|
group.episodeObjects = group.objects
|
||||||
' watch for button presses
|
' watch for button presses
|
||||||
group.observeField("episodeSelected", m.port)
|
group.observeField("selectedItem", m.port)
|
||||||
group.observeField("quickPlayNode", m.port)
|
group.observeField("quickPlayNode", m.port)
|
||||||
' don't wait for the extras button
|
' don't wait for the extras button
|
||||||
stopLoadingSpinner()
|
stopLoadingSpinner()
|
||||||
|
|
|
@ -71,11 +71,13 @@ sub runRegistryUserMigrations()
|
||||||
' app versions < 2.0.0 didn't save LastRunVersion at the user level
|
' app versions < 2.0.0 didn't save LastRunVersion at the user level
|
||||||
' fall back to using the apps lastRunVersion
|
' fall back to using the apps lastRunVersion
|
||||||
lastRunVersion = m.global.app.lastRunVersion
|
lastRunVersion = m.global.app.lastRunVersion
|
||||||
|
if isValid(lastRunVersion)
|
||||||
registry_write("LastRunVersion", lastRunVersion, section)
|
registry_write("LastRunVersion", lastRunVersion, section)
|
||||||
end if
|
end if
|
||||||
|
end if
|
||||||
|
|
||||||
' BASE_MIGRATION
|
' BASE_MIGRATION
|
||||||
if not versionChecker(lastRunVersion, CLIENT_VERSION_REQUIRING_BASE_MIGRATION)
|
if isValid(lastRunVersion) and not versionChecker(lastRunVersion, CLIENT_VERSION_REQUIRING_BASE_MIGRATION)
|
||||||
m.wasMigrated = true
|
m.wasMigrated = true
|
||||||
print `Running Registry Migration for ${CLIENT_VERSION_REQUIRING_BASE_MIGRATION} for userid: ${section}`
|
print `Running Registry Migration for ${CLIENT_VERSION_REQUIRING_BASE_MIGRATION} for userid: ${section}`
|
||||||
|
|
||||||
|
|
34
source/static/whatsNew/2.0.1.json
Normal file
34
source/static/whatsNew/2.0.1.json
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"description": "Fix startup crash",
|
||||||
|
"author": "cewert"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fix selection and display of subtitles that are not encoded",
|
||||||
|
"author": "1hitsong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Make music artist presentation views honor the Item Titles setting",
|
||||||
|
"author": "1hitsong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fix launching Live TV channels from outside the guide",
|
||||||
|
"author": "jimdogx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fix default subtitle track selection",
|
||||||
|
"author": "1hitsong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fix video playback for Roku devices running a version of Roku OS less than 12.0",
|
||||||
|
"author": "1hitsong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Fix selecting episode using OK button on episode list view",
|
||||||
|
"author": "1hitsong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Make GH jobs work with new branch workflow",
|
||||||
|
"author": "cewert"
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user