If you are creating a custom layer, there is the possibility of even including custom control presets with your layer. This come in handy if your layer has push buttons that controls the behaviour of your layer when its live.

Data Structure

The following data structure is a sample output which needs to be present in your composition under the `tvOut_ControlRepresentations` structure output key:

{
  "tvOut_ControlRepresentations": [
    {
      "title": "Compact Cut's",
      "buttons": [
        {
          "actions": [
            {
              "layerId": "03627C25-CC8A-474F-A738-C4DEB3353FA4",
              "variantId": "F6F52194-06DE-462E-983A-8D505C533A69",
              "type": "signal",
              "signalKey": "tvGroup_Control__Cut_1_TypeSignal",
              "signalName": "Cut 1"
            }
          ],
          "liveState": {
            "layerId": "03627C25-CC8A-474F-A738-C4DEB3353FA4",
            "variantId": "F6F52194-06DE-462E-983A-8D505C533A69",
            "dynamicSourceId": "tvIn_VideoSourceAImage"
          },
          "sourcePreview": {
            "layerId": "03627C25-CC8A-474F-A738-C4DEB3353FA4",
            "variantId": "F6F52194-06DE-462E-983A-8D505C533A69",
            "dynamicSourceId": "tvIn_VideoSourceAImage"
          },
          "color": "#BBBBBB",
          "width": 1,
          "height": 1,
          "x": 0,
          "y": 0,
          "icon": "facetime-video",
          "dynamicText": {
            "template": "{{inputputValues.tvIn_VideoSourceAName}}",
            "layerId": "03627C25-CC8A-474F-A738-C4DEB3353FA4",
            "variantId": "F6F52194-06DE-462E-983A-8D505C533A69"
          }
        }
      ]
    }
  ]
}

A preset has a “title” and an array of “buttons”, but one single button is also ok.

Using the Remote Control Preset API in Quartz Composer

The build in layers of mimoLive are using a LUA script to dynamical generate this data structure because Quartz Composer doesn’t support generating data structures on it own. The LUA script contains a complex function addButton() that can be used to create the structure for a single button. Usually you just can use this function and don’t need to modify it. Therefor it is quite easy to build new remote control default settings for your own layer.

For example this is the LUA script to generate the remote controls for the Stop Watch layer. Please notice that you can adopt this code by just changing the lines 15 through 29 of the code.

inLayerIdentifier = QC_STRING
inVariantIdentifier = QC_STRING
outControlRepresentations = QC_STRUCT
main = function()
  controlRepresentations = {}
  -- first representation
  controlRepresenation = {}
  -- {
    controlRepresenation["Title"] = "Stop Watch"
    buttons = {}
    -- {
      -- addButton(buttonsTable, buttonTitle, subTitle, positionX, positionY, width, height, color, icon, actionTarget, liveStateSource, previewSource)
      -- first row
      addButton(buttons, "{{outputValues.tvOut_TimeString}}", "", 0, 0, 2, 1, "#333333", "", "", "", "")
      addButton(buttons, "Live", "{\{name\}}", 2, 0, 1, 1, "#333333", "", "toggleLive", "layerVariant", "")
      -- second row
      addButton(buttons, "Start", "", 0, 1, 1, 1, "#33cc33", "play", "tvGroup_Control__Start_TypeSignal", "", "")
      addButton(buttons, "Stop", "", 1, 1, 1, 1, "#cc3333", "pause", "tvGroup_Control__Stop_TypeSignal", "", "")
      addButton(buttons, "Reset", "", 2, 1, 1, 1, "#cccc33", "stop", "tvGroup_Control__Reset_TypeSignal", "", "")
    -- }
    controlRepresenation["buttons"] = buttons
  -- }
  table.insert(controlRepresentations , controlRepresenation)
  outControlRepresentations = controlRepresentations
end
function addButton(buttonsTable, buttonTitle, buttonSubTitle, positionX, positionY, width, height, color, icon, actionTarget, liveStateSource, previewSource)
  local button = {}
  -- {
  if #buttonTitle > 0 then
    if string.find(buttonTitle, "{{") then
      -- dynamic titel
      local dynamicText = {}
      -- {
        dynamicText["layerId"] = inLayerIdentifier
        dynamicText["variantId"] = inVariantIdentifier
        dynamicText["template"] = buttonTitle
      -- }
      button["dynamicText"] = dynamicText
    else
      -- static title
      button["text"] = buttonTitle
    end -- if dynamic
  end -- if buttonTitle
  if #buttonSubTitle > 0 then
    if string.find(buttonSubTitle, "{{") then
      -- dynamic titel
      local dynamicSubText = {}
      -- {
        dynamicSubText["layerId"] = inLayerIdentifier
        dynamicSubText["variantId"] = inVariantIdentifier
        dynamicSubText["template"] = buttonSubTitle
      -- }
      button["dynamicSubText"] = dynamicSubText
    else
      -- static title
      button["subtext"] = buttonSubTitle
    end -- if dynamic
  end -- if buttonSubTitle
  button["x"] = positionX
  button["y"] = positionY
  button["width"] = width
  button["height"] = height
  if #color > 0 then
    button["color"] = color
  end -- if
  if #actionTarget > 0 then
    local actions = {}
    -- {
      local action = {}
      -- {
        action["layerId"] = inLayerIdentifier
        action["variantId"] = inVariantIdentifier
        if string.ends(actionTarget, "_TypeSignal") then
          action["type"] = "signal"
          action["signalKey"] = actionTarget
          action["signalName"] = buttonTitle
        elseif actionTarget == "toggleLive" then
          action["type"] = "toggleLive"
        else
          action["type"] = "toggleSwitch"
          action["toggleSwitchKey"] = actionTarget
          action["toggleSwitchName"] = buttonTitle
        end -- if
      -- }
      table.insert(actions , action)
    -- }
    button["actions"] = actions
  end -- if actionTarget
  if #liveStateSource > 0 then
    local liveState = {}
    -- {
      liveState["layerId"] = inLayerIdentifier
      liveState["variantId"] = inVariantIdentifier
      if string.starts(liveStateSource,"tvIn_VideoSource") then
        -- get the live state from a dynamic video source
        liveState["dynamicSourceId"] = liveStateSource
      elseif liveStateSource == "layerVariant" then
        -- nor more information needed
      else
        liveState["propertyPath"] = liveStateSource            end -- if
    -- }
    button["liveState"] = liveState
  end -- if liveState Source
  if #previewSource > 0 then
    local sourcePreview = {}
    -- {
      sourcePreview["layerId"] = inLayerIdentifier
      sourcePreview["variantId"] = inVariantIdentifier
      sourcePreview["dynamicSourceId"] = previewSource
    -- }
    button["sourcePreview"] = sourcePreview
  end -- if
  if #icon > 0 then
    -- See bottom of documentation page for list of avaiable icon names
    button["icon"] = icon
  end -- if
  -- }
  table.insert(buttonsTable, button)
end -- func
function string.starts(String,Start)
   return string.sub(String,1,string.len(Start))==Start
end
function string.ends(String,End)
   return End=='' or string.sub(String,-string.len(End))==End
end

Button Positioning

When using multiple buttons, the button layout has to be controlled manually. That means that buttons are positioned on the grid using “x” and “y” attributes, which, just as the “width” and “height” attributes, are given in multiples of grid cells. Positions are relative to the top left grid cell the button group was dragged onto.

Button Actions

Buttons can have multiple actions, one single action, or none at all. There are various valid combinations of action targets and action types.
Note that “textInput” and “numericInput” actions should be on their own, as a consistent user experience in the remote control is not guaranteed.

Action Targets

Action targets are elements inside the mimoLive document. They are addressed in a hierarchical way:

The “document ID” is always drawn at runtime, so it has not to be present in the preset.

  • If neither “layerId” nor “variantId” are given, the action will be triggered on the document itself.
  • If “layerId” is given, the action will be triggered on the specified layer.
  • If “layerId” and “variantId” are given, the action will be triggered on the specified variant. Only in this case, the additional “toggleSwitchKey” or “signalKey” may be specified (see Action Types below).

Action Types

Action Type Explanation Additional Attributes Valid Targets
toggleLive Toggle live state none Document (Start/Stop show), Layer, Variant
setLive Set live state to “live” none Document (Start Show), Layer, Variant
setOff Set live state to “off” none Document (Stop Show), Layer, Variant
signal Trigger a signal signalKey, signalName (recommended) Variant
toggleSwitch Toggle a boolean input value toggleSwitchKey, toggleSwitchName (recommended) Variant
textInput Opens a text input field that, on submit, updates the value at inputKey. inputKey, inputName (recommended) Layer, Variant, Source
numericInput Slider that controls a numeric value at inputKey. inputKey, inputDescription (object with both min and max values), inputName (recommended) Layer, Variant, Source

Button Live State

The “liveState” object can be used to configure the button’s tally light state, which can be on (red marking), transition (yellow marking) or off (no marking).

Live State Sources

Similar to button action targets, the live state can be obtained from multiple sources:
  • The “document ID” is always drawn at runtime, so it has not to be present in the preset.
  • If neither “layerId” nor “variantId” are given, the document’s live state is used (show has started or not).
  • If “layerId” is given, the layer’s live state is used.
  • If “layerId” and “variantId” are given, the variant’s live state is used. – Only in this case, the additional “propertyPath” may be specified to indicate that a boolean property on the variant’s input or output values has to be used. – Another possibility (mutually exclusive with propertyPath!) is to specify a “dynamicSourceId” property that redirects the live state lookup to a source (fetched by ID), which respects dynamic changes of the selected source (e.g. in the video switcher). The value of “dynamicSourceId” is looked up in the variant’s input values.
  • If “sourceId” is given, the source’s live state is used.

Button Text

Action buttons can display primary and secondary lines of text, both may be of static or dynamic nature.

Primary Text

This text will be the primary indicator what the button will do for the user. If the button preset contains a text property, it is a static text that will be shown as-is.

Alternatively, a dynamicText can be given which enables the inclusion of mimoLive layer or variant properties using a simple templating syntax. In the template string, regular characters can be combined with property paths enclosed in curly braces:

"template": "{\{propertyPath\}}"

Property paths are evaluated relative to the object that is resolved using “layerId”, “variantId”, “sourceId” and “filterId”. Target resolution follows these rules:

  • The “document ID” is always drawn at runtime, so it has not to be present in the preset.
  • If neither “layerId” nor “variantId” are given, the property paths are evaluated relative to the document
  • If “layerId” (and optionally “variantId”) is given, the property paths are evaluated relative to a layer/variant.
  • A variant’s parent layer can be accessed by starting a path with “layer”, such “aslayer.name”.
  • Static text will always take precedence over dynamic text, because this allows users to overwrite the dynamic text with some custom text that might be more useful in their situation.

Secondary Text

To give the user more information about a button, such as its parent layer’s name, a secondary text can be given using the “subtext” or “dynamicSubText” properties.

Templating syntax and object resolution follow the exact same rules as the primary text.

Button Text And Icon Size

The text an icons on buttons may vary in size base on the context. With the option “fontScale” you can specify a relative scale to a default size calculated for the current screen size. “1” will be the default size. Less than 1 (e.g. 0.8) will downscale the font size and greater than on (e.g. 1.5) will increase it.

Button Preview

Action buttons can display a preview image of the source they are controlling, or any other source available in the document.

Depending on whether the source is static (only images that were dragged into mimoLive), a static PNG representation of the source is loaded once which does support an alpha channel. For any other source, a JPEG representation of the source is updated regularly. The target refresh rate for dynamic sources is 5 frames per second, but this rate is reduced when network connection speed is found to be insufficient for the repeated loading of preview frames.

It is currently not supported to display the document’s program output on an action button. Layer default images, though not visible in the list of sources, are available for preview.

To specify a source preview, an object resolution mechanism is used:

  • The “document ID” is always drawn at runtime, so it has not to be present in the preset.
  • If “sourceId” is given, the source with that ID is used for preview.
  • If all of “layerId”, “variantId”, and “dynamicSourceId” are given, the source’s ID will be looked up on the specified layer’s variant under the key in “dynamicSourceId”. This can be used to reference sources like the video switcher’s source A-G, even when they are changed in the document.

Button Color

By using the “color” attribute, buttons can be colored with any color code that is representable in HTML (e.g. “#f80”, “#ff0088”, “orange”, or “rgb(255, 66, 88)”).

Button Icon

Buttons can display an icon to indicate their functionality. To see a list of all available icons, open http://localhost:8989/icons/demo.html while mimoLive is running and remote controls are enabled.
Use the plain name of the icon. e.g. “backward” or “arrow-down”.

Feedback

Was this helpful?

Yes No
You indicated this topic was not helpful to you ...
Could you please leave a comment telling us why? Thank you!
Thanks for your feedback.

Post your comment on this topic.

Post Comment