Merge pull request #1965 from jellyfin/2.1.z

This commit is contained in:
Charles Ewert 2024-10-05 17:51:21 -07:00 committed by GitHub
commit 6756b0188f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 185 additions and 66 deletions

View File

@ -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.1.5 VERSION := 2.1.6
## usage ## usage

View File

@ -4,7 +4,7 @@ import "pkg:/source/utils/config.bs"
sub init() sub init()
m.itemPoster = m.top.findNode("itemPoster") m.itemPoster = m.top.findNode("itemPoster")
m.posterText = m.top.findNode("posterText") m.posterText = m.top.findNode("posterText")
m.title = m.top.findNode("title") initTitle()
m.posterText.font.size = 30 m.posterText.font.size = 30
m.title.font.size = 25 m.title.font.size = 25
m.backdrop = m.top.findNode("backdrop") m.backdrop = m.top.findNode("backdrop")
@ -23,6 +23,10 @@ sub init()
end if end if
end sub end sub
sub initTitle()
m.title = m.top.findNode("title")
end sub
sub itemContentChanged() sub itemContentChanged()
m.backdrop.blendColor = "#101010" m.backdrop.blendColor = "#101010"
@ -54,6 +58,8 @@ sub itemContentChanged()
end sub end sub
sub focusChanged() sub focusChanged()
if not isValid(m.title) then initTitle()
if m.top.itemHasFocus = true if m.top.itemHasFocus = true
m.title.repeatCount = -1 m.title.repeatCount = -1
else else

View File

@ -6,6 +6,7 @@ import "pkg:/source/utils/config.bs"
import "pkg:/source/api/Image.bs" 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"
import "pkg:/source/utils/session.bs"
enum SubtitleSelection enum SubtitleSelection
notset = -2 notset = -2
@ -71,14 +72,24 @@ end function
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_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"
video.content = invalid video.content = invalid
return return
end if 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
video.MaxVideoDecodeResolution = [meta.json.MediaSources[0].MediaStreams[0].Width, meta.json.MediaSources[0].MediaStreams[0].Height]
subtitle_idx = m.top.selectedSubtitleIndex
videotype = LCase(meta.type) videotype = LCase(meta.type)
' Check for any Live TV streams or Recordings coming from other places other than the TV Guide ' Check for any Live TV streams or Recordings coming from other places other than the TV Guide
@ -183,6 +194,11 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
end if end if
video.container = getContainerType(meta) video.container = getContainerType(meta)
if video.container = "mp4"
video.content.StreamFormat = "mp4"
else if video.container = "mkv"
video.content.StreamFormat = "mkv"
end if
if not isValid(m.playbackInfo.MediaSources[0]) if not isValid(m.playbackInfo.MediaSources[0])
m.playbackInfo = meta.json m.playbackInfo = meta.json
@ -197,12 +213,10 @@ sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_s
} }
end if end if
' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles ' 'TODO: allow user selection of subtitle track before playback initiated, for now set to no subtitles
video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay video.directPlaySupported = m.playbackInfo.MediaSources[0].SupportsDirectPlay
fully_external = false fully_external = false
' For h264/hevc video, Roku spec states that it supports specfic encoding levels ' 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 ' 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 ' artifacts. If the user preference is set, and the only reason the server says we need to
@ -351,11 +365,15 @@ 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"
uri = parseUrl(m.playbackInfo.MediaSources[0].Path) 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, ' if the domain of the URI is local to the server,
' create a new URI by appending the received path to the server URL ' create a new URI by appending the received path to the server URL
' later we will substitute the users provided URL for this case ' 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 else
fully_external = true fully_external = true
video.content.url = m.playbackInfo.MediaSources[0].Path video.content.url = m.playbackInfo.MediaSources[0].Path

View File

@ -18,10 +18,8 @@ sub init()
m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode m.itemPoster.loadDisplayMode = m.topParent.imageDisplayMode
end if end if
m.gridTitles = m.global.session.user.settings["itemgrid.gridTitles"]
m.posterText.visible = false m.posterText.visible = false
m.postTextBackground.visible = false m.postTextBackground.visible = false
end sub end sub
sub itemContentChanged() sub itemContentChanged()

View File

@ -1,7 +1,9 @@
import "pkg:/source/utils/config.bs" import "pkg:/source/utils/config.bs"
import "pkg:/source/api/baserequest.bs" import "pkg:/source/api/baserequest.bs"
import "pkg:/source/roku_modules/log/LogMixin.brs"
sub init() sub init()
m.log = log.Logger("captionTask")
m.top.observeField("url", "fetchCaption") m.top.observeField("url", "fetchCaption")
m.top.currentCaption = [] m.top.currentCaption = []
m.top.currentPos = 0 m.top.currentPos = 0
@ -41,17 +43,26 @@ sub setFont()
end sub end sub
sub fetchCaption() sub fetchCaption()
m.log.debug("start fetchCaption()")
m.captionTimer.control = "stop" m.captionTimer.control = "stop"
re = CreateObject("roRegex", "(http.*?\.vtt)", "s") re = CreateObject("roRegex", "(http.*?\.vtt)", "s")
url = re.match(m.top.url)[0] url = re.match(m.top.url)[0]
if url <> invalid if url <> invalid
port = createObject("roMessagePort")
m.reader.setUrl(url) m.reader.setUrl(url)
text = m.reader.GetToString() m.reader.setMessagePort(port)
m.captionList = parseVTT(text) if m.reader.AsyncGetToString()
m.captionTimer.control = "start" msg = port.waitMessage(0)
if type(msg) = "roUrlEvent"
m.captionList = parseVTT(msg.GetString())
m.captionTimer.control = "start"
end if
end if
else else
m.captionTimer.control = "stop" m.captionTimer.control = "stop"
end if end if
m.log.debug("end fetchCaption()", url)
end sub end sub
function newlabel(txt) function newlabel(txt)

View File

@ -4,9 +4,12 @@ import "pkg:/source/utils/config.bs"
sub setFields() sub setFields()
json = m.top.json json = m.top.json
m.top.Type = "Person"
if json = invalid then return
m.top.id = json.id m.top.id = json.id
m.top.favorite = json.UserData.isFavorite m.top.favorite = json.UserData.isFavorite
m.top.Type = "Person"
setPoster() setPoster()
end sub end sub

View File

@ -10,12 +10,12 @@ sub init()
initItemPoster() initItemPoster()
m.itemProgress = m.top.findNode("progress") m.itemProgress = m.top.findNode("progress")
m.itemProgressBackground = m.top.findNode("progressBackground") m.itemProgressBackground = m.top.findNode("progressBackground")
m.itemIcon = m.top.findNode("itemIcon") initItemIcon()
initItemTextExtra() initItemTextExtra()
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged") m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
m.unplayedCount = m.top.findNode("unplayedCount") m.unplayedCount = m.top.findNode("unplayedCount")
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount") m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
m.playedIndicator = m.top.findNode("playedIndicator") initPlayedIndicator()
m.showProgressBarAnimation = m.top.findNode("showProgressBar") m.showProgressBarAnimation = m.top.findNode("showProgressBar")
m.showProgressBarField = m.top.findNode("showProgressBarField") m.showProgressBarField = m.top.findNode("showProgressBarField")
@ -50,6 +50,14 @@ sub initBackdrop()
m.backdrop = m.top.findNode("backdrop") m.backdrop = m.top.findNode("backdrop")
end sub end sub
sub initItemIcon()
m.itemIcon = m.top.findNode("itemIcon")
end sub
sub initPlayedIndicator()
m.playedIndicator = m.top.findNode("playedIndicator")
end sub
sub itemContentChanged() sub itemContentChanged()
if isValid(m.unplayedCount) then m.unplayedCount.visible = false if isValid(m.unplayedCount) then m.unplayedCount.visible = false
itemData = m.top.itemContent itemData = m.top.itemContent
@ -63,6 +71,8 @@ sub itemContentChanged()
if not isValid(m.itemText) then initItemText() if not isValid(m.itemText) then initItemText()
if not isValid(m.itemTextExtra) then initItemTextExtra() if not isValid(m.itemTextExtra) then initItemTextExtra()
if not isValid(m.backdrop) then initBackdrop() 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.itemPoster.width = itemData.imageWidth
m.itemText.maxWidth = itemData.imageWidth m.itemText.maxWidth = itemData.imageWidth
@ -83,11 +93,14 @@ sub itemContentChanged()
if LCase(itemData.type) = "series" if LCase(itemData.type) = "series"
if isValid(localGlobal) and isValid(localGlobal.session) and isValid(localGlobal.session.user) and isValid(localGlobal.session.user.settings) 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 isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
if itemData.json.UserData.UnplayedItemCount > 0 if itemData.json.UserData.UnplayedItemCount > 0
if isValid(m.unplayedCount) then m.unplayedCount.visible = true 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 end if
end if end if

View File

@ -181,8 +181,10 @@ end function
sub onShuffleEpisodeDataLoaded() sub onShuffleEpisodeDataLoaded()
m.getShuffleEpisodesTask.unobserveField("data") m.getShuffleEpisodesTask.unobserveField("data")
m.global.queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items) if isValid(m.getShuffleEpisodesTask.data)
m.global.queueManager.callFunc("playQueue") m.global.queueManager.callFunc("set", m.getShuffleEpisodesTask.data.items)
m.global.queueManager.callFunc("playQueue")
end if
end sub end sub
function onKeyEvent(key as string, press as boolean) as boolean function onKeyEvent(key as string, press as boolean) as boolean

View File

@ -1,5 +1,6 @@
import "pkg:/source/utils/misc.bs" import "pkg:/source/utils/misc.bs"
import "pkg:/source/utils/config.bs" import "pkg:/source/utils/config.bs"
import "pkg:/source/utils/session.bs"
import "pkg:/source/roku_modules/log/LogMixin.brs" import "pkg:/source/roku_modules/log/LogMixin.brs"
sub init() sub init()
@ -102,6 +103,7 @@ sub handleItemSkipAction(action as string)
' If there is something next in the queue, play it ' If there is something next in the queue, play it
if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1 if m.global.queueManager.callFunc("getPosition") < m.global.queueManager.callFunc("getCount") - 1
m.top.control = "stop" m.top.control = "stop"
session.video.Delete()
m.global.sceneManager.callFunc("clearPreviousScene") m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveForward") m.global.queueManager.callFunc("moveForward")
m.global.queueManager.callFunc("playQueue") m.global.queueManager.callFunc("playQueue")
@ -114,6 +116,7 @@ sub handleItemSkipAction(action as string)
' If there is something previous in the queue, play it ' If there is something previous in the queue, play it
if m.global.queueManager.callFunc("getPosition") > 0 if m.global.queueManager.callFunc("getPosition") > 0
m.top.control = "stop" m.top.control = "stop"
session.video.Delete()
m.global.sceneManager.callFunc("clearPreviousScene") m.global.sceneManager.callFunc("clearPreviousScene")
m.global.queueManager.callFunc("moveBack") m.global.queueManager.callFunc("moveBack")
m.global.queueManager.callFunc("playQueue") m.global.queueManager.callFunc("playQueue")
@ -612,19 +615,12 @@ sub onState(msg)
' Pass video state into OSD ' Pass video state into OSD
m.osd.playbackState = m.top.state m.osd.playbackState = m.top.state
' When buffering, start timer to monitor buffering process
if m.top.state = "buffering" if m.top.state = "buffering"
' start buffer timer ' When buffering, start timer to monitor buffering process
if isValid(m.bufferCheckTimer) if isValid(m.bufferCheckTimer)
m.bufferCheckTimer.control = "start" m.bufferCheckTimer.control = "start"
m.bufferCheckTimer.ObserveField("fire", "bufferCheck") m.bufferCheckTimer.ObserveField("fire", "bufferCheck")
end if end if
' update server if needed
if not m.playReported
m.playReported = true
ReportPlayback("start")
end if
else if m.top.state = "error" else if m.top.state = "error"
m.log.error(m.top.errorCode, m.top.errorMsg, m.top.errorStr, m.top.errorCode) m.log.error(m.top.errorCode, m.top.errorMsg, m.top.errorStr, m.top.errorCode)
@ -635,10 +631,10 @@ sub onState(msg)
else else
' If an error was encountered, Display dialog ' If an error was encountered, Display dialog
showPlaybackErrorDialog(tr("Error During Playback")) showPlaybackErrorDialog(tr("Error During Playback"))
session.video.Delete()
end if end if
' Stop playback and exit player
m.top.control = "stop"
else if m.top.state = "playing" else if m.top.state = "playing"
' Check if next episode is available ' Check if next episode is available
@ -664,9 +660,11 @@ sub onState(msg)
m.playbackTimer.control = "stop" m.playbackTimer.control = "stop"
ReportPlayback("stop") ReportPlayback("stop")
m.playReported = false m.playReported = false
session.video.Delete()
else if m.top.state = "finished" else if m.top.state = "finished"
m.playbackTimer.control = "stop" m.playbackTimer.control = "stop"
ReportPlayback("finished") ReportPlayback("finished")
session.video.Delete()
else else
m.log.warning("Unhandled state", m.top.state, m.playReported, m.playFinished) m.log.warning("Unhandled state", m.top.state, m.playReported, m.playFinished)
end if end if
@ -725,6 +723,7 @@ sub bufferCheck(msg)
' Stop playback and exit player ' Stop playback and exit player
m.top.control = "stop" m.top.control = "stop"
session.video.Delete()
end if end if
end if end if
@ -794,6 +793,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
if key = "OK" and m.nextEpisodeButton.hasfocus() and not m.top.trickPlayBar.visible if key = "OK" and m.nextEpisodeButton.hasfocus() and not m.top.trickPlayBar.visible
m.top.control = "stop" m.top.control = "stop"
m.top.state = "finished" m.top.state = "finished"
session.video.Delete()
hideNextEpisodeButton() hideNextEpisodeButton()
return true return true
else else
@ -868,6 +868,7 @@ function onKeyEvent(key as string, press as boolean) as boolean
if key = "back" if key = "back"
m.top.control = "stop" m.top.control = "stop"
session.video.Delete()
end if end if
return false return false

View File

@ -3,7 +3,7 @@
title=Jellyfin title=Jellyfin
major_version=2 major_version=2
minor_version=1 minor_version=1
build_version=5 build_version=6
### Main Menu Icons / Channel Poster Artwork ### Main Menu Icons / Channel Poster Artwork

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "jellyfin-roku", "name": "jellyfin-roku",
"version": "2.1.5", "version": "2.1.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "jellyfin-roku", "name": "jellyfin-roku",
"version": "2.1.5", "version": "2.1.6",
"hasInstallScript": true, "hasInstallScript": true,
"license": "GPL-2.0", "license": "GPL-2.0",
"dependencies": { "dependencies": {

View File

@ -1,7 +1,7 @@
{ {
"name": "jellyfin-roku", "name": "jellyfin-roku",
"type": "module", "type": "module",
"version": "2.1.5", "version": "2.1.6",
"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",

View File

@ -224,8 +224,12 @@ sub Main (args as dynamic) as void
' Find the object in the scene's data and update its json data ' Find the object in the scene's data and update its json data
for i = 0 to currentScene.objects.Items.count() - 1 for i = 0 to currentScene.objects.Items.count() - 1
if LCase(currentScene.objects.Items[i].id) = LCase(currentEpisode.id) 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 exit for
end if end if
end for end for
@ -244,15 +248,19 @@ sub Main (args as dynamic) as void
currentScene = m.global.sceneManager.callFunc("getActiveScene") currentScene = m.global.sceneManager.callFunc("getActiveScene")
if isValid(currentScene) and isValid(currentScene.itemContent) and isValid(currentScene.itemContent.id) if isValid(currentScene) and isValid(currentScene.itemContent) and isValid(currentScene.itemContent.id)
' Refresh movie detail data data = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id)
currentScene.itemContent.json = api.users.GetItem(m.global.session.user.id, currentScene.itemContent.id) if isValid(data)
movieMetaData = ItemMetaData(currentScene.itemContent.id) currentScene.itemContent.json = data
' Set updated starting point for the queue item
m.global.queueManager.callFunc("setTopStartingPoint", data.UserData.PlaybackPositionTicks)
' Redraw movie poster ' Refresh movie detail data
currentScene.newPosterImageURI = movieMetaData.posterURL movieMetaData = ItemMetaData(currentScene.itemContent.id)
if isValid(movieMetaData)
' Set updated starting point for the queue item ' Redraw movie poster
m.global.queueManager.callFunc("setTopStartingPoint", currentScene.itemContent.json.UserData.PlaybackPositionTicks) currentScene.newPosterImageURI = movieMetaData.posterURL
end if
end if
end if end if
stopLoadingSpinner() stopLoadingSpinner()
@ -577,36 +585,41 @@ sub Main (args as dynamic) as void
' If a button is selected, we have some determining to do ' If a button is selected, we have some determining to do
btn = getButton(msg) btn = getButton(msg)
group = sceneManager.callFunc("getActiveScene") group = sceneManager.callFunc("getActiveScene")
if isValid(btn) and btn.id = "play-button" if isValid(btn) and btn.id = "play-button"
if not isValid(group) then return
' User chose Play button from movie detail view ' User chose Play button from movie detail view
startLoadingSpinner() startLoadingSpinner()
' Check if a specific Audio Stream was selected ' Check if a specific Audio Stream was selected
audio_stream_idx = 0 audio_stream_idx = 0
if isValid(group) and isValid(group.selectedAudioStreamIndex) if isValid(group.selectedAudioStreamIndex)
audio_stream_idx = group.selectedAudioStreamIndex audio_stream_idx = group.selectedAudioStreamIndex
end if end if
group.itemContent.selectedAudioStreamIndex = audio_stream_idx if isValid(group.itemContent)
group.itemContent.id = group.selectedVideoStreamId group.itemContent.selectedAudioStreamIndex = audio_stream_idx
group.itemContent.id = group.selectedVideoStreamId
' Display playback options dialog ' Display playback options dialog
if group.itemContent.json.userdata.PlaybackPositionTicks > 0 if group.itemContent.json.userdata.PlaybackPositionTicks > 0
m.global.queueManager.callFunc("hold", group.itemContent) m.global.queueManager.callFunc("hold", group.itemContent)
playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json) playbackOptionDialog(group.itemContent.json.userdata.PlaybackPositionTicks, group.itemContent.json)
else else
m.global.queueManager.callFunc("clear") m.global.queueManager.callFunc("clear")
m.global.queueManager.callFunc("push", group.itemContent) m.global.queueManager.callFunc("push", group.itemContent)
m.global.queueManager.callFunc("playQueue") m.global.queueManager.callFunc("playQueue")
end if
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") buttons = group.findNode("buttons")
if isValid(buttons) if isValid(buttons)
group.lastFocus = group.findNode("buttons") group.lastFocus = group.findNode("buttons")
end if end if
end if end if
if isValid(group) and isValid(group.lastFocus) if isValid(group.lastFocus)
group.lastFocus.setFocus(true) group.lastFocus.setFocus(true)
end if end if

View File

@ -263,16 +263,20 @@ sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx
protocol = LCase(m.playbackInfo.MediaSources[0].Protocol) protocol = LCase(m.playbackInfo.MediaSources[0].Protocol)
if protocol <> "file" if protocol <> "file"
uri = parseUrl(m.playbackInfo.MediaSources[0].Path) 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. ' the domain of the URI is local to the server.
' create a new URI by appending the received path to the server URL ' create a new URI by appending the received path to the server URL
' later we will substitute the users provided URL for this case ' 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 else
fully_external = true fully_external = true
video.content.url = m.playbackInfo.MediaSources[0].Path video.content.url = m.playbackInfo.MediaSources[0].Path
end if end if
else: else
params.append({ params.append({
"Static": "true", "Static": "true",
"Container": video.container, "Container": video.container,

View File

@ -1,4 +1,5 @@
import "pkg:/source/api/sdk.bs" import "pkg:/source/api/sdk.bs"
import "pkg:/source/utils/misc.bs"
function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger) function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger)
params = { params = {
@ -13,9 +14,6 @@ function ItemGetPlaybackInfo(id as string, startTimeTicks = 0 as longinteger)
end function end function
function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger) function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioTrackIndex = -1 as integer, subtitleTrackIndex = -1 as integer, startTimeTicks = 0 as longinteger)
body = {
"DeviceProfile": getDeviceProfile()
}
params = { params = {
"UserId": m.global.session.user.id, "UserId": m.global.session.user.id,
"StartTimeTicks": startTimeTicks, "StartTimeTicks": startTimeTicks,
@ -25,6 +23,7 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT
"MaxStaticBitrate": "140000000", "MaxStaticBitrate": "140000000",
"SubtitleStreamIndex": subtitleTrackIndex "SubtitleStreamIndex": subtitleTrackIndex
} }
deviceProfile = getDeviceProfile()
' Note: Jellyfin v10.9+ now remuxs LiveTV and does not allow DirectPlay anymore. ' 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 ' Because of this, we need to tell the server "EnableDirectPlay = false" so that we receive the
@ -38,11 +37,38 @@ function ItemPostPlaybackInfo(id as string, mediaSourceId = "" as string, audioT
params.EnableDirectPlay = false params.EnableDirectPlay = false
end if end if
if audioTrackIndex > -1 then params.AudioStreamIndex = audioTrackIndex if audioTrackIndex > -1
selectedAudioStream = m.global.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 = APIRequest(Substitute("Items/{0}/PlaybackInfo", id), params)
req.SetRequest("POST") req.SetRequest("POST")
return postJson(req, FormatJson(body)) return postJson(req, FormatJson({ "DeviceProfile": deviceProfile }))
end function end function
' Search across all libraries ' Search across all libraries

View File

@ -451,6 +451,12 @@ function getCodecProfiles() as object
"Value": "Main", "Value": "Main",
"IsRequired": true "IsRequired": true
}, },
{
"Condition": "NotEquals",
"Property": "AudioProfile",
"Value": "HE-AAC",
"IsRequired": true
},
{ {
"Condition": "LessThanEqual", "Condition": "LessThanEqual",
"Property": "AudioChannels", "Property": "AudioChannels",

View File

@ -16,6 +16,9 @@ namespace session
Policy: {}, Policy: {},
settings: {}, settings: {},
lastRunVersion: invalid lastRunVersion: invalid
},
video: {
json: {}
} }
} }
}) })
@ -31,7 +34,7 @@ namespace session
' Update one value from the global session array (m.global.session) ' Update one value from the global session array (m.global.session)
sub Update(key as string, value = {} as object) sub Update(key as string, value = {} as object)
' validate parameters ' 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" print "Error in session.Update(): Invalid parameters provided"
return return
end if end if
@ -430,4 +433,19 @@ namespace session
end sub end sub
end namespace end namespace
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 end namespace