Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement QuickLoop (SOFIE-2878) #1112

Open
wants to merge 39 commits into
base: release51
Choose a base branch
from

Conversation

ianshade
Copy link
Contributor

@ianshade ianshade commented Jan 8, 2024

About the Contributor

This pull request is posted on behalf of the BBC.

Type of Contribution

This is a:

Feature

Current Behavior

Currently Sofie allows looping of an entire Rundown Playlist. Whether the playlist should be looping can only be set by the Blueprints (usually done according to properties coming from the NRCS).

New Behavior

What it does:

  • The QuickLoop feature enables users to define a looping section within Rundown Playlists using Start and End markers.
  • Automatically loops between a designated section.

How to use it:

  1. Set Markers:

    • Right-click on a part within the Rundown Playlist.
    • Choose "Set as QuickLoop Start" or "Set as QuickLoop End" from the context menu.
  2. Activate QuickLoop:

    • QuickLoop mode will automatically trigger when any part between the Start and End markers is taken.
    • If configured, parts within the QuickLoop will progress automatically.
  3. Modify the QuickLoop:

    • Set new markers (by context-clicking) to extend or shorten the QuickLoop section.
    • Execute adlibs without interrupting the QuickLoop.
    • Ensure logical marker placement to prevent errors.
  4. Exit QuickLoop Mode:

    • Clear the markers using the context menu, or set a part outside of the QuickLoop as Next.
    • Playback continues seamlessly after exiting QuickLoop Mode.

Deviations from the RFC, and some more details:

  • A Rundown Playlist with user-defined QuickLoop has similar indication in the Lobby and Rundown View as a looping Playlist.
  • Setting a Start marker on a part after the End marker results in clearing the End marker, and vice versa.
  • Clearing/exiting the QuickLoop by the use of a hotkey or AdLib Action is not implemented.
  • The following settings are available in the Generic properties of a Studio:
    • Enable QuickLoop (enables QuickLoop context menu options, but doesn't affect Playlist looping enabled by the NRCS/blueprints)
    • Force Auto in a Loop, with three available options:
      • Disabled (default - consistent with existing Playlist loop behaviour)
      • Enabled, but skipping parts with undefined or 0 duration
      • Enabled on all Parts, applying QuickLoop Fallback Part Duration if needed
    • QuickLoop Fallback Part Duration
  • The QuickLoop section is indicated as shown below
    image
    image
    image

Testing Instructions

Setting and Clearing Markers:

  1. Open Sofie GUI and navigate to a Rundown Playlist that does not have looping enabled by the NRCS.
  2. Right-click on a Part (as long as it is not marked as invalid) and verify that you can set a Start marker.
  3. Right-click on another Part (as long as it is not marked as invalid) and set the End marker.
  4. Ensure that setting a Start marker after the End marker will clear the previous End marker, and vice versa.
  5. Verify that you can clear the Start and End markers from the context menu.
  6. Test that setting the Start and End markers on the same Part creates a single-part loop.

Loop Mode Functionality:

  1. Set the Start and End markers within a Rundown Playlist.
  2. Take a Part within the defined loop section.
  3. Verify that the Loop Mode is triggered and subsequent Parts progress automatically.
  4. Check that Parts within the loop show the AUTO indicator in the GUI.
  5. Ensure that when the end of the loop is reached, the first item in the loop becomes Next and an Auto Take occurs.
  6. Test exiting the Loop Mode using the dedicated hotkey, AdLib Action, or context menu item.
  7. Confirm that played adlibs are removed each time the loop starts over.
  8. Verify behavior when a dynamic part is inserted while playing the End part of the loop.
  9. Test for Parts with a duration of 0 seconds or undefined within the looping section and verify the default behavior or any custom settings applied.
  10. Check that all Parts outside of the loop are dimmed but retain functionality.
  11. Verify the indication showing that the Loop Mode is active.

Additional Scenarios:

  1. Test setting Start/End markers on different Parts to modify the looping section in the Loop Mode.
  2. Verify that setting a Part outside of the looping section as Next and taking it exits the Loop Mode.
  3. Ensure that ingest operations altering the order of Segments and Parts or removing Parts containing markers stop the Loop Mode and delete the markers.
  4. Test ingest operations hiding the Segment containing marked parts and verify Loop Mode behavior.

Testing Instructions for Studio Settings:

Enable QuickLoop:

  1. Verify that enabling QuickLoop in the Studio settings enables corresponding context menu options.

Force Auto in a Loop:

  1. Test each option for "Force Auto in a Loop" to ensure they behave as described:
  • Verify that the "Disabled" option maintains consistency with existing Playlist loop behavior, not applying auto-next.
  • Validate the behavior of the "Enabled, but skipping parts with undefined or 0 duration" option.
  • Confirm that the "Enabled on all Parts, applying QuickLoop Fallback Part Duration if needed" option functions correctly.

QuickLoop Fallback Part Duration:

  1. Verify that the QuickLoop Fallback Part Duration setting is applied appropriately in conjunction with corresponding Force Auto in a Loop value.

Regression testing instructions for the existing Playlist Looping Feature:

  1. Validate that existing Blueprints continue to function without requiring modifications for Playlist looping.
  2. Verify that Playlist looping enabled by the NRCS/Blueprints functions as expected and is indicated in the GUI as it used to be.
  3. Confirm that Start and End markers are correctly locked to the Playlist start and end and the user can not override them.

Other Information

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

@jstarpl jstarpl added the Contribution from BBC Contributions sponsored by BBC (bbc.co.uk) label Jan 9, 2024
@jstarpl jstarpl self-assigned this Jan 9, 2024
Copy link

codecov bot commented Jan 11, 2024

Codecov Report

Attention: Patch coverage is 36.97334% with 733 lines in your changes are missing coverage. Please review.

Project coverage is 57.63%. Comparing base (3ad0c73) to head (951a79c).
Report is 174 commits behind head on release51.

❗ Current head 951a79c differs from pull request most recent head a793397. Consider uploading reports for the commit a793397 to get more accurate results

Files Patch % Lines
meteor/server/publications/partsUI/publication.ts 0.00% 277 Missing ⚠️
...c/playout/model/implementation/PlayoutModelImpl.ts 16.58% 181 Missing ⚠️
...ver/publications/partsUI/rundownContentObserver.ts 0.00% 72 Missing ⚠️
...erver/publications/partsUI/reactiveContentCache.ts 0.00% 51 Missing ⚠️
meteor/server/api/rest/v1/typeConversion.ts 0.00% 44 Missing ⚠️
...del/implementation/PlayoutPartInstanceModelImpl.ts 21.42% 44 Missing ⚠️
meteor/lib/Rundown.ts 43.24% 21 Missing ⚠️
packages/job-worker/src/rundownPlaylists.ts 31.25% 11 Missing ⚠️
meteor/client/lib/rundown.ts 73.91% 5 Missing and 1 partial ⚠️
packages/job-worker/src/playout/selectNextPart.ts 92.30% 5 Missing ⚠️
... and 9 more
Additional details and impacted files
@@              Coverage Diff              @@
##           release51    #1112      +/-   ##
=============================================
- Coverage      57.74%   57.63%   -0.12%     
=============================================
  Files            512      520       +8     
  Lines          82571    84258    +1687     
  Branches        4307     4399      +92     
=============================================
+ Hits           47679    48559     +880     
- Misses         34841    35643     +802     
- Partials          51       56       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jstarpl jstarpl changed the title feat: Implement QuickLoop feat: Implement QuickLoop (SOFIE-2878) Jan 22, 2024
@jstarpl jstarpl linked an issue Feb 9, 2024 that may be closed by this pull request
5 tasks
@ianshade ianshade marked this pull request as ready for review February 15, 2024 11:02
@jstarpl jstarpl requested a review from a team February 16, 2024 10:22
Copy link
Member

@jstarpl jstarpl left a comment

Choose a reason for hiding this comment

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

I've went through 1/3 of the files, here are my early comments.

meteor/client/lib/__tests__/rundownTiming.test.ts Outdated Show resolved Hide resolved
meteor/client/lib/rundownTiming.ts Outdated Show resolved Hide resolved
meteor/client/ui/RundownView.tsx Outdated Show resolved Hide resolved
Copy link
Member

@Julusian Julusian left a comment

Choose a reason for hiding this comment

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

I've gone through 50% of the files (everything backend)

I want to think some more on the overrideProperties stuff, expect some thoughts on that later

* Original values of properties overriden by some features.
* Currently this only supports the QuickLoop
*/
overridenProperties?: Partial<NullableProps<IBlueprintPart>>
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure that I like this.
I am not convinced that it can be used by anything else safely, because it needs to be possible to 'undo' the overrides when leaving quickloop, which will be hard if the contents of this are partially from some unrelated feature.

I don't have any good ideas for alternative approaches right now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Notes from discussion:
Could we move this to the PartInstance instead? It seems that it's only used in playout-cases where we should have a PartInstance?

More questions:

  • should adlibActions see the overridden or original value when aorking on the PartInstance?
  • when we sync changes to PartInstance, are the overridenProperties taken into account?

TODOs:

  • rename to overriddenProperties
  • change the type to not be as generic. {expectedDuration, autoNext} would be enough to achieve this use case, and limits the future scope.

Copy link
Member

@Julusian Julusian Apr 12, 2024

Choose a reason for hiding this comment

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

After discussion, I am leaning towards that this will be rather hard to ensure behaviour is correct in all scenarios.
See the questions in the comment above.

It feels like persisting these changes could be avoided and calculated on the fly. For the ui, this could be achieved by a custom UIPartInstances publication (to save having to chase the change through hundreds of references). For the backend, only the playout logic should be interested in this and I can't imagine there to be too many references to the properties which could be overridden, so changing these to use some helper functions to on the fly calculate the overrides and use those may not be too painful
I am open to other solutions too

/**
* A playout UI version of Parts.
*/
export const UIParts = createSyncCustomPublicationMongoCollection(CustomCollectionName.UIParts) // TODO
Copy link
Member

Choose a reason for hiding this comment

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

todo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's more of a to-ask-you, whether this is the right place for it. This collection stands out as the only UI collection in meteor/lib, while the other are in meteor/client, but it works, so I hope it's fine...? It's like that because this collection is used by RundownPlaylistCollectionUtil

Copy link
Member

Choose a reason for hiding this comment

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

Hmm yeah, it probably shouldn't be in meteor/lib as it wont work from the server-side.
But moving it is a little non-trivial as it gets used by RundownPlaylistCollectionUtil which has other methods which have to reside in lib.

I suppose the proper solution would be to move the methods which need this UIParts into a client specific lib file. @jstarpl any thoughts on this and suggestions on where to put it?

Copy link
Member

Choose a reason for hiding this comment

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

It looks like the places this could be called from are all in the client, via a few methods in lib.
So I think the solution here is to verify this and move the relevant code into the client

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, it indeed turned out possible! I addressed this in 36dc3ce

packages/job-worker/src/playout/take.ts Show resolved Hide resolved
packages/job-worker/src/playout/take.ts Outdated Show resolved Hide resolved
packages/job-worker/src/playout/timings/partPlayback.ts Outdated Show resolved Hide resolved
meteor/server/publications/partsUI/publication.ts Outdated Show resolved Hide resolved
meteor/server/publications/partsUI/publication.ts Outdated Show resolved Hide resolved
Comment on lines 46 to 48
invalidateRundownIds: RundownId[]
invalidateSegmentIds: SegmentId[]
invalidatePartIds: PartId[]
Copy link
Member

Choose a reason for hiding this comment

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

These properties don't appear to be used, only set

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as #1112 (comment)

Comment on lines 125 to 126
// We know that `collection` does diffing when 'commiting' all of the changes we have made
// meaning that for anything we will call `replace()` on, we can `remove()` it first for no extra cost
Copy link
Member

Choose a reason for hiding this comment

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

this is true, but when we know that a select parts/segments have changed it would be nice to not recalculate every part in the playlist.
While the publication won't emit those which have not changed, that relies on doing a deep-equals check of each part. As the publicData can be arbitrarily large, this may not be cheap when performing this for hundreds of parts

Copy link
Contributor Author

@ianshade ianshade Feb 26, 2024

Choose a reason for hiding this comment

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

This code comment is taken from other publications. Are you're suggesting that I actually use invalidatePartIds and so on, mentioned in your previous comment? I eventually decided to invalidate everything every time. I assumed that it is relatively cheap because diffObject was doing a shallow comparison of objects' properties, as its documentation would suggest: * Perform a shallow diff of a pair of objects

Copy link
Member

Choose a reason for hiding this comment

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

I think the docs of that function are misleading/incorrect? It is calculating a shallow difference, but it is comparing each property with a deep equality check.
But even so, inside the replace call, it is doing a clone of whatever you pass in, so its never going to be a cheap/quick diff.

So yes, I think this does need to be cleverer on what it updates each time it is run.
At least handling the cases which can be easily cheap, such as when a part/segment changes it shouldnt need to invalidate everything. When the playlist quickLoop prop changes, I expect this could get very complicated to figure out exactly what should be invalidated. But it also won't change too often, so could get away with being less optimal.

Or perhaps take inspiration from what I did in the PieceContentStatus publication. Another approach would be to re-generate each Part as it changes. Then each time the publication changes, iterate through each part in the collection and update just the properties which quickloop may override. Be aware that the updateAll and updateOne methods of the collection don't perform any diffing themselves (and will always perform a clone), so manually diffing of the changes will be required to avoid making the ddp layer doing a deep comparison

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is addressed in a793397. Please let me know what you think.

@jstarpl jstarpl requested a review from a team March 12, 2024 10:26
@jstarpl jstarpl requested a review from a team March 19, 2024 16:33
@nytamin nytamin self-requested a review April 4, 2024 09:35
@jstarpl
Copy link
Member

jstarpl commented Apr 22, 2024

A workshop has been held on 2024/04/12 and the contributor will still address the comments above.

Copy link
Member

Choose a reason for hiding this comment

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

empty file

}
}

function stringsToIndexLookup(strings: string[]): Record<string, number> {
Copy link
Member

Choose a reason for hiding this comment

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

It might be nicer if this returned a Map, then it could preserve the RundownId or whatever ProtectedString typings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Contribution from BBC Contributions sponsored by BBC (bbc.co.uk)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

RFC: Updating the Looping Feature with Per-Part Looping
4 participants