source_utils_liveStreamHandoff.bs
import "pkg:/source/enums/AbandonedLoadAction.bs"
import "pkg:/source/enums/LiveStreamStage.bs"
import "pkg:/source/utils/misc.bs"
' Who releases the Live TV live stream a video load opens. Each release drops one consumer, and
' a second release ends another viewer's shared stream, so exactly one party releases each one:
' - the player, once the view has taken the stream (reportPlaybackEnd, endClosesLiveStream)
' - the view, when the load failed after opening it (VideoPlayerView.onVideoContentLoaded)
' - the view's teardown, when the load still holds it (CLOSE_AND_STOP)
' - the load itself, when the view left while the stream was opening (FINISH_OPENING)
'
' It works because the load records its stage on its own node (LoadVideoContentTask.liveStream)
' and, when PlaybackInfo answers, writes OPEN before it reads isAbandoned, and the view reads the
' stage and sets isAbandoned in one render-thread callback, which the load's two node accesses
' cannot split. The view sets isAbandoned only when it saw OPENING and closes nothing itself, and
' the load closes only when isAbandoned is set, so the two can never both close; the write
' order is what keeps them from both leaving it. A load the view STOPs makes no further node
' access (tests/source/unit/platform/TaskStopProgress.spec.bs), so it sends nothing more either.
' What the view's teardown does with its load, from the stage the load recorded.
'
' @param {dynamic} liveStream - LoadVideoContentTask.liveStream: { stage, id }, or invalid
' @returns {string} an AbandonedLoadAction
function abandonedLoadActionFor(liveStream as dynamic) as string
if liveStreamStageOf(liveStream) = LiveStreamStage.OPENING then return AbandonedLoadAction.FINISH_OPENING
if openLiveStreamId(liveStream) <> "" then return AbandonedLoadAction.CLOSE_AND_STOP
return AbandonedLoadAction.STOP_LOAD
end function
' The stream a load holds and nobody else will release, or "" when there is none.
'
' @param {dynamic} liveStream - LoadVideoContentTask.liveStream: { stage, id }, or invalid
' @returns {string} the LiveStreamId to close, or ""
function openLiveStreamId(liveStream as dynamic) as string
if liveStreamStageOf(liveStream) <> LiveStreamStage.OPEN then return ""
id = liveStream.id
if not isValidAndNotEmpty(id) then return ""
return id
end function
' The recorded stage, NONE when nothing is recorded.
'
' @param {dynamic} liveStream - LoadVideoContentTask.liveStream: { stage, id }, or invalid
' @returns {string} a LiveStreamStage
function liveStreamStageOf(liveStream as dynamic) as string
if type(liveStream) <> "roAssociativeArray" then return LiveStreamStage.NONE
if not isValidAndNotEmpty(liveStream.stage) then return LiveStreamStage.NONE
return liveStream.stage
end function