source_utils_nodeHelpers.bs
' Node utility functions for creating and copying SGNodes
namespace nodeHelpers
' createQueueItem: Create a queue item AA from a source item.
'
' The queue item is a thin AA containing only what VideoPlayerView and QueueManager need.
' LoadVideoContentTask re-fetches all metadata server-side via ItemMetaData(), so the
' queue item does NOT need to carry full data — eliminating the deep-copy perf problem.
'
' Accepts either a JellyfinBaseItem node (typed camelCase fields) or a raw Jellyfin API
' response AA (PascalCase fields). BrightScript AA lookup is case-insensitive, so field
' access works identically for both — e.g. sourceNode.backdropImageTags finds
' BackdropImageTags on a raw API AA and backdropImageTags on a typed node.
'
' @param sourceNode - JellyfinBaseItem node or raw Jellyfin API response AA
' @returns Associative array with guaranteed field contract, or invalid if sourceNode is invalid
'
' Fields always present in the returned AA (guaranteed contract):
' - backdropImageTags: Array of backdrop image tags (empty array = no backdrop)
' - parentBackdropImageTags: Array of parent backdrop tags (empty array = none)
' - parentBackdropItemId: Parent item ID for parent backdrops (empty string = none)
'
' Fields present only when non-empty / non-zero:
' - id: Item identifier (required for playback)
' - type: Media type (required for queue routing)
' - title: Display title
' - mediaSourceId: Media source hint
' - runTimeTicks: Runtime duration
' - seriesId: Series ID for episode navigation in resume dialog
' - seasonId: Season ID for episode navigation in resume dialog
' - playbackPositionTicks: Saved resume position
'
' Callers set startingPoint and selectedAudioStreamIndex on the returned AA after creation.
'
function createQueueItem(sourceNode as object) as object
if not isValid(sourceNode)
print "[nodeHelpers.createQueueItem] ERROR: sourceNode is invalid"
return invalid
end if
' Thin AA — only fields the queue infrastructure actually reads
queueItem = {}
if isValidAndNotEmpty(sourceNode.id) then queueItem.id = sourceNode.id
if isValidAndNotEmpty(sourceNode.type) then queueItem.type = sourceNode.type
if isValidAndNotEmpty(sourceNode.title) then queueItem.title = sourceNode.title
if isValidAndNotEmpty(sourceNode.mediaSourceId) then queueItem.mediaSourceId = sourceNode.mediaSourceId
if isValid(sourceNode.runTimeTicks) and sourceNode.runTimeTicks > 0
queueItem.runTimeTicks = sourceNode.runTimeTicks
end if
' Episode navigation fields used by the resume dialog handler
if isValidAndNotEmpty(sourceNode.seriesId) then queueItem.seriesId = sourceNode.seriesId
if isValidAndNotEmpty(sourceNode.seasonId) then queueItem.seasonId = sourceNode.seasonId
' Resume position — stored so the resume dialog can read it without the original node.
' Typed nodes: transformer populates playbackPositionTicks from UserData.
' Raw API AAs: fall back to UserData.PlaybackPositionTicks (nested).
positionTicks = 0
if isValid(sourceNode.playbackPositionTicks) and sourceNode.playbackPositionTicks > 0
positionTicks = sourceNode.playbackPositionTicks
else if isValid(sourceNode.UserData) and isValid(sourceNode.UserData.PlaybackPositionTicks)
positionTicks = sourceNode.UserData.PlaybackPositionTicks
end if
if positionTicks > 0 then queueItem.playbackPositionTicks = positionTicks
' Backdrop fields — always set to guarantee callers can check .Count() without isValid().
' Empty array/string = no backdrop available for this item.
queueItem.backdropImageTags = isValid(sourceNode.backdropImageTags) ? sourceNode.backdropImageTags : []
queueItem.parentBackdropImageTags = isValid(sourceNode.parentBackdropImageTags) ? sourceNode.parentBackdropImageTags : []
queueItem.parentBackdropItemId = isValidAndNotEmpty(sourceNode.parentBackdropItemId) ? sourceNode.parentBackdropItemId : ""
return queueItem
end function
end namespace