Skip to content

Commit 6b77365

Browse files
committed
Update code docs
1 parent 06ccb11 commit 6b77365

4 files changed

Lines changed: 162 additions & 53 deletions

docs/components_ItemGrid_LoadVideoContentTask.bs.html

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
sub loadItems()
2525
queueManager = m.global.queueManager
2626

27-
' Reset intro tracker in case task gets reused
27+
' Reset per-run fields in case task gets reused
2828
m.top.isIntro = false
29+
m.top.errorMsg = ""
2930

3031
' Only show preroll once per queue
3132
if queueManager.callFunc("isPrerollActive")
@@ -127,12 +128,17 @@
127128
subtitle_idx = m.top.selectedSubtitleIndex
128129
videotype = LCase(meta.type)
129130

130-
' Check for any Live TV streams or Recordings coming from other places other than the TV Guide
131+
' Check for any Live TV streams or Recordings coming from other places other than the TV Guide.
132+
' TvChannel is always live — include it here so mediaSourceId is cleared and live-stream
133+
' flags (content.live, StreamFormat, transcodeParams.LiveStreamId) are set correctly below.
131134
isLive = false
132135
titleOverride = ""
133-
if videotype = "recording" or isValidAndNotEmpty(meta.channelId)
136+
if videotype = "recording" or videotype = "tvchannel" or isValidAndNotEmpty(meta.channelId)
134137
if isValidAndNotEmpty(meta.episodeTitle)
135138
titleOverride = meta.episodeTitle
139+
else if videotype = "tvchannel" and isValid(meta.currentProgram) and isValidAndNotEmpty(meta.currentProgram.name)
140+
' For TvChannel, prefer the currently-airing program's name over the channel name
141+
titleOverride = meta.currentProgram.name
136142
else
137143
titleOverride = meta.name
138144
end if
@@ -144,9 +150,6 @@
144150
end if
145151
end if
146152

147-
' TVChannel CurrentProgram fields (live program title/episode info) deferred to Phase 6
148-
' meta.json.CurrentProgram is not a typed field on JellyfinBaseItem
149-
150153
video.chapters = meta.chapters
151154
video.title = isValidAndNotEmpty(titleOverride) ? titleOverride : meta.title
152155
video.showID = meta.seriesId
@@ -177,6 +180,21 @@
177180
else if isValidAndNotEmpty(meta.primaryImageTag)
178181
video.logoImage = ImageURL(meta.id, "Primary", { maxHeight: 212, maxWidth: 500, quality: 90, tag: meta.primaryImageTag })
179182
end if
183+
else if videotype = "tvchannel"
184+
' For live TV channels, prefer the currently-airing program's artwork over the channel icon.
185+
' Priority: program logo → program primary (poster) → channel icon (fallback).
186+
currentProgram = meta.currentProgram
187+
if isValid(currentProgram)
188+
if isValidAndNotEmpty(currentProgram.logoImageTag)
189+
video.logoImage = ImageURL(currentProgram.id, "Logo", { maxHeight: 212, maxWidth: 500, quality: 90, tag: currentProgram.logoImageTag })
190+
else if isValidAndNotEmpty(currentProgram.primaryImageTag)
191+
video.logoImage = ImageURL(currentProgram.id, "Primary", { maxHeight: 534, maxWidth: 500, quality: 90, tag: currentProgram.primaryImageTag })
192+
else if isValidAndNotEmpty(meta.primaryImageTag)
193+
video.logoImage = ImageURL(meta.id, "Primary", { maxHeight: 212, maxWidth: 212, quality: 90, tag: meta.primaryImageTag })
194+
end if
195+
else if isValidAndNotEmpty(meta.primaryImageTag)
196+
video.logoImage = ImageURL(meta.id, "Primary", { maxHeight: 212, maxWidth: 212, quality: 90, tag: meta.primaryImageTag })
197+
end if
180198
end if
181199

182200
if LCase(m.top.itemType) = "episode"
@@ -226,22 +244,27 @@
226244
' Call ItemPostPlaybackInfo ONCE with final subtitle_idx
227245
m.playbackInfo = ItemPostPlaybackInfo(video.id, mediaSourceId, audio_stream_idx, subtitle_idx, playbackPosition, meta, bypassDoviPreservation, forceTranscoding)
228246
if not isValid(m.playbackInfo)
229-
video.errorMsg = "Error loading playback info"
247+
m.log.error("ItemPostPlaybackInfo returned invalid response")
248+
m.top.errorMsg = tr("There was an error retrieving playback information from the server.")
230249
video.content = invalid
231250
return
232251
end if
233252

234-
' Playback info can be logged here for debugging playback issues
235-
print "LoadItems_AddVideoContent() m.playbackInfo =", m.playbackInfo
236-
if m.playbackInfo.MediaSources.Count() > 0
237-
for each mediaSource in m.playbackInfo.MediaSources
238-
print "mediaSource =", mediaSource
239-
' for each mediaStream in mediaSource.MediaStreams
240-
' print "mediaStream =", mediaStream
241-
' end for
242-
end for
253+
' Surface structured server errors as user-friendly messages before inspecting MediaSources
254+
if isValidAndNotEmpty(m.playbackInfo.ErrorCode)
255+
errorCode = m.playbackInfo.ErrorCode
256+
m.log.error("PlaybackInfo returned error code", "errorCode", errorCode, "itemId", video.id)
257+
if errorCode = "NoCompatibleStream"
258+
m.top.errorMsg = tr("No compatible streams are available for this item.")
259+
else
260+
m.top.errorMsg = tr("The server was unable to start playback.") + " (" + errorCode + ")"
261+
end if
262+
video.content = invalid
263+
return
243264
end if
244265

266+
m.log.debug("PlaybackInfo loaded", "mediaSourceCount", m.playbackInfo.MediaSources.Count())
267+
245268
' Call addSubtitlesToVideo ONCE after ItemPostPlaybackInfo
246269
addSubtitlesToVideo(video, meta)
247270

@@ -313,9 +336,10 @@
313336
applyLiveDirectPlayFallback(video, meta)
314337
else
315338
if not isValid(m.playbackInfo.MediaSources[0].TranscodingUrl)
316-
' If server does not provide a transcode URL, display a message to the user
317-
m.global.sceneManager.callFunc("userMessage", tr("Error Getting Playback Information"), tr("An error was encountered while playing this item. Server did not provide required transcoding data."))
318-
video.errorMsg = "Error getting playback information"
339+
' Server did not provide a transcode URL — surface a message to the user via the task field
340+
' so VideoPlayerView shows one dialog (not two).
341+
m.log.error("Server did not provide a TranscodingUrl", "itemId", video.id)
342+
m.top.errorMsg = tr("An error was encountered while playing this item. The server did not provide the required transcoding data.")
319343
video.content = invalid
320344
return
321345
end if

docs/components_video_OSD.bs.html

Lines changed: 92 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,15 @@
6363

6464
itemType = item.type
6565

66-
' Determine isMovie/isSeries flags for ratings display settings
67-
' TODO Phase 6: For TvChannel/Program types, isMovie/isSeries should come from CurrentProgram data
68-
isMovieFlag = item.isMovie
69-
isSeriesFlag = item.isSeries
66+
' Determine isMovie/isSeries flags for ratings display settings.
67+
' For TvChannel, use the currently-airing program's flags if available.
68+
if itemType = "TvChannel" and isValid(item.currentProgram)
69+
isMovieFlag = item.currentProgram.isMovie
70+
isSeriesFlag = item.currentProgram.isSeries
71+
else
72+
isMovieFlag = item.isMovie
73+
isSeriesFlag = item.isSeries
74+
end if
7075

7176
' Validate mutual exclusivity: both flags cannot be true
7277
if isMovieFlag and isSeriesFlag
@@ -95,16 +100,17 @@
95100
' Stream counts - read pre-computed values from item node
96101
m.top.numAudioStreams = item.audioStreamCount
97102

98-
' Runtime
99-
if item.runTimeTicks > 0
100-
m.top.runTimeMinutes = ticksToMinutes(item.runTimeTicks)
103+
' Runtime — for TvChannel, use the currently-airing program's runtime if available
104+
runTimeTicks = item.runTimeTicks
105+
if itemType = "TvChannel" and isValid(item.currentProgram) and item.currentProgram.runTimeTicks > 0
106+
runTimeTicks = item.currentProgram.runTimeTicks
107+
end if
108+
if runTimeTicks > 0
109+
m.top.runTimeMinutes = ticksToMinutes(runTimeTicks)
101110
else
102111
m.top.runTimeMinutes = 0
103112
end if
104113

105-
' TODO Phase 6: Live TV CurrentProgram override for runTimeTicks/runTimeMinutes
106-
' When currentProgram data is available on JellyfinBaseItem, override here.
107-
108114
setButtonStates()
109115
populateData()
110116
end sub
@@ -150,15 +156,30 @@
150156
end sub
151157

152158
sub setEndsAtText()
159+
endsAtText = m.top.findNode("endsAtText")
160+
153161
if m.global.user.settings.uiDesignHideClock
154-
endsAtText = m.top.findNode("endsAtText")
155162
endsAtText.visible = false
156163
m.endsAtTime.text = ""
157164
return
158165
end if
159166

160-
' TODO Phase 6: Live TV "ends at" time from CurrentProgram.EndDate
161-
' When currentProgram data is available on JellyfinBaseItem, restore TvChannel EndDate display here.
167+
' For live TV channels, use the currently-airing program's EndDate for end time.
168+
' remainingPositionTime ≈ 0 for live streams so the default calculation would show current time.
169+
if isValid(m.itemData) and m.itemData.type = "TvChannel"
170+
currentProgram = m.itemData.currentProgram
171+
if isValid(currentProgram) and isValidAndNotEmpty(currentProgram.endDate)
172+
endDt = CreateObject("roDateTime")
173+
endDt.FromISO8601String(currentProgram.endDate)
174+
endDt.toLocalTime()
175+
m.endsAtTime.text = formatTime(endDt)
176+
endsAtText.visible = true
177+
else
178+
endsAtText.visible = false
179+
m.endsAtTime.text = ""
180+
end if
181+
return
182+
end if
162183

163184
' Calculate endsAtTime based on remainingPositionTime
164185
date = CreateObject("roDateTime")
@@ -171,16 +192,16 @@
171192

172193
sub setVideoLogoGroup()
173194
m.videoLogo.uri = m.top.videoLogo
174-
175-
' TODO Phase 6: Live TV channel logo from CurrentProgram image tags
176-
' When currentProgram data is available on JellyfinBaseItem, restore TvChannel logo override here.
177195
end sub
178196

179197
sub setVideoTitle()
180-
m.videoTitle.text = m.itemData.name
181-
182-
' TODO Phase 6: Live TV channel title from CurrentProgram.Name
183-
' When currentProgram data is available on JellyfinBaseItem, restore TvChannel title override here.
198+
item = m.itemData
199+
' For TvChannel, display the currently-airing program name rather than the channel name
200+
if item.type = "TvChannel" and isValid(item.currentProgram) and isValidAndNotEmpty(item.currentProgram.name)
201+
m.videoTitle.text = item.currentProgram.name
202+
else
203+
m.videoTitle.text = item.name
204+
end if
184205
end sub
185206

186207
sub setVideoSubTitle()
@@ -191,6 +212,12 @@
191212
item = m.itemData
192213
itemType = item.type
193214

215+
' For TvChannel, ratings and dates are sourced from the currently-airing program when available
216+
metaItem = item
217+
if itemType = "TvChannel" and isValid(item.currentProgram)
218+
metaItem = item.currentProgram
219+
end if
220+
194221
' EPISODE
195222
if itemType = "Episode" or itemType = "Recording"
196223
' Title
@@ -241,18 +268,53 @@
241268
displaySubtitleNode(productionYearNode)
242269
end if
243270
else if itemType = "TvChannel"
244-
' TODO Phase 6: Live TV channel number/name from CurrentProgram data
245-
' When currentProgram data is available on JellyfinBaseItem, restore channel info display here.
271+
' Display currently-airing program metadata (episode info or movie year)
272+
currentProgram = item.currentProgram
273+
if isValid(currentProgram)
274+
if currentProgram.isSeries
275+
' Episode info: S1E2 - Episode Name
276+
episodeInfoText = ""
277+
if currentProgram.parentIndexNumber > 0
278+
episodeInfoText = episodeInfoText + `${tr("S")}${currentProgram.parentIndexNumber}`
279+
end if
280+
if currentProgram.indexNumber > 0
281+
episodeInfoText = episodeInfoText + `${tr("E")}${currentProgram.indexNumber}`
282+
end if
283+
if isValidAndNotEmpty(currentProgram.name)
284+
if isValidAndNotEmpty(episodeInfoText)
285+
episodeInfoText = episodeInfoText + ` - ${currentProgram.name}`
286+
else
287+
episodeInfoText = currentProgram.name
288+
end if
289+
end if
290+
if isValidAndNotEmpty(episodeInfoText)
291+
episodeInfoNode = createSubtitleLabelNode("episodeInfo")
292+
episodeInfoNode.text = episodeInfoText
293+
displaySubtitleNode(episodeInfoNode)
294+
end if
295+
else if currentProgram.isMovie and currentProgram.productionYear > 0
296+
airDateNodeCreated = true
297+
productionYearNode = createSubtitleLabelNode("productionYear")
298+
productionYearNode.text = currentProgram.productionYear.toStr().trim()
299+
displaySubtitleNode(productionYearNode)
300+
end if
301+
end if
302+
' Channel number (e.g. "CH 4") — always shown when available
303+
if isValidAndNotEmpty(item.channelNumber)
304+
channelNumberNode = createSubtitleLabelNode("channelNumber")
305+
channelNumberNode.text = `${tr("CH")} ${item.channelNumber}`
306+
displaySubtitleNode(channelNumberNode)
307+
end if
246308
end if
247309

248310
' append these to all video types
249311
'
250312
userSettings = m.global.user.settings
251313

252314
' Official Rating
253-
if isValidAndNotEmpty(item.officialRating)
315+
if isValidAndNotEmpty(metaItem.officialRating)
254316
officialRatingNode = createSubtitleLabelNode("officialRating")
255-
officialRatingNode.text = item.officialRating
317+
officialRatingNode.text = metaItem.officialRating
256318
displaySubtitleNode(officialRatingNode)
257319
end if
258320

@@ -271,30 +333,28 @@
271333

272334
if showRatings
273335
' communityRating (star + rating)
274-
' TODO Phase 6: For TvChannel/Program, communityRating should come from CurrentProgram
275-
if item.communityRating <> 0
336+
if metaItem.communityRating <> 0
276337
communityRatingNode = CreateObject("roSGNode", "CommunityRating")
277338
communityRatingNode.id = "communityRating"
278-
communityRatingNode.rating = item.communityRating
339+
communityRatingNode.rating = metaItem.communityRating
279340
communityRatingNode.iconSize = 30
280341
displaySubtitleNode(communityRatingNode)
281342
end if
282343

283344
' criticRating (tomato + rating)
284-
' TODO Phase 6: For TvChannel/Program, criticRating should come from CurrentProgram
285-
if item.criticRating <> 0
345+
if metaItem.criticRating <> 0
286346
criticRatingNode = CreateObject("roSGNode", "CriticRating")
287347
criticRatingNode.id = "criticRating"
288-
criticRatingNode.rating = item.criticRating
348+
criticRatingNode.rating = metaItem.criticRating
289349
criticRatingNode.iconSize = 30
290350
displaySubtitleNode(criticRatingNode)
291351
end if
292352
end if
293353

294354
' videoAirDate if needed
295-
if not airDateNodeCreated and isValidAndNotEmpty(item.premiereDate)
355+
if not airDateNodeCreated and isValidAndNotEmpty(metaItem.premiereDate)
296356
premiereDateNode = createSubtitleLabelNode("videoAirDate")
297-
premiereDateNode.text = formatIsoDateVideo(item.premiereDate)
357+
premiereDateNode.text = formatIsoDateVideo(metaItem.premiereDate)
298358
displaySubtitleNode(premiereDateNode)
299359
end if
300360

docs/components_video_VideoPlayerView.bs.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -519,10 +519,17 @@
519519
videoContent = m.LoadMetaDataTask.content
520520
m.LoadMetaDataTask.content = []
521521

522-
' If we have nothing to play, return to previous screen
522+
' If we have nothing to play, show an error dialog and return.
523+
' Prefer the task's specific errorMsg (e.g. NoCompatibleStream) over the generic fallback
524+
' so the user sees an actionable message rather than a generic one.
523525
if not isValid(videoContent) or not isValid(videoContent[0])
524526
stopLoadingSpinner()
525-
showPlaybackErrorDialog(tr("There was an error retrieving the data for this item from the server."))
527+
taskErrorMsg = m.LoadMetaDataTask.errorMsg
528+
if isValidAndNotEmpty(taskErrorMsg)
529+
showPlaybackErrorDialog(taskErrorMsg)
530+
else
531+
showPlaybackErrorDialog(tr("There was an error retrieving the data for this item from the server."))
532+
end if
526533
return
527534
end if
528535

docs/source_data_JellyfinDataTransformer.bs.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,24 @@
197197
m.transformSeasonFields(item, apiData)
198198
else if itemType = "Series"
199199
m.transformSeriesFields(item, apiData)
200+
else if itemType = "TvChannel"
201+
' TV channels are always live — set the ContentNode live built-in so applyLiveDirectPlayFallback
202+
' can arm the transcode retry path, and isLive in the JellyfinBaseItem field for consistency.
203+
item.isLive = true
204+
item.live = true
205+
' Channel number lives in apiData.Number for TvChannel items (distinct from apiData.ChannelNumber,
206+
' which is the program-to-channel reference field used only on Program/Recording items).
207+
item.channelNumber = apiData.Number ?? ""
208+
' CurrentProgram is a nested BaseItemDto returned by default on all TvChannel items
209+
' (not gated behind ItemFields — no extra fields= param needed). Transform it so the
210+
' OSD and playback stack can read typed program fields (name, episode info, artwork,
211+
' runtime, end time) directly from the TvChannel node.
212+
if isValid(apiData.CurrentProgram)
213+
programNode = m.transformBaseItem(apiData.CurrentProgram, serverVersion)
214+
if isValid(programNode)
215+
item.currentProgram = programNode
216+
end if
217+
end if
200218
else if itemType = "Program" or itemType = "Recording"
201219
m.transformProgramFields(item, apiData)
202220
else if itemType = "MusicAlbum" or itemType = "Audio" or itemType = "MusicArtist" or itemType = "MusicVideo"

0 commit comments

Comments
 (0)