Skip to content

fix(signage): correct takeover, trigger and random playlist scheduling - #517

Merged
MrYuion merged 3 commits into
developfrom
fix/signage-scheduling
Oct 2, 2026
Merged

MrYuion merged 3 commits into
developfrom
fix/signage-scheduling

Conversation

@MrYuion

@MrYuion MrYuion commented Oct 1, 2026 •

Copy link
Copy Markdown
Collaborator

The player showed the wrong content in five scheduling cases:

  • Random playlists got a new order on every 15 s schedule tick. The item on screen restarted, so videos longer than 15 s never finished.
  • Triggers fired when they were bound (on boot and on every display reload) and when their value changed back to false.
  • A background schedule on a playlist that also had a takeover schedule made the playlist a full-screen takeover (for example, 8 hours instead of 10 minutes).
  • Single-pass (play_period: 0) takeovers were cleared when their 30 s trigger window closed, after about 2 items.
  • A takeover whose media had all expired still started, and showed a blank screen for its full play period.

Changes

All in apps/signage/src/app/signage.service.ts:

  • Random order: each random playlist keeps one shuffled order until its media list changes.
  • Triggers: a trigger fires only when its value changes from false to true.
  • Schedule kind: activePlaylistSchedules takes a background or takeover kind. Takeovers use only takeover schedules. Background playback and media validity windows use the schedule of the matching kind. The unused trigger_window_seconds argument is removed.
  • Single pass: a single-pass run stays in the override after its trigger window until the player reports playlist_through. The hold ends if the playlist is removed or disabled, is no longer a single-pass takeover, or has no valid media.
  • No valid media: takeover runs with no valid media do not start. This is checked on every tick, so a takeover starts when its media becomes valid.
  • Diagnostics: state().playlists.active shows a running takeover over a background schedule.
  • Single pass vs timed (review follow-up): single-pass runs play alone, ahead of timed runs, with ends_at 0. Held runs are kept when the override is rebuilt. Timed runs start or resume after the pass and still end at their scheduled time. Single-pass media uses a new single-pass schedule kind for its validity window.

USER_STORIES.md (US-SIG-006, 019, 020) and DEBUGGING.md are updated.

Testing

  • Unit tests for each case. Each test fails on develop.
  • nx test signage passes (378 tests). nx build signage passes. nx lint signage has 12 errors that are also on develop (selector prefixes, module boundaries in test-setup.ts).
  • Tested against a local PlaceOS stack with Playwright: random order held over 10 ticks and reshuffled once on a media change, background and takeover on one playlist, a single pass of 6 items, disabling during a pass, expired and valid_from takeovers, triggers (realtime socket mocked), a plain playlist.

Needs a decision

  • Trigger at reboot: after a page load, a trigger that is already true fires once. Display reloads do not replay it.
  • Single pass has no time limit: it waits for playlist_through. fix(signage): recover from stalled plugins, hung recovery and failed boot #518 adds a limit for plugins that never finish, which is the likely cause of a missing report.
  • Backend rejects play_period: 0: the local stack's backend returns "play_period must be greater than 0", but the manager schedule form can send 0. Please check the production backend.

Merge order

Independent of #518 and #519, but all three add rows to the symptom table in apps/signage/DEBUGGING.md. The second and third to merge need a rebase. Based on develop.


Changes made by Claude Opus 5.5 (1M context) in Claude Code, running in T3 Code.

🤖 Generated with Claude Code

- Random playlists keep one shuffled order until their media list
  changes. The playlist was rebuilt every 15 s tick with a new order,
  which restarted the item on screen.
- Triggers fire only when their value turns true. Binding emitted the
  current value at once, so triggers fired on boot, on every display
  reload and when reset to false.
- Only schedules marked as takeover start a takeover. A background
  schedule on a takeover playlist made it a full-screen takeover.
- Single-pass takeovers are held after their 30 s trigger window until
  the player reports a full pass. They were cleared after ~2 items.
- A takeover with no valid media does not start, so it cannot pause
  normal playback behind a blank screen.
- Diagnostics report a running takeover over a background schedule.
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
frontend-templates Ignored Ignored Preview Oct 1, 2026 2:44pm UTC

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Fixes scheduling logic for playlists and takeovers.

The reviewed changes appear safe to merge; the prior single-pass expiry issue is fixed.

Findings

  1. P1 Single pass ends early ▶

Summary

The signage player now keeps random playlists steady, starts triggers only on a false-to-true change, and separates background schedules from takeovers. Single-pass takeovers can finish their playlist, while takeovers wait for valid media before starting.

  • Keeps a random playlist’s order until its media list changes.
  • Uses the matching schedule kind for normal playback and takeovers.
  • Adds single-pass and valid-media checks for takeovers.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Check active takeover runs] --> B{Single-pass run active or held?}
  B -- Yes --> C[Build override from single-pass runs]
  B -- No --> D[Build override from timed runs]
  C --> E[Wait for playlist_through]
  D --> F[End at scheduled time]
Loading

Reviews (3) · Last reviewed commit: "fix(signage): limit single-pass media by..."

Comment thread apps/signage/src/app/signage.service.ts Outdated
Comment thread apps/signage/src/app/signage.service.ts Outdated
@greptile-apps

This comment has been minimized.

The player ends an override at `ends_at` or after one pass, never both.
A single pass that shared an override with a timed run was ended by the
timed run's end time, kept a disabled timed playlist on screen, or was
dropped when a new takeover started.

Single-pass runs now play alone, ahead of timed runs, with `ends_at` 0.
Held runs are kept when the override is rebuilt. A timed run is detected
across its whole play period, so it starts or resumes after the pass and
still ends at its scheduled time.
@MrYuion

MrYuion commented Oct 1, 2026

Copy link
Copy Markdown
Collaborator Author

Re the outside-diff finding New takeover cuts off pass (signage.service.ts:1186): fixed in e624deb. Held single-pass runs are now kept when the override is rebuilt, and a timed run that starts during the pass waits for it. Test: should start a timed takeover once a held single pass finishes.

One known limit remains, outside this PR: if two single-pass runs overlap, they share one override. signage.component.ts clears that override on the first playlist_through, so the second pass can be cut short. Fixing that needs a component change (for example, clear only on the last playlist in the override), and #518 owns that file.

Comment thread apps/signage/src/app/signage.service.ts Outdated
Comment on lines +1186 to +1190
const media = this._getPlaylistMedia(
display,
active_playlists.map((_) => _.id),
[...new Set(runs.map(({ playlist_id }) => playlist_id))],
() => true,
'takeover',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Single pass ends early

When a playlist has a timed takeover listed before a single-pass takeover and both are active, the override picks the single pass but builds its media from the playlist's first active takeover schedule. That can give the media the timed run's expiry. If the timed run ends before the pass finishes, the player drops the remaining media and ends the pass early. Build the media using the selected single-pass run's window.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d3a6558. Added a single-pass schedule kind (a takeover with play_period 0). Single-pass overrides build their media with it, so a timed takeover on the same playlist no longer sets their validity window. Test: should not expire single-pass media with a timed run on the same playlist. It fails without the fix.

A playlist can have a timed and a single-pass takeover active at once.
Single-pass media took the validity window of the first active takeover
schedule, which could be the timed one. The pass then ended at the timed
run's end. Add a `single-pass` schedule kind and use it to build media
for single-pass overrides.
@MrYuion
MrYuion merged commit 9638120 into develop Oct 2, 2026
6 checks passed
@MrYuion
MrYuion deleted the fix/signage-scheduling branch October 2, 2026 00:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant