-
Notifications
You must be signed in to change notification settings - Fork 55
Description
About Me
This RFC is posted on behalf of the BBC.
Use Case
We are addressing a scenario where a user may wish to control a video and/or audio mixer directly while the same system is otherwise being controlled by Sofie.
In this scenario a user does a take in Sofie which takes input A on air. The user proceeds to directly control the vision mixer and cut to input B. After this the user does another take in Sofie which should take input A on air again.
Note that in the current architecture Sofie is not aware of any external changes and will not send a command for the second take as it assumes input A is already on-air.
Proposal
Note: most of these changes are to be done in the Timeline State Resolver library, I am posting here as the use case is from Sofie's perspective and I believe posting here may give more visibility
Short gist: introduce tracking of both Actual State (i.e. what is currently on the device) and the Expected State (ie. what the state should be according to the timeline) for portions of the device (i.e. track each channel of an audio mixer separately). When the Actual State is updated to be different from the Expected State we may assume the device is being controlled externally and we “block” our control of this part of the device until some signal is received to retake control.
In order to achieve this:
- Introduce a StateTracker class in the TSR to track both the expected/internal and actual/external state of a device
- The Actual State is to be derived from information the device sends us, the Expected State is to be derived from the Timeline
- Track small parts of the entire state individually, i.e. a mix effect or audio channel. Call this an AddressState
- Each small part of state, the AddressState, must have an Address, an index by which we can look up the stored state
- Each Address can be blocked individually, when an Address is blocked the TSR integration is not allowed to control the part of the device described by the AddressState
- Each Address has a ControlValue, when this value changes the TSR integration should resume control of the part of the device described by the AddressState. This ControlValue can be a string expressed on a timeline object. Changing this string is the only way to unblock an Address currently. The external device can only block control and never unblock as we assume the external device does not understand what a TSR is.
- When the Actual State for an Address is updated, it shall be compared to the Expected State for that Address and when they are not the same the Address shall be blocked
An integration wanting to implement Shared Control shall implement the following:
- A function that can compare 2 AdressStates and returns a boolean value that shall be true if and only if the 2 states are not the same
- A mechanism that takes updates from the external device and converts into AddressStates
- A function that can generate AddressStates from an internal Device State (Note - a Device State is currently the output of the diffState method on the integration)
- Alternatively, the Address States may be generated in the same call that currently converts a Timeline State into a Device State
- A way to integrate an AddressState into it’s state diff mechanism
- This will be in the form of a function that can take a Device State and an array of Address States and override the Device State with the contents of the Address State.
- This process must also be able to understand when the Device State will not be overridden - i.e. the Address shall be unblocked by this state change.
The TSR shall provide
- Tracking of the current Expected State for every Address
- Blocking Addresses
- Based on the output of the diff function provided by the integration
The general flow shall be as follows
- A new timeline state is generated from the timeline
- The integration transforms the timeline state into a device state, as well as Address States. Note - there may be devices that wish to only use AddressStates so perhaps an option here to omit the device state completely?
- The StateHandler overrides any parts in the device state that are blocked using the Address States
- For each address, a function is called to decide whether the address should still be blocked after the state change (As of yet I’m a bit uncertain whether the Control Value should be something inside of the Address State or separately tracked in the StateTracker, I’ll probably try out both for the final implementation and see what works better)
- The integration performs a diff on the current expected state and the next expected state, resulting in commands
- The blocked addresses are already overridden in the states here, the integration does not have to understand whether something is blocked nor why it is.
- When the next state becomes active, the integration sends each command to the device
- The command sending logic has no knowledge of the current state, any blocking is done by adjusting the states fed to the diff
- Internally, the StateHandler will update the Expected State on the State Tracker by using the Address States provided in step 2.
Note: we could provide some standard state and diff functions that could be replaced by integrations if they want to? i.e. atem needs a special device state object for the libraries but sisyfos might just be able to only use AddressStates
The reverse flow (ie. the device feeding updates to TSR) shall be as follows:
- The integration receives an update from the device on its connection
- The integration creates an Address State corresponding to this update
- The created address state is stored in the state tracker, if the address is not blocked it is fed to a diff function together with the expected state. If the new actual state is different from the expected state the address is blocked
Sequence Diagram
Timing and edge cases
Race conditions are inevitable when dealing with concurrent systems. When the integration updates the actual state we must decide whether that update came from TSR or an external system. The current prototype implementation aims to mitigate some of these issues by introducing a delay. It always waits 200ms before deciding if an Address is to be blocked. The exact amount of how long to wait is not set in stone for this RFC and perhaps should be configurable per integration. A lower value could reduce delay in blocking the Address, that being said this is only relevant when a user is controlling the device just before the automation sends a command. In the current use case that is not as likely.
If the user manually sets an input while Sofie expects another but then switches back before Sofie has unblocked the Address the Address currently remains blocked. Once we are in a blocked state for the Address only a change in Control Value can unblock the Address.
An important thing to consider here is that the integration needs to be able to receive consistent updates from the external device for this to work. If the integration sends a command based on the the state changing and the device sends a response back that is inconsistent with the integration's expectation the Address would be blocked until a new control value is received. Depending on how the Control Value is used by the library user (or blueprint developer in Sofie's case) this may cause problematic and unexpected behaviour. One solution could be to expose an option to disable Shared Control, this would not be ideal but at least give users a mitigation that does not involve a bug fix in code. Alternatively, we could consider ignoring any updates that follow directly after we send a command although that could also introduce additional race conditions if the user were to override an erroneous automation decision. (TBD what "Directly" means)
Future extensions
- This RFC describes the Control Value being expressed on the Timeline, currently this in the form of a timeline object but it could also be using a timeline object's id, start time or some other property. Additionally, an extension could be made where a Control Value could be changed by using a TSR Action. This would give the user more explicit control over when control should be given back to Sofie without having to manipulate the timeline. The integration developer would have to take special care to understand how the control values coming from the timeline and the values coming from the action would interact in that case.
- It may be useful for the user to be able to explicitly be informed in the Sofie UI that a certain part of a device is not fully under control of the automation. Currently Sofie is not aware of Addresses so either it needs to become aware of this or alternatively we could inform Sofie that a mapping is no longer fully under control. Sofie could then attach this information to pieces that are affected by this.
- If the blueprints would be aware of how a device's state has been changed by the user it could react to it by manipulating pieces in Sofie and it would be possible to implement features such as AFV. This could perhaps be done by exposing the interfaces of the Address States in the timeline-state-resolver-types package. I think this would be a larger undertaking and probably quite far out of scope for now.
Prototype
This RFC is based on the work done in a prototype done for BBC. Note that this prototype doesn't have the same abstractions as described in this RFC but the basic mechanisms of state tracking, diffing and blocking are the same.
Process
The Sofie Team will evaluate this RFC and open up a discussion about it, usually within a week.
- RFC created
- Sofie Team has evaluated the RFC
- A workshop has been planned
- RFC has been discussed in a workshop
- A conclusion has been reached, see comments in thread