source_utils_mediaSegments.bs

import "pkg:/source/enums/MediaSegmentAction.bs"
import "pkg:/source/utils/misc.bs"

' supportsMediaSegments: Checks if the connected Jellyfin server supports the MediaSegments API.
'
' The /MediaSegments/{itemId} endpoint was introduced in Jellyfin 10.10.0.
' Servers below 10.10.0 do not have this endpoint and will return 404.
'
' @returns {boolean} - true if server supports media segments
function supportsMediaSegments() as boolean
  return versionChecker(m.global.server.version, "10.10.0")
end function

' resolveSegmentAction: Resolves the action to take for a given media segment type.
'
' Checks JellyRock per-type override setting first, then falls back to web client
' DisplayPreferences CustomPrefs value, then falls back to built-in defaults.
'
' Built-in defaults:
'   All types = AskToSkip
'
' @param {string} segmentType - MediaSegmentType value ("Intro", "Outro", etc.)
' @param {object} userSettings - JellyfinUserSettings node (JellyRock settings)
' @param {object} userConfig - JellyfinUserConfiguration node (web client settings)
' @returns {string} - MediaSegmentAction value ("None", "AskToSkip", "Skip")
function resolveSegmentAction(segmentType as string, userSettings as object, userConfig as object) as string
  defaultValue = MediaSegmentAction.ASK_TO_SKIP

  ' Try web client setting from config node (loaded from DisplayPreferences CustomPrefs)
  configFieldKey = "segmentAction" + segmentType
  if isValid(userConfig) and isValid(userConfig[configFieldKey]) and userConfig[configFieldKey] <> ""
    defaultValue = userConfig[configFieldKey]
  end if

  ' Check for JellyRock override setting
  settingKey = "playbackSegmentAction" + segmentType
  if isValid(userSettings) and isValid(userSettings[settingKey]) and userSettings[settingKey] <> ""
    overrideValue = userSettings[settingKey]
    if overrideValue = "skip"
      return MediaSegmentAction.SKIP
    else if overrideValue = "askToSkip"
      return MediaSegmentAction.ASK_TO_SKIP
    else if overrideValue = "none"
      return MediaSegmentAction.NONE
    end if
    ' "webclient" or unrecognized -> fall through to web client value
  end if

  return defaultValue
end function

' findActiveSegment: Finds the media segment containing the current playback position.
'
' Linear scan over the segments array (typically 2-5 items).
' Returns the first segment where StartTicks <= position < EndTicks.
'
' @param {roArray} segments - Array of MediaSegmentDto objects ({Id, Type, StartTicks, EndTicks})
' @param {float} positionSeconds - Current playback position in seconds
' @returns {dynamic} The active MediaSegmentDto or invalid if not inside any segment
function findActiveSegment(segments as object, positionSeconds as float) as dynamic
  if not isValid(segments) then return invalid

  for each segment in segments
    startSeconds = segmentTicksToSeconds(segment.StartTicks)
    endSeconds = segmentTicksToSeconds(segment.EndTicks)

    if positionSeconds >= startSeconds and positionSeconds < endSeconds
      return segment
    end if
  end for

  return invalid
end function

' findSegmentById: Finds a media segment by its Id.
'
' Linear scan over the segments array (typically 2-5 items).
' Prefer this over caching segment objects, since the segments array can be
' replaced during content loading (audio/subtitle track changes).
'
' @param {roArray} segments - Array of MediaSegmentDto objects
' @param {string} segmentId - The segment Id to find
' @returns {dynamic} The matching MediaSegmentDto or invalid if not found
function findSegmentById(segments as object, segmentId as string) as dynamic
  if not isValid(segments) or segmentId = "" then return invalid

  for each segment in segments
    if (segment.Id ?? "") = segmentId
      return segment
    end if
  end for

  return invalid
end function

' segmentTicksToSeconds: Converts Jellyfin ticks (100-nanosecond units) to seconds.
'
' @param {dynamic} ticks - Tick count (10,000,000 ticks = 1 second)
' @returns {float} - Position in seconds
function segmentTicksToSeconds(ticks as dynamic) as float
  if not isValid(ticks) then return 0
  return ticks / 10000000#
end function