mirror of
https://github.com/jellyfin/jellyfin-roku.git
synced 2024-11-23 06:09:41 +00:00
Merge remote-tracking branch 'upstream/master' into cache-global
This commit is contained in:
commit
79d671d4f4
4
.github/workflows/build-dev.yml
vendored
4
.github/workflows/build-dev.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
@ -21,7 +21,7 @@ jobs:
|
||||
run: npm run ropm
|
||||
- name: Build app
|
||||
run: npm run build
|
||||
- uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
with:
|
||||
name: Jellyfin-Roku-dev-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/build/staging
|
||||
|
2
.github/workflows/build-docs.yml
vendored
2
.github/workflows/build-docs.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
# Give the default GITHUB_TOKEN write permission to commit and push the changed files back to the repository.
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
4
.github/workflows/build-prod.yml
vendored
4
.github/workflows/build-prod.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
if: ${{ github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'release-prep') }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
@ -23,7 +23,7 @@ jobs:
|
||||
run: npm run ropm
|
||||
- name: Build app for production
|
||||
run: npm run build-prod
|
||||
- uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4
|
||||
- uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4
|
||||
with:
|
||||
name: Jellyfin-Roku-v${{ env.newManVersion }}-${{ github.sha }}
|
||||
path: ${{ github.workspace }}/build/staging
|
||||
|
12
.github/workflows/bump-version.yml
vendored
12
.github/workflows/bump-version.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
- name: Install required packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
@ -50,7 +50,7 @@ jobs:
|
||||
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
|
||||
- name: Checkout bugfix branch
|
||||
if: github.event.inputs.targetBranch == 'bugfix'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
ref: ${{ env.targetBranch }}
|
||||
# Save old version again if needed
|
||||
@ -101,7 +101,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
- name: Install jq to update json
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
@ -125,7 +125,7 @@ jobs:
|
||||
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
|
||||
- name: Checkout bugfix branch
|
||||
if: github.event.inputs.targetBranch == 'bugfix'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
ref: ${{ env.targetBranch }}
|
||||
# Calculate new version
|
||||
@ -169,7 +169,7 @@ jobs:
|
||||
steps:
|
||||
# Setup
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
- name: Install jq to update json
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
@ -193,7 +193,7 @@ jobs:
|
||||
run: echo "targetBranch=${{ env.bugfixBranch }}" >> $GITHUB_ENV
|
||||
- name: Checkout bugfix branch
|
||||
if: github.event.inputs.targetBranch == 'bugfix'
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
with:
|
||||
ref: ${{ env.targetBranch }}
|
||||
# Calculate new version
|
||||
|
2
.github/workflows/deploy-api-docs.yml
vendored
2
.github/workflows/deploy-api-docs.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
|
||||
- name: Upload artifact
|
||||
|
2
.github/workflows/roku-analysis.yml
vendored
2
.github/workflows/roku-analysis.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
||||
if: github.repository == 'jellyfin/jellyfin-roku' && github.event_name != 'pull_request' || github.repository == 'jellyfin/jellyfin-roku' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4
|
||||
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
2
Makefile
2
Makefile
@ -3,7 +3,7 @@
|
||||
# If you want to get_images, you'll also need convert from ImageMagick
|
||||
##########################################################################
|
||||
|
||||
VERSION := 2.1.5
|
||||
VERSION := 2.1.8
|
||||
|
||||
## usage
|
||||
|
||||
|
@ -4,7 +4,7 @@ import "pkg:/source/utils/config.bs"
|
||||
sub init()
|
||||
m.itemPoster = m.top.findNode("itemPoster")
|
||||
m.posterText = m.top.findNode("posterText")
|
||||
m.title = m.top.findNode("title")
|
||||
initTitle()
|
||||
m.posterText.font.size = 30
|
||||
m.title.font.size = 25
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
@ -23,6 +23,10 @@ sub init()
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub initTitle()
|
||||
m.title = m.top.findNode("title")
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
m.backdrop.blendColor = "#101010"
|
||||
|
||||
@ -54,6 +58,8 @@ sub itemContentChanged()
|
||||
end sub
|
||||
|
||||
sub focusChanged()
|
||||
if not isValid(m.title) then initTitle()
|
||||
|
||||
if m.top.itemHasFocus = true
|
||||
m.title.repeatCount = -1
|
||||
else
|
||||
|
@ -6,6 +6,7 @@ import "pkg:/source/utils/config.bs"
|
||||
import "pkg:/source/api/Image.bs"
|
||||
import "pkg:/source/api/userauth.bs"
|
||||
import "pkg:/source/utils/deviceCapabilities.bs"
|
||||
import "pkg:/source/utils/session.bs"
|
||||
|
||||
enum SubtitleSelection
|
||||
notset = -2
|
||||
@ -73,8 +74,6 @@ end function
|
||||
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean)
|
||||
|
||||
meta = ItemMetaData(video.id)
|
||||
subtitle_idx = m.top.selectedSubtitleIndex
|
||||
|
||||
if not isValid(meta)
|
||||
video.errorMsg = "Error loading metadata"
|
||||
video.content = invalid
|
||||
@ -85,6 +84,20 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||
userSession = m.global.session.user
|
||||
userSettings = userSession.settings
|
||||
|
||||
session.video.Update(meta)
|
||||
|
||||
if isValid(meta.json.MediaSources[0].RunTimeTicks)
|
||||
if meta.json.MediaSources[0].RunTimeTicks = 0
|
||||
video.length = 0
|
||||
else
|
||||
video.length = meta.json.MediaSources[0].RunTimeTicks / 10000000
|
||||
end if
|
||||
end if
|
||||
if isValid(meta.json.MediaSources[0]) and isValid(meta.json.MediaSources[0].MediaStreams[0])
|
||||
video.MaxVideoDecodeResolution = [meta.json.MediaSources[0].MediaStreams[0].Width, meta.json.MediaSources[0].MediaStreams[0].Height]
|
||||
end if
|
||||
|
||||
subtitle_idx = m.top.selectedSubtitleIndex
|
||||
videotype = LCase(meta.type)
|
||||
|
||||
' Check for any Live TV streams or Recordings coming from other places other than the TV Guide
|
||||
@ -202,12 +215,10 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||
}
|
||||
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
|
||||
@ -357,11 +368,15 @@ sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
||||
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
||||
if protocol <> "file"
|
||||
uri = parseUrl(m.playbackInfo.MediaSources[0].Path)
|
||||
if isLocalhost(uri[2])
|
||||
if not isValidAndNotEmpty(uri) then return
|
||||
|
||||
if isValid(uri[2]) and isLocalhost(uri[2])
|
||||
' if the domain of the URI is local to the server,
|
||||
' create a new URI by appending the received path to the server URL
|
||||
' later we will substitute the users provided URL for this case
|
||||
video.content.url = buildURL(uri[4])
|
||||
if isValid(uri[4])
|
||||
video.content.url = buildURL(uri[4])
|
||||
end if
|
||||
else
|
||||
fully_external = true
|
||||
video.content.url = m.playbackInfo.MediaSources[0].Path
|
||||
|
@ -18,10 +18,8 @@ sub init()
|
||||
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
|
||||
end if
|
||||
|
||||
m.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"]
|
||||
m.posterText.visible = false
|
||||
m.postTextBackground.visible = false
|
||||
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
|
@ -1,7 +1,9 @@
|
||||
import "pkg:/source/utils/config.bs"
|
||||
import "pkg:/source/api/baserequest.bs"
|
||||
import "pkg:/source/roku_modules/log/LogMixin.brs"
|
||||
|
||||
sub init()
|
||||
m.log = log.Logger("captionTask")
|
||||
m.top.observeField("url", "fetchCaption")
|
||||
m.top.currentCaption = []
|
||||
m.top.currentPos = 0
|
||||
@ -41,17 +43,26 @@ sub setFont()
|
||||
end sub
|
||||
|
||||
sub fetchCaption()
|
||||
m.log.debug("start fetchCaption()")
|
||||
m.captionTimer.control = "stop"
|
||||
re = CreateObject("roRegex", "(http.*?\.vtt)", "s")
|
||||
url = re.match(m.top.url)[0]
|
||||
|
||||
if url <> invalid
|
||||
port = createObject("roMessagePort")
|
||||
m.reader.setUrl(url)
|
||||
text = m.reader.GetToString()
|
||||
m.captionList = parseVTT(text)
|
||||
m.captionTimer.control = "start"
|
||||
m.reader.setMessagePort(port)
|
||||
if m.reader.AsyncGetToString()
|
||||
msg = port.waitMessage(0)
|
||||
if type(msg) = "roUrlEvent"
|
||||
m.captionList = parseVTT(msg.GetString())
|
||||
m.captionTimer.control = "start"
|
||||
end if
|
||||
end if
|
||||
else
|
||||
m.captionTimer.control = "stop"
|
||||
end if
|
||||
m.log.debug("end fetchCaption()", url)
|
||||
end sub
|
||||
|
||||
function newlabel(txt)
|
||||
|
@ -4,9 +4,12 @@ import "pkg:/source/utils/config.bs"
|
||||
|
||||
sub setFields()
|
||||
json = m.top.json
|
||||
m.top.Type = "Person"
|
||||
|
||||
if json = invalid then return
|
||||
|
||||
m.top.id = json.id
|
||||
m.top.favorite = json.UserData.isFavorite
|
||||
m.top.Type = "Person"
|
||||
setPoster()
|
||||
end sub
|
||||
|
||||
|
@ -10,12 +10,12 @@ sub init()
|
||||
initItemPoster()
|
||||
m.itemProgress = m.top.findNode("progress")
|
||||
m.itemProgressBackground = m.top.findNode("progressBackground")
|
||||
m.itemIcon = m.top.findNode("itemIcon")
|
||||
initItemIcon()
|
||||
initItemTextExtra()
|
||||
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
|
||||
m.unplayedCount = m.top.findNode("unplayedCount")
|
||||
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
initPlayedIndicator()
|
||||
|
||||
m.showProgressBarAnimation = m.top.findNode("showProgressBar")
|
||||
m.showProgressBarField = m.top.findNode("showProgressBarField")
|
||||
@ -50,6 +50,14 @@ sub initBackdrop()
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
end sub
|
||||
|
||||
sub initItemIcon()
|
||||
m.itemIcon = m.top.findNode("itemIcon")
|
||||
end sub
|
||||
|
||||
sub initPlayedIndicator()
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
if isValid(m.unplayedCount) then m.unplayedCount.visible = false
|
||||
itemData = m.top.itemContent
|
||||
@ -63,6 +71,8 @@ sub itemContentChanged()
|
||||
if not isValid(m.itemText) then initItemText()
|
||||
if not isValid(m.itemTextExtra) then initItemTextExtra()
|
||||
if not isValid(m.backdrop) then initBackdrop()
|
||||
if not isValid(m.itemIcon) then initItemIcon()
|
||||
if not isValid(m.playedIndicator) then initPlayedIndicator()
|
||||
|
||||
m.itemPoster.width = itemData.imageWidth
|
||||
m.itemText.maxWidth = itemData.imageWidth
|
||||
@ -83,11 +93,14 @@ sub itemContentChanged()
|
||||
|
||||
if LCase(itemData.type) = "series"
|
||||
if isValid(userSettings)
|
||||
if not userSettings["ui.tvshows.disableUnwatchedEpisodeCount"]
|
||||
unwatchedEpisodeCountSetting = userSettings["ui.tvshows.disableUnwatchedEpisodeCount"]
|
||||
if isValid(unwatchedEpisodeCountSetting) and not unwatchedEpisodeCountSetting
|
||||
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||
if itemData.json.UserData.UnplayedItemCount > 0
|
||||
if isValid(m.unplayedCount) then m.unplayedCount.visible = true
|
||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||
if isValid(m.unplayedEpisodeCount)
|
||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
@ -28,7 +28,9 @@ sub itemContentChanged()
|
||||
item.selectedVideoStreamId = itemData.MediaSources[0].id
|
||||
end if
|
||||
|
||||
if isValid(itemData.indexNumber)
|
||||
if isValid(itemData.parentIndexNumber) and itemData.parentIndexNumber = 0
|
||||
indexNumber = `${tr("Special")} - `
|
||||
else if isValid(itemData.indexNumber)
|
||||
indexNumber = `${itemData.indexNumber}. `
|
||||
if isValid(itemData.indexNumberEnd)
|
||||
indexNumber = `${itemData.indexNumber}-${itemData.indexNumberEnd}. `
|
||||
|
@ -181,9 +181,12 @@ end function
|
||||
|
||||
sub onShuffleEpisodeDataLoaded()
|
||||
m.getShuffleEpisodesTask.unobserveField("data")
|
||||
queueManager = m.global.queueManager
|
||||
queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items)
|
||||
queueManager.callFunc("playQueue")
|
||||
|
||||
if isValid(m.getShuffleEpisodesTask.data)
|
||||
queueManager = m.global.queueManager
|
||||
queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items)
|
||||
queueManager.callFunc("playQueue")
|
||||
end if
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
@ -1,5 +1,6 @@
|
||||
import "pkg:/source/utils/misc.bs"
|
||||
import "pkg:/source/utils/config.bs"
|
||||
import "pkg:/source/utils/session.bs"
|
||||
import "pkg:/source/roku_modules/log/LogMixin.brs"
|
||||
|
||||
sub init()
|
||||
@ -105,6 +106,7 @@ sub handleItemSkipAction(action as string)
|
||||
' If there is something next in the queue, play it
|
||||
if queueManager.callFunc("getPosition") < queueManager.callFunc("getCount") - 1
|
||||
m.top.control = "stop"
|
||||
session.video.Delete()
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
queueManager.callFunc("moveForward")
|
||||
queueManager.callFunc("playQueue")
|
||||
@ -119,6 +121,7 @@ sub handleItemSkipAction(action as string)
|
||||
' If there is something previous in the queue, play it
|
||||
if queueManager.callFunc("getPosition") > 0
|
||||
m.top.control = "stop"
|
||||
session.video.Delete()
|
||||
m.global.sceneManager.callFunc("clearPreviousScene")
|
||||
queueManager.callFunc("moveBack")
|
||||
queueManager.callFunc("playQueue")
|
||||
@ -617,19 +620,12 @@ sub onState(msg)
|
||||
' Pass video state into OSD
|
||||
m.osd.playbackState = m.top.state
|
||||
|
||||
' When buffering, start timer to monitor buffering process
|
||||
if m.top.state = "buffering"
|
||||
' start buffer timer
|
||||
' When buffering, start timer to monitor buffering process
|
||||
if isValid(m.bufferCheckTimer)
|
||||
m.bufferCheckTimer.control = "start"
|
||||
m.bufferCheckTimer.ObserveField("fire", "bufferCheck")
|
||||
end if
|
||||
|
||||
' update server if needed
|
||||
if not m.playReported
|
||||
m.playReported = true
|
||||
ReportPlayback("start")
|
||||
end if
|
||||
else if m.top.state = "error"
|
||||
m.log.error(m.top.errorCode, m.top.errorMsg, m.top.errorStr, m.top.errorCode)
|
||||
|
||||
@ -640,10 +636,10 @@ sub onState(msg)
|
||||
else
|
||||
' If an error was encountered, Display dialog
|
||||
showPlaybackErrorDialog(tr("Error During Playback"))
|
||||
session.video.Delete()
|
||||
end if
|
||||
|
||||
' Stop playback and exit player
|
||||
m.top.control = "stop"
|
||||
|
||||
else if m.top.state = "playing"
|
||||
|
||||
' Check if next episode is available
|
||||
@ -669,9 +665,11 @@ sub onState(msg)
|
||||
m.playbackTimer.control = "stop"
|
||||
ReportPlayback("stop")
|
||||
m.playReported = false
|
||||
session.video.Delete()
|
||||
else if m.top.state = "finished"
|
||||
m.playbackTimer.control = "stop"
|
||||
ReportPlayback("finished")
|
||||
session.video.Delete()
|
||||
else
|
||||
m.log.warning("Unhandled state", m.top.state, m.playReported, m.playFinished)
|
||||
end if
|
||||
@ -730,6 +728,7 @@ sub bufferCheck(msg)
|
||||
|
||||
' Stop playback and exit player
|
||||
m.top.control = "stop"
|
||||
session.video.Delete()
|
||||
end if
|
||||
end if
|
||||
|
||||
@ -799,6 +798,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
if key = "OK" and m.nextEpisodeButton.hasfocus() and not m.top.trickPlayBar.visible
|
||||
m.top.control = "stop"
|
||||
m.top.state = "finished"
|
||||
session.video.Delete()
|
||||
hideNextEpisodeButton()
|
||||
return true
|
||||
else
|
||||
@ -873,6 +873,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
||||
if key = "back"
|
||||
m.top.control = "stop"
|
||||
session.video.Delete()
|
||||
end if
|
||||
|
||||
return false
|
||||
|
@ -6,7 +6,7 @@ import "pkg:/source/utils/config.bs"
|
||||
sub init()
|
||||
m.itemPoster = m.top.findNode("itemPoster")
|
||||
m.posterText = m.top.findNode("posterText")
|
||||
m.title = m.top.findNode("title")
|
||||
initTitle()
|
||||
m.posterText.font.size = 30
|
||||
m.title.font.size = 25
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
@ -25,6 +25,10 @@ sub init()
|
||||
end if
|
||||
end sub
|
||||
|
||||
sub initTitle()
|
||||
m.title = m.top.findNode("title")
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
m.backdrop.blendColor = "#101010"
|
||||
|
||||
@ -56,6 +60,8 @@ sub itemContentChanged()
|
||||
end sub
|
||||
|
||||
sub focusChanged()
|
||||
if not isValid(m.title) then initTitle()
|
||||
|
||||
if m.top.itemHasFocus = true
|
||||
m.title.repeatCount = -1
|
||||
else
|
||||
|
@ -8,6 +8,7 @@ import "pkg:/source/utils/config.bs"
|
||||
import "pkg:/source/api/Image.bs"
|
||||
import "pkg:/source/api/userauth.bs"
|
||||
import "pkg:/source/utils/deviceCapabilities.bs"
|
||||
import "pkg:/source/utils/session.bs"
|
||||
|
||||
enum SubtitleSelection
|
||||
notset = -2
|
||||
@ -73,14 +74,26 @@ end function
|
||||
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean)
|
||||
|
||||
meta = ItemMetaData(video.id)
|
||||
subtitle_idx = m.top.selectedSubtitleIndex
|
||||
|
||||
if not isValid(meta)
|
||||
video.errorMsg = "Error loading metadata"
|
||||
video.content = invalid
|
||||
return
|
||||
end if
|
||||
|
||||
session.video.Update(meta)
|
||||
|
||||
if isValid(meta.json.MediaSources[0].RunTimeTicks)
|
||||
if meta.json.MediaSources[0].RunTimeTicks = 0
|
||||
video.length = 0
|
||||
else
|
||||
video.length = meta.json.MediaSources[0].RunTimeTicks / 10000000
|
||||
end if
|
||||
end if
|
||||
if isValid(meta.json.MediaSources[0]) and isValid(meta.json.MediaSources[0].MediaStreams[0])
|
||||
video.MaxVideoDecodeResolution = [meta.json.MediaSources[0].MediaStreams[0].Width, meta.json.MediaSources[0].MediaStreams[0].Height]
|
||||
end if
|
||||
|
||||
subtitle_idx = m.top.selectedSubtitleIndex
|
||||
videotype = LCase(meta.type)
|
||||
|
||||
' Check for any Live TV streams or Recordings coming from other places other than the TV Guide
|
||||
@ -199,12 +212,10 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
|
||||
}
|
||||
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
|
||||
@ -353,11 +364,15 @@ sub addVideoContentURL(video, mediaSourceId, audio_stream_idx, fully_external)
|
||||
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
||||
if protocol <> "file"
|
||||
uri = parseUrl(m.playbackInfo.MediaSources[0].Path)
|
||||
if isLocalhost(uri[2])
|
||||
if not isValidAndNotEmpty(uri) then return
|
||||
|
||||
if isValid(uri[2]) and isLocalhost(uri[2])
|
||||
' if the domain of the URI is local to the server,
|
||||
' create a new URI by appending the received path to the server URL
|
||||
' later we will substitute the users provided URL for this case
|
||||
video.content.url = buildURL(uri[4])
|
||||
if isValid(uri[4])
|
||||
video.content.url = buildURL(uri[4])
|
||||
end if
|
||||
else
|
||||
fully_external = true
|
||||
video.content.url = m.playbackInfo.MediaSources[0].Path
|
||||
|
@ -20,10 +20,8 @@ sub init()
|
||||
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
|
||||
end if
|
||||
|
||||
m.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"]
|
||||
m.posterText.visible = false
|
||||
m.postTextBackground.visible = false
|
||||
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
|
File diff suppressed because one or more lines are too long
@ -6,9 +6,12 @@ import "pkg:/source/utils/config.bs"
|
||||
|
||||
sub setFields()
|
||||
json = m.top.json
|
||||
m.top.Type = "Person"
|
||||
|
||||
if json = invalid then return
|
||||
|
||||
m.top.id = json.id
|
||||
m.top.favorite = json.UserData.isFavorite
|
||||
m.top.Type = "Person"
|
||||
setPoster()
|
||||
end sub
|
||||
|
||||
|
@ -12,12 +12,12 @@ sub init()
|
||||
initItemPoster()
|
||||
m.itemProgress = m.top.findNode("progress")
|
||||
m.itemProgressBackground = m.top.findNode("progressBackground")
|
||||
m.itemIcon = m.top.findNode("itemIcon")
|
||||
initItemIcon()
|
||||
initItemTextExtra()
|
||||
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
|
||||
m.unplayedCount = m.top.findNode("unplayedCount")
|
||||
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
initPlayedIndicator()
|
||||
|
||||
m.showProgressBarAnimation = m.top.findNode("showProgressBar")
|
||||
m.showProgressBarField = m.top.findNode("showProgressBarField")
|
||||
@ -52,6 +52,14 @@ sub initBackdrop()
|
||||
m.backdrop = m.top.findNode("backdrop")
|
||||
end sub
|
||||
|
||||
sub initItemIcon()
|
||||
m.itemIcon = m.top.findNode("itemIcon")
|
||||
end sub
|
||||
|
||||
sub initPlayedIndicator()
|
||||
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||
end sub
|
||||
|
||||
sub itemContentChanged()
|
||||
if isValid(m.unplayedCount) then m.unplayedCount.visible = false
|
||||
itemData = m.top.itemContent
|
||||
@ -65,6 +73,8 @@ sub itemContentChanged()
|
||||
if not isValid(m.itemText) then initItemText()
|
||||
if not isValid(m.itemTextExtra) then initItemTextExtra()
|
||||
if not isValid(m.backdrop) then initBackdrop()
|
||||
if not isValid(m.itemIcon) then initItemIcon()
|
||||
if not isValid(m.playedIndicator) then initPlayedIndicator()
|
||||
|
||||
m.itemPoster.width = itemData.imageWidth
|
||||
m.itemText.maxWidth = itemData.imageWidth
|
||||
@ -85,11 +95,14 @@ sub itemContentChanged()
|
||||
|
||||
if LCase(itemData.type) = "series"
|
||||
if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings)
|
||||
if not localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"]
|
||||
unwatchedEpisodeCountSetting = localGlobal.session.user.settings["ui.tvshows.disableUnwatchedEpisodeCount"]
|
||||
if isValid(unwatchedEpisodeCountSetting) and not unwatchedEpisodeCountSetting
|
||||
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||
if itemData.json.UserData.UnplayedItemCount > 0
|
||||
if isValid(m.unplayedCount) then m.unplayedCount.visible = true
|
||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||
if isValid(m.unplayedEpisodeCount)
|
||||
m.unplayedEpisodeCount.text = itemData.json.UserData.UnplayedItemCount
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
@ -29,7 +29,9 @@ sub itemContentChanged()
|
||||
item.selectedVideoStreamId = itemData.MediaSources[0].id
|
||||
end if
|
||||
|
||||
if isValid(itemData.indexNumber)
|
||||
if isValid(itemData.parentIndexNumber) and itemData.parentIndexNumber = 0
|
||||
indexNumber = `${tr("Special")} - `
|
||||
else if isValid(itemData.indexNumber)
|
||||
indexNumber = `${itemData.indexNumber}. `
|
||||
if isValid(itemData.indexNumberEnd)
|
||||
indexNumber = `${itemData.indexNumber}-${itemData.indexNumberEnd}. `
|
||||
|
@ -183,8 +183,10 @@ end function
|
||||
|
||||
sub onShuffleEpisodeDataLoaded()
|
||||
m.getShuffleEpisodesTask.unobserveField("data")
|
||||
m.global.queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
if isValid(m.getShuffleEpisodesTask.data)
|
||||
m.global.queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
end if
|
||||
end sub
|
||||
|
||||
function onKeyEvent(key as string, press as boolean) as boolean
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -226,8 +226,12 @@
|
||||
' Find the object in the scene's data and update its json data
|
||||
for i = 0 to currentScene.objects.Items.count() - 1
|
||||
if LCase(currentScene.objects.Items[i].id) = LCase(currentEpisode.id)
|
||||
currentScene.objects.Items[i].json = api.users.GetItem(m.global.session.user.id, currentEpisode.id)
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", currentScene.objects.Items[i].json.UserData.PlaybackPositionTicks)
|
||||
|
||||
data = api.users.GetItem(m.global.session.user.id, currentEpisode.id)
|
||||
if isValid(data)
|
||||
currentScene.objects.Items[i].json = data
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", data.UserData.PlaybackPositionTicks)
|
||||
end if
|
||||
exit for
|
||||
end if
|
||||
end for
|
||||
@ -246,15 +250,19 @@
|
||||
currentScene = m.global.sceneManager.callFunc("getActiveScene")
|
||||
|
||||
if isValid(currentScene) and isValid(currentScene.itemContent) and isValid(currentScene.itemContent.id)
|
||||
' Refresh movie detail data
|
||||
currentScene.itemContent.json = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id)
|
||||
movieMetaData = ItemMetaData(currentScene.itemContent.id)
|
||||
data = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id)
|
||||
if isValid(data)
|
||||
currentScene.itemContent.json = data
|
||||
' Set updated starting point for the queue item
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", data.UserData.PlaybackPositionTicks)
|
||||
|
||||
' Redraw movie poster
|
||||
currentScene.newPosterImageURI = movieMetaData.posterURL
|
||||
|
||||
' Set updated starting point for the queue item
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", currentScene.itemContent.json.UserData.PlaybackPositionTicks)
|
||||
' Refresh movie detail data
|
||||
movieMetaData = ItemMetaData(currentScene.itemContent.id)
|
||||
if isValid(movieMetaData)
|
||||
' Redraw movie poster
|
||||
currentScene.newPosterImageURI = movieMetaData.posterURL
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
stopLoadingSpinner()
|
||||
@ -579,36 +587,41 @@
|
||||
' If a button is selected, we have some determining to do
|
||||
btn = getButton(msg)
|
||||
group = sceneManager.callFunc("getActiveScene")
|
||||
|
||||
if isValid(btn) and btn.id = "play-button"
|
||||
if not isValid(group) then return
|
||||
|
||||
' User chose Play button from movie detail view
|
||||
startLoadingSpinner()
|
||||
' Check if a specific Audio Stream was selected
|
||||
audio_stream_idx = 0
|
||||
if isValid(group) and isValid(group.selectedAudioStreamIndex)
|
||||
if isValid(group.selectedAudioStreamIndex)
|
||||
audio_stream_idx = group.selectedAudioStreamIndex
|
||||
end if
|
||||
|
||||
group.itemContent.selectedAudioStreamIndex = audio_stream_idx
|
||||
group.itemContent.id = group.selectedVideoStreamId
|
||||
if isValid(group.itemContent)
|
||||
group.itemContent.selectedAudioStreamIndex = audio_stream_idx
|
||||
group.itemContent.id = group.selectedVideoStreamId
|
||||
|
||||
' Display playback options dialog
|
||||
if group.itemContent.json.userdata.PlaybackPositionTicks > 0
|
||||
m.global.queueManager.callFunc("hold", group.itemContent)
|
||||
playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json)
|
||||
else
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("push", group.itemContent)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
' Display playback options dialog
|
||||
if group.itemContent.json.userdata.PlaybackPositionTicks > 0
|
||||
m.global.queueManager.callFunc("hold", group.itemContent)
|
||||
playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json)
|
||||
else
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("push", group.itemContent)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
end if
|
||||
end if
|
||||
|
||||
if isValid(group) and isValid(group.lastFocus) and isValid(group.lastFocus.id) and group.lastFocus.id = "main_group"
|
||||
if isValid(group.lastFocus) and isValid(group.lastFocus.id) and group.lastFocus.id = "main_group"
|
||||
buttons = group.findNode("buttons")
|
||||
if isValid(buttons)
|
||||
group.lastFocus = group.findNode("buttons")
|
||||
end if
|
||||
end if
|
||||
|
||||
if isValid(group) and isValid(group.lastFocus)
|
||||
if isValid(group.lastFocus)
|
||||
group.lastFocus.setFocus(true)
|
||||
end if
|
||||
|
||||
|
@ -265,16 +265,20 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx
|
||||
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
||||
if protocol <> "file"
|
||||
uri = parseUrl(m.playbackInfo.MediaSources[0].Path)
|
||||
if isLocalhost(uri[2])
|
||||
if not isValidAndNotEmpty(uri) then return
|
||||
|
||||
if isValid(uri[2]) and isLocalhost(uri[2])
|
||||
' 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])
|
||||
if isValid(uri[4])
|
||||
video.content.url = buildURL(uri[4])
|
||||
end if
|
||||
else
|
||||
fully_external = true
|
||||
video.content.url = m.playbackInfo.MediaSources[0].Path
|
||||
end if
|
||||
else:
|
||||
else
|
||||
params.append({
|
||||
"Static": "true",
|
||||
"Container": video.container,
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -99,11 +99,8 @@ sub SaveDeviceToGlobal()
|
||||
print "ERROR parsing deviceInfo.GetVideoMode()"
|
||||
end if
|
||||
videoWidth = heightToWidth[videoHeight]
|
||||
if videoHeight = "2160" and extraData = "b10"
|
||||
bitDepth = 10
|
||||
else if videoHeight = "4320"
|
||||
bitDepth = 12
|
||||
end if
|
||||
if extraData <> invalid and extraData = "b10" then bitDepth = 10
|
||||
if videoHeight = "4320" then bitDepth = 12
|
||||
|
||||
m.global.addFields({
|
||||
device: {
|
||||
|
@ -1314,6 +1314,14 @@
|
||||
<translation>Use Show Image</translation>
|
||||
<extracomment>User Setting - Setting option title</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Play Next Episode Automatically</source>
|
||||
<translation>Play Next Episode Automatically</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>When finished playing a single episode, play the next one automatically.</source>
|
||||
<translation>When finished playing a single episode, play the next one automatically.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name></name>
|
||||
|
@ -1321,5 +1321,10 @@
|
||||
<translation>Use Show Image</translation>
|
||||
<extracomment>User Setting - Setting option title</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Special</source>
|
||||
<translation>Special</translation>
|
||||
<extracomment>Special episode of a TV Show</extracomment>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
@ -106,7 +106,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Latest in</source>
|
||||
<translation type="unfinished">Dernières entrées</translation>
|
||||
<translation>Dernières entrées</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Home</source>
|
||||
@ -195,7 +195,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Extras</source>
|
||||
<translation type="unfinished">Bonus</translation>
|
||||
<translation>Bonus</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>There was an error retrieving the data for this item from the server.</source>
|
||||
@ -302,7 +302,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>CRITIC_RATING</source>
|
||||
<translation type="unfinished">Note des critiques</translation>
|
||||
<translation>Note des critiques</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>DATE_PLAYED</source>
|
||||
@ -437,7 +437,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>View Channel</source>
|
||||
<translation type="unfinished">Afficher la chaîne</translation>
|
||||
<translation>Afficher la chaîne</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Record</source>
|
||||
@ -730,7 +730,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Hides tagline text on details pages.</source>
|
||||
<translation type="unfinished">Masque le texte de l'accroche sur les pages de détails.</translation>
|
||||
<translation>Masque le texte de l'accroche sur les pages de détails.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Options for TV Shows.</source>
|
||||
@ -744,7 +744,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>Attempt Direct Play for H.264 media with unsupported profile levels before falling back to transcoding if it fails.</source>
|
||||
<translation type="unfinished">Essayer Lecture directe pour les médias en H.264 avec des profils non supportés avant de se tourner vers la conversion si ça échoue.</translation>
|
||||
<translation>Essayer Lecture directe pour les médias en H.264 avec des profils non supportés avant de se tourner vers la conversion si ça échoue.</translation>
|
||||
<extracomment>Settings Menu - Description for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
@ -774,6 +774,302 @@
|
||||
<translation>Sauter les détails pour les saisons uniques</translation>
|
||||
<extracomment>Settings Menu - Title for option</extracomment>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show On Hover</source>
|
||||
<translation>Afficher au survol</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Years</source>
|
||||
<translation>Années</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always Hide</source>
|
||||
<translation>Toujours masquer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Album Artists (Presentation)</source>
|
||||
<translation>Artistes de l'album (Diaporama)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Limit</source>
|
||||
<translation>Activer la limite</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings that apply when Grid views are enabled.</source>
|
||||
<translation>Paramètres s'appliquant lorsque les vues en grille sont activées.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Custom Subtitles</source>
|
||||
<translation>Sous-titres Personnalisés</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show What's New popup when Jellyfin is updated to a new version.</source>
|
||||
<translation>Afficher la fenêtre contextuelle des nouveautés lorsque que Jellyfin est mise à jour avec la dernière version.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Select when to show titles.</source>
|
||||
<translation>Sélectionner quand afficher les titres.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Transcoding Information</source>
|
||||
<translation>Informations de Transcodage</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Audio Channels</source>
|
||||
<translation>Canaux Audio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Stream Information</source>
|
||||
<translation>Informations du flux</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec Tag</source>
|
||||
<translation>Balise de codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Songs</source>
|
||||
<translation>Chansons</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View All</source>
|
||||
<translation type="unfinished">Tout voir</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Configure the maximum playback bitrate.</source>
|
||||
<translation>Configurer le débit binaire maximum de lecture.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable or disable the 'Maximum Bitrate' setting.</source>
|
||||
<translation>Activer ou désactiver le paramètre 'Débit binaire maximal'.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set the maximum bitrate in Mbps. Set to 0 to use Roku's specifications. This setting must be enabled to take effect.</source>
|
||||
<translation>Définir le débit binaire maximum en Mbps. Régler sur 0 pour utiliser les spécifications de Roku. Ce paramètre doit être activé pour être pris en compte.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to the appearance of pages in TV Libraries.</source>
|
||||
<translation>Paramètres relatifs à l'apparence des pages dans la médiathèque séries TV.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>all</source>
|
||||
<translation>tout</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Default view for Movie Libraries.</source>
|
||||
<translation>Vue par défaut pour les médiathèques de films.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Attempt Direct Play for HEVC media with unsupported profile levels before falling back to transcoding if it fails.</source>
|
||||
<translation>Essayer la lecture directe des médias HEVC comportant des niveaux de profils incompatibles avant de revenir au transcodage en cas d'échec.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to find any albums or songs belonging to this artist</source>
|
||||
<translation>Aucun album ou chanson de cet artiste n'a pu être trouvé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Replace Roku's default subtitle functions with custom functions that support CJK fonts. Fallback fonts must be configured and enabled on the server for CJK rendering to work.</source>
|
||||
<translation>Remplacez les fonctions de sous-titres par défaut de Roku par des fonctions personnalisées prenant en charge les polices CJK. Les polices de secours doivent être configurées et activées sur le serveur pour que le rendu CJK fonctionne.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Off</source>
|
||||
<translation>Diaporama Désactivé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Parental Ratings</source>
|
||||
<translation>Classifications parentales</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Resumable</source>
|
||||
<translation>Reprise possible</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Movie Library Default View</source>
|
||||
<translation>Vue par défaut pour la médiathèque de films</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide the star and community rating for episodes of a TV show. This is to prevent spoilers of an upcoming good/bad episode.</source>
|
||||
<translation>Cacher l'étoile et la notation de la communauté pour les épisodes d'une série TV. Cela permet d'éviter les spoilers sur un bon/mauvais épisode à venir.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Maximum Bitrate</source>
|
||||
<translation>Débit binaire maximum</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bitrate Limit</source>
|
||||
<translation>Limite de débit binaire</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to the appearance of the Home screen and the program in general.</source>
|
||||
<translation>Paramètres relatifs à l'apparence de la page d'accueil et du programme en général.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Artists (Presentation)</source>
|
||||
<translation>Artistes (diaporama)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Artists (Grid)</source>
|
||||
<translation>Artistes (grille)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Song</source>
|
||||
<translation>Chanson</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disable Community Rating for Episodes</source>
|
||||
<translation>Désactiver la notation de la communauté pour les épisodes</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Max Days Next Up</source>
|
||||
<translation>Maximum de jours dans la section «À suivre»</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Video Codec</source>
|
||||
<translation>Codec vidéo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Audio Codec</source>
|
||||
<translation>Codec audio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>direct</source>
|
||||
<translation>direct</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Codec</source>
|
||||
<translation>Codec</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Level</source>
|
||||
<translation>Niveau</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Size</source>
|
||||
<translation>Taille</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Video range type</source>
|
||||
<translation>Type de plage vidéo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Text Subtitles Only</source>
|
||||
<translation>Sous-titres texte uniquement</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Aired</source>
|
||||
<translation>Diffusé le</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow On</source>
|
||||
<translation>Diaporama activé</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MPEG-4 Support</source>
|
||||
<translation>Support du MPEG-4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unplayed</source>
|
||||
<translation>Non lu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set the maximum amount of days a show should stay in the 'Next Up' list without watching it.</source>
|
||||
<translation>Définit le nombre maximal de jours qu'une série peut rester dans la section 'À suivre' si elle n'est plus regardée.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Total Bitrate</source>
|
||||
<translation>Débit Total</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Bit Rate</source>
|
||||
<translation>Débit binaire</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Container</source>
|
||||
<translation>Conteneur</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pixel format</source>
|
||||
<translation>Format de pixel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>WxH</source>
|
||||
<translation>L × H</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Random Off</source>
|
||||
<translation>Aléatoire désactivée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Playback Information</source>
|
||||
<translation>Informations de Lecture</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Reason</source>
|
||||
<translation>Raison</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Only display text subtitles to minimize transcoding.</source>
|
||||
<translation>Affiche seulement des sous-titres texte pour minimiser l'impact du transcodage.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Paused</source>
|
||||
<translation>Diaporama en Pause</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Random On</source>
|
||||
<translation>Aléatoire activée</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Slideshow Resumed</source>
|
||||
<translation>Reprise du diaporama</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show What's New Popup</source>
|
||||
<translation>Afficher la fenêtre contextuelle des nouveautés</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Played</source>
|
||||
<translation>Lu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Item Titles</source>
|
||||
<translation>Titres des éléments</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always Show</source>
|
||||
<translation>Toujours afficher</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Album</source>
|
||||
<translation type="unfinished">Album</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Album Artists (Grid)</source>
|
||||
<translation>Artistes de l'album (grille)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Albums</source>
|
||||
<translation>Albums</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Biographical information for this person is not currently available.</source>
|
||||
<translation>Les informations biographiques pour cette personne sont indisponibles pour le moment.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Libraries</source>
|
||||
<translation>Médiathèques</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Settings relating to the appearance of Library pages</source>
|
||||
<translation>Paramètres relatifs à l'apparence des pages de médiathèque</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>General</source>
|
||||
<translation>Général</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Grid View Settings</source>
|
||||
<translation>Paramètres de la vue Grille</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name></name>
|
||||
|
2
manifest
2
manifest
@ -3,7 +3,7 @@
|
||||
title=Jellyfin
|
||||
major_version=2
|
||||
minor_version=1
|
||||
build_version=5
|
||||
build_version=8
|
||||
|
||||
### Main Menu Icons / Channel Poster Artwork
|
||||
|
||||
|
447
package-lock.json
generated
447
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "jellyfin-roku",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jellyfin-roku",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.8",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-2.0",
|
||||
"dependencies": {
|
||||
@ -25,7 +25,7 @@
|
||||
"rimraf": "6.0.1",
|
||||
"roku-deploy": "3.12.1",
|
||||
"roku-log-bsc-plugin": "0.8.1",
|
||||
"rooibos-roku": "5.13.0",
|
||||
"rooibos-roku": "5.14.0",
|
||||
"ropm": "0.10.26",
|
||||
"spellchecker-cli": "6.2.0",
|
||||
"undent": "0.1.0"
|
||||
@ -494,6 +494,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/caseless": {
|
||||
"version": "0.12.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
|
||||
"integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
|
||||
@ -556,6 +563,26 @@
|
||||
"integrity": "sha512-zvSN2Esek1aeLdKDYuntKAYjti9Z2oT4I8bfkLLhIxHlv3dwZ5vvATxOc31820iYm4hQRCwjUgDpwSMFjfTUnw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/request": {
|
||||
"version": "2.48.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz",
|
||||
"integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/caseless": "*",
|
||||
"@types/node": "*",
|
||||
"@types/tough-cookie": "*",
|
||||
"form-data": "^2.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tough-cookie": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
@ -746,6 +773,16 @@
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@ -1417,6 +1454,13 @@
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.2.tgz",
|
||||
"integrity": "sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw=="
|
||||
},
|
||||
"node_modules/debounce": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
|
||||
"integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/debounce-promise": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz",
|
||||
@ -1694,6 +1738,15 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emitter-component": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.2.tgz",
|
||||
"integrity": "sha512-QdXO3nXOzZB4pAjM0n6ZE+R9/+kPpECA/XSELIcc54NeYVnBqIk+4DFiBgK+8QbV3mdvTG6nedl7dTYgO+5wDw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@ -1721,6 +1774,13 @@
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/eol": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz",
|
||||
"integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
@ -1857,6 +1917,26 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz",
|
||||
"integrity": "sha512-jPrupTOe/pO//3a9Ty2o4NqQCp0L46UG+swUnfFtdmtQVN8pEltKpAqR7Nuf6vWn0GBXx5w+R1MyZzqwjEIqdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"traverse-chain": "~0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-in-files": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/find-in-files/-/find-in-files-0.5.0.tgz",
|
||||
"integrity": "sha512-VraTc6HdtdSHmAp0yJpAy20yPttGKzyBWc7b7FPnnsX9TOgmKx0g9xajizpF/iuu4IvNK4TP0SpyBT9zAlwG+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"find": "^0.1.5",
|
||||
"q": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/find-replace": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz",
|
||||
@ -1905,6 +1985,21 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
|
||||
"integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.6",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/format": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
|
||||
@ -3722,6 +3817,23 @@
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/natural-orderby": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz",
|
||||
"integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/net": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz",
|
||||
"integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nlcst-affix-emoticon-modifier": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nlcst-affix-emoticon-modifier/-/nlcst-affix-emoticon-modifier-2.1.1.tgz",
|
||||
@ -4247,6 +4359,31 @@
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder": {
|
||||
"version": "1.0.32",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
|
||||
"integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^2.6.4",
|
||||
"debug": "^3.2.7",
|
||||
"mkdirp": "^0.5.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/portfinder/node_modules/debug": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
|
||||
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/postman-request": {
|
||||
"version": "2.88.1-postman.32",
|
||||
"resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.32.tgz",
|
||||
@ -4341,6 +4478,18 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/q": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
"integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
|
||||
"deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6.0",
|
||||
"teleport": ">=0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz",
|
||||
@ -4538,6 +4687,74 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/replace-in-file": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-6.3.5.tgz",
|
||||
"integrity": "sha512-arB9d3ENdKva2fxRnSjwBEXfK1npgyci7ZZuwysgAp7ORjHSyxz6oqIjTEv8R0Ydl4Ll7uOAZXL4vbkhGIizCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"glob": "^7.2.0",
|
||||
"yargs": "^17.2.1"
|
||||
},
|
||||
"bin": {
|
||||
"replace-in-file": "bin/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/replace-in-file/node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/replace-in-file/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/replace-in-file/node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/replace-last": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/replace-last/-/replace-last-1.2.6.tgz",
|
||||
"integrity": "sha512-Cj+MK38VtNu1S5J73mEZY3ciQb9dJajNq1Q8inP4dn/MhJMjHwoAF3Z3FjspwAEV9pfABl565MQucmrjOkty4g==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@ -4952,6 +5169,95 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/roku-debug": {
|
||||
"version": "0.21.11",
|
||||
"resolved": "https://registry.npmjs.org/roku-debug/-/roku-debug-0.21.11.tgz",
|
||||
"integrity": "sha512-OXFKmU91hefyWc5QKYz7bGYjWYntNo8/tOaPwYwDRQnNSYoXYCPViUjQV4VCpOFp7Xj7VJjJIFKwTK/BmvSAQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@rokucommunity/logger": "^0.3.9",
|
||||
"@types/request": "^2.48.8",
|
||||
"brighterscript": "^0.67.7",
|
||||
"dateformat": "^4.6.3",
|
||||
"debounce": "^1.2.1",
|
||||
"eol": "^0.9.1",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"fast-glob": "^3.2.11",
|
||||
"find-in-files": "^0.5.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"natural-orderby": "^2.0.3",
|
||||
"portfinder": "^1.0.32",
|
||||
"postman-request": "^2.88.1-postman.32",
|
||||
"replace-in-file": "^6.3.2",
|
||||
"replace-last": "^1.2.6",
|
||||
"roku-deploy": "^3.12.1",
|
||||
"semver": "^7.5.4",
|
||||
"serialize-error": "^8.1.0",
|
||||
"smart-buffer": "^4.2.0",
|
||||
"source-map": "^0.7.4",
|
||||
"telnet-client": "^1.4.9",
|
||||
"vscode-debugadapter": "^1.49.0",
|
||||
"vscode-debugprotocol": "^1.49.0",
|
||||
"vscode-languageserver": "^6.1.1",
|
||||
"xml2js": "^0.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"roku-debug": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/roku-debug/node_modules/dateformat": {
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
|
||||
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/roku-debug/node_modules/serialize-error": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz",
|
||||
"integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/roku-debug/node_modules/type-fest": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
|
||||
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
|
||||
"dev": true,
|
||||
"license": "(MIT OR CC0-1.0)",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/roku-debug/node_modules/vscode-languageserver": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-6.1.1.tgz",
|
||||
"integrity": "sha512-DueEpkUAkD5XTR4MLYNr6bQIp/UFR0/IPApgXU3YfCBCB08u2sm9hRCs6DxYZELkk++STPjpcjksR2H8qI3cDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-languageserver-protocol": "^3.15.3"
|
||||
},
|
||||
"bin": {
|
||||
"installServerIntoExtension": "bin/installServerIntoExtension"
|
||||
}
|
||||
},
|
||||
"node_modules/roku-deploy": {
|
||||
"version": "3.12.1",
|
||||
"resolved": "https://registry.npmjs.org/roku-deploy/-/roku-deploy-3.12.1.tgz",
|
||||
@ -5043,26 +5349,22 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/rooibos-roku": {
|
||||
"version": "5.13.0",
|
||||
"resolved": "https://registry.npmjs.org/rooibos-roku/-/rooibos-roku-5.13.0.tgz",
|
||||
"integrity": "sha512-tp+hlAtYVnzF3ZMatSmodGl990+pzKg0uwSqM8dyFBNn0Ob5PdUGFRv0oQKg3m7FYCjnglIQxiL97VpiT4rOMg==",
|
||||
"version": "5.14.0",
|
||||
"resolved": "https://registry.npmjs.org/rooibos-roku/-/rooibos-roku-5.14.0.tgz",
|
||||
"integrity": "sha512-eSAqO5J2IKYn08ZSFji/DGlD/7mX8wYD7/E0HgxQrWIFoxggR8e/o+8ljxnp/udcJ5aATbBgUi8xd89asyVmCQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"roku-debug": "^0.21.10",
|
||||
"roku-deploy": "^3.12.1",
|
||||
"source-map": "^0.7.3",
|
||||
"undent": "^0.1.0",
|
||||
"vscode-languageserver": "~6.1.1",
|
||||
"vscode-languageserver-protocol": "~3.15.3"
|
||||
}
|
||||
},
|
||||
"node_modules/rooibos-roku/node_modules/vscode-jsonrpc": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-5.0.1.tgz",
|
||||
"integrity": "sha512-JvONPptw3GAQGXlVV2utDcHx0BiY34FupW/kI6mZ5x06ER5DdPG/tXWMVHjTNULF5uKPOUUD0SaXg5QaubJL0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.0.0 || >=10.0.0"
|
||||
"vscode-languageserver-protocol": "~3.17.5",
|
||||
"yargs": "^16.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"rooibos": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/rooibos-roku/node_modules/vscode-languageserver": {
|
||||
@ -5078,24 +5380,25 @@
|
||||
"installServerIntoExtension": "bin/installServerIntoExtension"
|
||||
}
|
||||
},
|
||||
"node_modules/rooibos-roku/node_modules/vscode-languageserver-protocol": {
|
||||
"version": "3.15.3",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.3.tgz",
|
||||
"integrity": "sha512-zrMuwHOAQRhjDSnflWdJG+O2ztMWss8GqUUB8dXLR/FPenwkiBNkMIJJYfSN6sgskvsF0rHAoBowNQfbyZnnvw==",
|
||||
"node_modules/rooibos-roku/node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"vscode-jsonrpc": "^5.0.1",
|
||||
"vscode-languageserver-types": "3.15.1"
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/rooibos-roku/node_modules/vscode-languageserver-types": {
|
||||
"version": "3.15.1",
|
||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.15.1.tgz",
|
||||
"integrity": "sha512-+a9MPUQrNGRrGU630OGbYVQ+11iOIovjCkqxajPa9w57Sd5ruK8WQNsslzpa0x/QJqC8kRc2DUxWjIFwoNm4ZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ropm": {
|
||||
"version": "0.10.26",
|
||||
"resolved": "https://registry.npmjs.org/ropm/-/ropm-0.10.26.tgz",
|
||||
@ -5362,6 +5665,17 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
|
||||
@ -5471,6 +5785,16 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stream": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz",
|
||||
"integrity": "sha512-gCq3NDI2P35B2n6t76YJuOp7d6cN/C7Rt0577l91wllh0sY9ZBuw9KaSGqH/b0hzn3CWWJbpbW0W0WvQ1H/Q7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emitter-component": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/stream-length": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz",
|
||||
@ -5619,6 +5943,29 @@
|
||||
"integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/telnet-client": {
|
||||
"version": "1.4.11",
|
||||
"resolved": "https://registry.npmjs.org/telnet-client/-/telnet-client-1.4.11.tgz",
|
||||
"integrity": "sha512-m15pRh7F74ZzWmqjUtOg3SYp8iSnH7xY1lD9hWk8icjuLRSItABPk1vRJFwSM7op6TgZuEXOWOSbiK9nknloSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.4",
|
||||
"net": "^1.0.2",
|
||||
"stream": "^0.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/kozjak"
|
||||
}
|
||||
},
|
||||
"node_modules/telnet-client/node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/temp-dir": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
|
||||
@ -5668,6 +6015,13 @@
|
||||
"integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/traverse-chain": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/traverse-chain/-/traverse-chain-0.1.0.tgz",
|
||||
"integrity": "sha512-up6Yvai4PYKhpNp5PkYtx50m3KbwQrqDwbuZP/ItyL64YEWHAvH6Md83LFLV/GRSk/BoUVwwgUzX6SOQSbsfAg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/trough": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/trough/-/trough-2.1.0.tgz",
|
||||
@ -6090,6 +6444,39 @@
|
||||
"integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vscode-debugadapter": {
|
||||
"version": "1.51.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.51.0.tgz",
|
||||
"integrity": "sha512-mObaXD5/FH/z6aL2GDuyCLbnwLsYRCAJWgFid01vKW9Y5Si8OvINK+Tn+Yl/lRUbetjNuZW3j1euMEz6z8Yzqg==",
|
||||
"deprecated": "This package has been renamed to @vscode/debugadapter, please update to the new name",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"vscode-debugprotocol": "1.51.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-debugadapter/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-debugprotocol": {
|
||||
"version": "1.51.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.51.0.tgz",
|
||||
"integrity": "sha512-dzKWTMMyebIMPF1VYMuuQj7gGFq7guR8AFya0mKacu+ayptJfaRuM0mdHCqiOth4FnRP8mPhEroFPx6Ift8wHA==",
|
||||
"deprecated": "This package has been renamed to @vscode/debugprotocol, please update to the new name",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vscode-jsonrpc": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jellyfin-roku",
|
||||
"type": "module",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.8",
|
||||
"description": "Roku app for Jellyfin media server",
|
||||
"dependencies": {
|
||||
"@rokucommunity/bslib": "0.1.1",
|
||||
@ -19,7 +19,7 @@
|
||||
"rimraf": "6.0.1",
|
||||
"roku-deploy": "3.12.1",
|
||||
"roku-log-bsc-plugin": "0.8.1",
|
||||
"rooibos-roku": "5.13.0",
|
||||
"rooibos-roku": "5.14.0",
|
||||
"ropm": "0.10.26",
|
||||
"spellchecker-cli": "6.2.0",
|
||||
"undent": "0.1.0"
|
||||
|
@ -25,7 +25,7 @@
|
||||
"description": "Enable or disable the 'Maximum Bitrate' setting.",
|
||||
"settingName": "playback.bitrate.maxlimited",
|
||||
"type": "bool",
|
||||
"default": "true"
|
||||
"default": "false"
|
||||
},
|
||||
{
|
||||
"title": "Maximum Bitrate",
|
||||
|
@ -224,8 +224,12 @@ sub Main (args as dynamic) as void
|
||||
' Find the object in the scene's data and update its json data
|
||||
for i = 0 to currentScene.objects.Items.count() - 1
|
||||
if LCase(currentScene.objects.Items[i].id) = LCase(currentEpisode.id)
|
||||
currentScene.objects.Items[i].json = api.users.GetItem(m.global.session.user.id, currentEpisode.id)
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", currentScene.objects.Items[i].json.UserData.PlaybackPositionTicks)
|
||||
|
||||
data = api.users.GetItem(m.global.session.user.id, currentEpisode.id)
|
||||
if isValid(data)
|
||||
currentScene.objects.Items[i].json = data
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", data.UserData.PlaybackPositionTicks)
|
||||
end if
|
||||
exit for
|
||||
end if
|
||||
end for
|
||||
@ -244,15 +248,19 @@ sub Main (args as dynamic) as void
|
||||
currentScene = m.global.sceneManager.callFunc("getActiveScene")
|
||||
|
||||
if isValid(currentScene) and isValid(currentScene.itemContent) and isValid(currentScene.itemContent.id)
|
||||
' Refresh movie detail data
|
||||
currentScene.itemContent.json = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id)
|
||||
movieMetaData = ItemMetaData(currentScene.itemContent.id)
|
||||
data = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id)
|
||||
if isValid(data)
|
||||
currentScene.itemContent.json = data
|
||||
' Set updated starting point for the queue item
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", data.UserData.PlaybackPositionTicks)
|
||||
|
||||
' Redraw movie poster
|
||||
currentScene.newPosterImageURI = movieMetaData.posterURL
|
||||
|
||||
' Set updated starting point for the queue item
|
||||
m.global.queueManager.callFunc("setTopStartingPoint", currentScene.itemContent.json.UserData.PlaybackPositionTicks)
|
||||
' Refresh movie detail data
|
||||
movieMetaData = ItemMetaData(currentScene.itemContent.id)
|
||||
if isValid(movieMetaData)
|
||||
' Redraw movie poster
|
||||
currentScene.newPosterImageURI = movieMetaData.posterURL
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
stopLoadingSpinner()
|
||||
@ -577,36 +585,41 @@ sub Main (args as dynamic) as void
|
||||
' If a button is selected, we have some determining to do
|
||||
btn = getButton(msg)
|
||||
group = sceneManager.callFunc("getActiveScene")
|
||||
|
||||
if isValid(btn) and btn.id = "play-button"
|
||||
if not isValid(group) then return
|
||||
|
||||
' User chose Play button from movie detail view
|
||||
startLoadingSpinner()
|
||||
' Check if a specific Audio Stream was selected
|
||||
audio_stream_idx = 0
|
||||
if isValid(group) and isValid(group.selectedAudioStreamIndex)
|
||||
if isValid(group.selectedAudioStreamIndex)
|
||||
audio_stream_idx = group.selectedAudioStreamIndex
|
||||
end if
|
||||
|
||||
group.itemContent.selectedAudioStreamIndex = audio_stream_idx
|
||||
group.itemContent.id = group.selectedVideoStreamId
|
||||
if isValid(group.itemContent)
|
||||
group.itemContent.selectedAudioStreamIndex = audio_stream_idx
|
||||
group.itemContent.id = group.selectedVideoStreamId
|
||||
|
||||
' Display playback options dialog
|
||||
if group.itemContent.json.userdata.PlaybackPositionTicks > 0
|
||||
m.global.queueManager.callFunc("hold", group.itemContent)
|
||||
playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json)
|
||||
else
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("push", group.itemContent)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
' Display playback options dialog
|
||||
if group.itemContent.json.userdata.PlaybackPositionTicks > 0
|
||||
m.global.queueManager.callFunc("hold", group.itemContent)
|
||||
playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json)
|
||||
else
|
||||
m.global.queueManager.callFunc("clear")
|
||||
m.global.queueManager.callFunc("push", group.itemContent)
|
||||
m.global.queueManager.callFunc("playQueue")
|
||||
end if
|
||||
end if
|
||||
|
||||
if isValid(group) and isValid(group.lastFocus) and isValid(group.lastFocus.id) and group.lastFocus.id = "main_group"
|
||||
if isValid(group.lastFocus) and isValid(group.lastFocus.id) and group.lastFocus.id = "main_group"
|
||||
buttons = group.findNode("buttons")
|
||||
if isValid(buttons)
|
||||
group.lastFocus = group.findNode("buttons")
|
||||
end if
|
||||
end if
|
||||
|
||||
if isValid(group) and isValid(group.lastFocus)
|
||||
if isValid(group.lastFocus)
|
||||
group.lastFocus.setFocus(true)
|
||||
end if
|
||||
|
||||
|
@ -263,16 +263,20 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx
|
||||
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
|
||||
if protocol <> "file"
|
||||
uri = parseUrl(m.playbackInfo.MediaSources[0].Path)
|
||||
if isLocalhost(uri[2])
|
||||
if not isValidAndNotEmpty(uri) then return
|
||||
|
||||
if isValid(uri[2]) and isLocalhost(uri[2])
|
||||
' 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])
|
||||
if isValid(uri[4])
|
||||
video.content.url = buildURL(uri[4])
|
||||
end if
|
||||
else
|
||||
fully_external = true
|
||||
video.content.url = m.playbackInfo.MediaSources[0].Path
|
||||
end if
|
||||
else:
|
||||
else
|
||||
params.append({
|
||||
"Static": "true",
|
||||
"Container": video.container,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import "pkg:/source/api/sdk.bs"
|
||||
import "pkg:/source/utils/misc.bs"
|
||||
|
||||
function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger)
|
||||
params = {
|
||||
@ -13,9 +14,6 @@ function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger)
|
||||
end function
|
||||
|
||||
function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger)
|
||||
body = {
|
||||
"DeviceProfile": getDeviceProfile()
|
||||
}
|
||||
params = {
|
||||
"UserId": m.global.session.user.id,
|
||||
"StartTimeTicks": startTimeTicks,
|
||||
@ -25,6 +23,7 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT
|
||||
"MaxStaticBitrate": "140000000",
|
||||
"SubtitleStreamIndex": subtitleTrackIndex
|
||||
}
|
||||
deviceProfile = getDeviceProfile()
|
||||
|
||||
' Note: Jellyfin v10.9+ now remuxs LiveTV and does not allow DirectPlay anymore.
|
||||
' Because of this, we need to tell the server "EnableDirectPlay = false" so that we receive the
|
||||
@ -38,11 +37,39 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT
|
||||
params.EnableDirectPlay = false
|
||||
end if
|
||||
|
||||
if audioTrackIndex > -1 then params.AudioStreamIndex = audioTrackIndex
|
||||
myGLobal = m.global
|
||||
|
||||
if audioTrackIndex > -1 and myGLobal.session.video.json.MediaStreams <> invalid
|
||||
selectedAudioStream = myGLobal.session.video.json.MediaStreams[audioTrackIndex]
|
||||
|
||||
if selectedAudioStream <> invalid
|
||||
params.AudioStreamIndex = audioTrackIndex
|
||||
|
||||
' force the server to transcode AAC profiles we don't support to MP3 instead of the usual AAC
|
||||
' TODO: Remove this after server adds support for transcoding AAC from one profile to another
|
||||
if LCase(selectedAudioStream.Codec) = "aac"
|
||||
if LCase(selectedAudioStream.Profile) = "main" or LCase(selectedAudioStream.Profile) = "he-aac"
|
||||
for each rule in deviceProfile.TranscodingProfiles
|
||||
if rule.Container = "ts" or rule.Container = "mp4"
|
||||
if rule.AudioCodec = "aac"
|
||||
rule.AudioCodec = "mp3"
|
||||
else if rule.AudioCodec.Left(4) = "aac,"
|
||||
rule.AudioCodec = mid(rule.AudioCodec, 5)
|
||||
|
||||
if rule.AudioCodec.Left(3) <> "mp3"
|
||||
rule.AudioCodec = "mp3," + rule.AudioCodec
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end for
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
req = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params)
|
||||
req.SetRequest("POST")
|
||||
return postJson(req, FormatJson(body))
|
||||
return postJson(req, FormatJson({ "DeviceProfile": deviceProfile }))
|
||||
end function
|
||||
|
||||
' Search across all libraries
|
||||
|
@ -1207,7 +1207,7 @@ namespace api
|
||||
|
||||
' Moves a playlist item.
|
||||
function Move(playlistid as string, itemid as string, newindex as integer)
|
||||
req = APIRequest(Substitute("/playlists/{0}/items/{1}/move/{2}", playlistid, itemid, newindex))
|
||||
req = APIRequest(Substitute("/playlists/{0}/items/{1}/move/{2}", playlistid, itemid, newindex.ToStr()))
|
||||
return postVoid(req)
|
||||
end function
|
||||
end namespace
|
||||
@ -2099,7 +2099,7 @@ namespace api
|
||||
|
||||
' Gets an HLS subtitle playlist.
|
||||
function GetHLSSubtitlePlaylistURL(id as string, streamindex as integer, mediasourceid as string, params = {} as object)
|
||||
return buildURL(Substitute("/videos/{0}/{1}/subtitles/{2}/subtitles.m3u8", id, streamindex, mediasourceid), params)
|
||||
return buildURL(Substitute("/videos/{0}/{1}/subtitles/{2}/subtitles.m3u8", id, streamindex.ToStr(), mediasourceid), params)
|
||||
end function
|
||||
|
||||
' Upload an external subtitle file.
|
||||
@ -2115,13 +2115,13 @@ namespace api
|
||||
' Gets subtitles in a specified format.
|
||||
function GetSubtitlesWithStartPosition(routeitemid as string, routemediasourceid as string, routeindex as integer, routestartpositionticks as integer, routeformat as string, params = {} as object)
|
||||
' We maxed out params for substitute() so we must manually add the routeformat value
|
||||
return buildURL(Substitute("/videos/{0}/{1}/subtitles/{2}/{3}/stream." + routeformat, routeitemid, routemediasourceid, routeindex, routestartpositionticks), params)
|
||||
return buildURL(Substitute("/videos/{0}/{1}/subtitles/{2}/{3}/stream." + routeformat, routeitemid, routemediasourceid, routeindex.ToStr(), routestartpositionticks.ToStr()), params)
|
||||
end function
|
||||
|
||||
' Gets subtitles in a specified format.
|
||||
function GetSubtitles(routeitemid as string, routemediasourceid as string, routeindex as integer, routestartpositionticks as integer, routeformat as string, params = {} as object)
|
||||
' We maxed out params for substitute() so we must manually add the routeformat value
|
||||
return buildURL(Substitute("/videos/{0}/{1}/subtitles/{2}/{3}/stream." + routeformat, routeitemid, routemediasourceid, routeindex, routestartpositionticks), params)
|
||||
return buildURL(Substitute("/videos/{0}/{1}/subtitles/{2}/{3}/stream." + routeformat, routeitemid, routemediasourceid, routeindex.ToStr(), routestartpositionticks.ToStr()), params)
|
||||
end function
|
||||
end namespace
|
||||
|
||||
|
@ -414,7 +414,9 @@ function getContainerProfiles() as object
|
||||
end function
|
||||
|
||||
function getCodecProfiles() as object
|
||||
globalUserSettings = m.global.session.user.settings
|
||||
myGlobal = m.global
|
||||
globalUserSettings = myGlobal.session.user.settings
|
||||
|
||||
codecProfiles = []
|
||||
profileSupport = {
|
||||
"h264": {},
|
||||
@ -451,6 +453,12 @@ function getCodecProfiles() as object
|
||||
"Value": "Main",
|
||||
"IsRequired": true
|
||||
},
|
||||
{
|
||||
"Condition": "NotEquals",
|
||||
"Property": "AudioProfile",
|
||||
"Value": "HE-AAC",
|
||||
"IsRequired": true
|
||||
},
|
||||
{
|
||||
"Condition": "LessThanEqual",
|
||||
"Property": "AudioChannels",
|
||||
@ -549,26 +557,45 @@ function getCodecProfiles() as object
|
||||
hevcVideoRangeTypes = "SDR"
|
||||
vp9VideoRangeTypes = "SDR"
|
||||
av1VideoRangeTypes = "SDR"
|
||||
canPlayDovi = false
|
||||
|
||||
dp = di.GetDisplayProperties()
|
||||
if dp.Hdr10
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HDR10"
|
||||
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HDR10"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HDR10"
|
||||
end if
|
||||
if dp.Hdr10Plus
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HDR10+"
|
||||
end if
|
||||
if dp.HLG
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HLG"
|
||||
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HLG"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HLG"
|
||||
end if
|
||||
if dp.DolbyVision
|
||||
h264VideoRangeTypes = h264VideoRangeTypes + "|DOVI"
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|DOVI"
|
||||
'vp9VideoRangeTypes = vp9VideoRangeTypes + ",DOVI" no evidence that vp9 can hold DOVI
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|DOVI"
|
||||
if canPlay4k()
|
||||
dp = di.GetDisplayProperties()
|
||||
|
||||
if dp.DolbyVision
|
||||
canPlayDovi = true
|
||||
|
||||
h264VideoRangeTypes = h264VideoRangeTypes + "|DOVI|DOVIWithSDR"
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|DOVI|DOVIWithSDR"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|DOVI|DOVIWithSDR"
|
||||
end if
|
||||
|
||||
if dp.Hdr10
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HDR10"
|
||||
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HDR10"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HDR10"
|
||||
|
||||
if canPlayDovi
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|DOVIWithHDR10"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|DOVIWithHDR10"
|
||||
end if
|
||||
end if
|
||||
|
||||
if dp.Hdr10Plus
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HDR10+"
|
||||
end if
|
||||
|
||||
if dp.HLG
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|HLG"
|
||||
vp9VideoRangeTypes = vp9VideoRangeTypes + "|HLG"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|HLG"
|
||||
|
||||
if canPlayDovi
|
||||
hevcVideoRangeTypes = hevcVideoRangeTypes + "|DOVIWithHLG"
|
||||
vp9VideoRangeTypes = vp9VideoRangeTypes + "|DOVIWithHLG"
|
||||
av1VideoRangeTypes = av1VideoRangeTypes + "|DOVIWithHLG"
|
||||
end if
|
||||
end if
|
||||
end if
|
||||
|
||||
' H264
|
||||
@ -599,12 +626,6 @@ function getCodecProfiles() as object
|
||||
"Value": "true",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Condition": "LessThanEqual",
|
||||
"Property": "VideoBitDepth",
|
||||
"Value": "8",
|
||||
"IsRequired": false
|
||||
},
|
||||
{
|
||||
"Condition": "EqualsAny",
|
||||
"Property": "VideoProfile",
|
||||
@ -1088,3 +1109,28 @@ function setPreferredCodec(codecString as string, preferredCodec as string) as s
|
||||
return newCodecString
|
||||
end if
|
||||
end function
|
||||
|
||||
' does the connected display support playing 4k video?
|
||||
function canPlay4k() as boolean
|
||||
deviceInfo = CreateObject("roDeviceInfo")
|
||||
hdmiStatus = CreateObject("roHdmiStatus")
|
||||
|
||||
' Check if the output mode is 2160p or higher
|
||||
maxVideoHeight = m.global.device.videoHeight
|
||||
if maxVideoHeight = invalid then return false
|
||||
if maxVideoHeight.ToInt() < 2160
|
||||
return false
|
||||
end if
|
||||
|
||||
' Check if HDCP 2.2 is enabled, skip check for TVs
|
||||
if deviceInfo.GetModelType() = "STB" and hdmiStatus.IsHdcpActive("2.2") <> true
|
||||
return false
|
||||
end if
|
||||
|
||||
' Check if the Roku player can decode 4K 60fps HEVC streams
|
||||
if deviceInfo.CanDecodeVideo({ Codec: "hevc", Profile: "main", Level: "5.1" }).result <> true
|
||||
return false
|
||||
end if
|
||||
|
||||
return true
|
||||
end function
|
||||
|
@ -97,11 +97,8 @@ sub SaveDeviceToGlobal()
|
||||
print "ERROR parsing deviceInfo.GetVideoMode()"
|
||||
end if
|
||||
videoWidth = heightToWidth[videoHeight]
|
||||
if videoHeight = "2160" and extraData = "b10"
|
||||
bitDepth = 10
|
||||
else if videoHeight = "4320"
|
||||
bitDepth = 12
|
||||
end if
|
||||
if extraData <> invalid and extraData = "b10" then bitDepth = 10
|
||||
if videoHeight = "4320" then bitDepth = 12
|
||||
|
||||
m.global.addFields({
|
||||
device: {
|
||||
|
@ -16,6 +16,9 @@ namespace session
|
||||
Policy: {},
|
||||
settings: {},
|
||||
lastRunVersion: invalid
|
||||
},
|
||||
video: {
|
||||
json: {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -31,7 +34,7 @@ namespace session
|
||||
' Update one value from the global session array (m.global.session)
|
||||
sub Update(key as string, value = {} as object)
|
||||
' validate parameters
|
||||
if key = "" or (key <> "user" and key <> "server") or value = invalid
|
||||
if key = "" or (key <> "user" and key <> "server" and key <> "video") or value = invalid
|
||||
print "Error in session.Update(): Invalid parameters provided"
|
||||
return
|
||||
end if
|
||||
@ -430,4 +433,19 @@ namespace session
|
||||
end sub
|
||||
end namespace
|
||||
end namespace
|
||||
|
||||
namespace video
|
||||
' Return the global video session array to it's default state
|
||||
sub Delete()
|
||||
session.Update("video", { json: {} })
|
||||
end sub
|
||||
|
||||
' Update the global video session array (m.global.session.video)
|
||||
sub Update(videoMetaData as object)
|
||||
if videoMetaData = invalid then return
|
||||
|
||||
session.video.Delete()
|
||||
session.Update("video", videoMetaData)
|
||||
end sub
|
||||
end namespace
|
||||
end namespace
|
||||
|
Loading…
Reference in New Issue
Block a user