Skip to content

Commit

Permalink
v1.0.0-beta.3 (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
axelboberg authored Feb 24, 2025
1 parent 8c1e08c commit 5eb73b8
Show file tree
Hide file tree
Showing 20 changed files with 384 additions and 105 deletions.
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 0.0.1
## 1.0.0-beta.3
### Fixed
- An issue where context menus would get stuck when triggered by rundown items
### Added
- Time indicators in the rundown when items are scheduled or triggered
- On end actions for playable items
- An onEnd event
- A note regarding supported Caspar server versions in settings

## 1.0.0-beta.2
### Changed
- Updated UI
### Fixed
- General bug fixes

## 1.0.0-beta.1
### Changed
- Initial development version
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ Extendable and lightweight playout software

## Goals

- [ ] ⚡️ To be a server agnostic, lightweight and quick playout client
- [ ] 🧑‍💻 To be easily extendable with plugins and encourage contribution
- [ ] 💪 To provide a reliable backbone for productions
- [x] ⚡️ To be a server agnostic, lightweight and quick playout client
- [x] 🧑‍💻 To be easily extendable with plugins and encourage contribution
- [x] 💪 To provide a reliable backbone for productions

## Table of contents
- [Goals](#goals)
- [Motivation](#motivation)
- [Features](#features)
- [Compatibility](#compatibility-notes)
- [Download and install](#download-and-install)
- [Documentation and API](#full-documentation-and-api)
- [Security](#security)
Expand All @@ -31,16 +32,20 @@ As developers of production software we found ourselves in a position of rebuild

## Features

- [ ] Real-time sync for multiple operators
- [ ] A fully customizable grid layout
- [ ] Variables
- [ ] Item references
- [ ] Sub-frame accurate timing
- [ ] Multi-threaded architecture
- [ ] Nested groups
- [ ] Multiple rundowns per project
- [ ] Shotbox-style buttons
- [ ] OSC API
- HTTP Web interface for remote use
- A fully customizable grid layout
- Variables
- Item references
- Sub-frame accurate timing
- Multi-threaded architecture
- Nested groups
- Multiple rundowns per project
- Shotbox-style buttons
- OSC API

## Compatibility notes
- Bridge works with Caspar CG Server 2.3 and up.
- Bridge provides data to HTML templates as JSON.

## Download and install
Built binaries are available on the releases page.
Expand Down
4 changes: 2 additions & 2 deletions app/components/ContextMenu/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const ContextMenu = ({ x, y, children, onClose = () => {} }) => {

React.useEffect(() => {
openTimestampRef.current = Date.now()
}, [])
}, [x, y])

React.useEffect(() => {
function closeContext () {
Expand All @@ -45,7 +45,7 @@ export const ContextMenu = ({ x, y, children, onClose = () => {} }) => {
window.removeEventListener('click', closeContext)
window.removeEventListener('contextmenu', closeContext)
}
}, [onClose])
}, [x, y, onClose])

/*
Make sure that the menu open in the direction
Expand Down
1 change: 1 addition & 0 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ bridge.events.on('item.stop', item => {
| `state.change` | Emitted every time the remote state changes |
| `item.play` | Emitted when an item is played, after an optional delay |
| `item.stop` | Emitted when an item is stopped |
| `item.end` | Emitted when an item ends, this will not trigger when an item is stopped |
| `shortcut` | Emitted when a shortcut is triggered |
| `selection` | Emitted when the selection of the current client changes |

Expand Down
24 changes: 24 additions & 0 deletions lib/api/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ function factory (api, workspace) {

api.commands.executeCommand('scheduler.abort', undefined, `play:${item.id}`)

/*
Schedule a call to items.endItem if there's
a specified delay and/or duration
so that we can fire the item.end event
*/
const endDelay = Math.max(item?.data?.duration || 0, 0)
if (!Number.isNaN(endDelay) && endDelay > 0) {
api.commands.executeCommand('scheduler.delay', undefined, `end:${item.id}`, endDelay, 'items.endItem', item)
} else {
api.commands.executeCommand('scheduler.abort', undefined, `end:${item.id}`)
endItem(item)
}

workspace.state.apply({
items: {
[item.id]: {
Expand All @@ -36,6 +49,15 @@ function factory (api, workspace) {
api.events.emit('item.play', item)
}

/**
* Mark the end of an item
* and emit the item.end event
* @param { Item } item
*/
function endItem (item) {
api.events.emit('item.end', item)
}

/**
* Schedule an item to be
* played after a certain delay
Expand Down Expand Up @@ -77,6 +99,7 @@ function factory (api, workspace) {
}

api.commands.executeCommand('scheduler.abort', undefined, `play:${item.id}`)
api.commands.executeCommand('scheduler.abort', undefined, `end:${item.id}`)

workspace.state.apply({
items: {
Expand Down Expand Up @@ -138,6 +161,7 @@ function factory (api, workspace) {
api.items.getItem = getItem
api.items.deleteItems = deleteItems

api.commands.registerAsyncCommand('items.endItem', endItem)
api.commands.registerAsyncCommand('items.playItem', playItem)
api.commands.registerAsyncCommand('items.stopItem', stopItem)
api.commands.registerAsyncCommand('items.scheduleItem', scheduleItem)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bridge",
"version": "1.0.0-beta.2",
"version": "1.0.0-beta.3",
"description": "",
"main": "index.js",
"scripts": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/caspar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ exports.activate = async () => {
bridge.settings.registerSetting({
title: 'Server',
group: 'Caspar CG',
description: 'Configure Caspar servers',
description: 'Configure Caspar servers (supports server v2.3 and up)',
inputs: [
{ type: 'frame', uri: `${htmlPath}?path=settings/servers` }
]
Expand Down
27 changes: 27 additions & 0 deletions plugins/caspar/lib/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,30 @@ bridge.events.on('item.stop', async item => {
.catch(err => logger.warn(err.message))
}
})

/*
Handle the item.end event to keep indicating
that an item is looping, if it is looping
This will recursively schedule
the endItem function
*/
bridge.events.on('item.end', async coldItem => {
if (!coldItem?.id) {
return
}

const hotItem = await bridge.items.getItem(coldItem.id)
if (!hotItem?.data?.caspar?.loop) {
return
}

bridge.items.applyItem(hotItem.id, {
didStartPlayingAt: Date.now()
})

const endDelay = Math.max(hotItem?.data?.duration || 0, 0)
if (!Number.isNaN(endDelay)) {
bridge.commands.executeCommand('scheduler.delay', `end:${hotItem.id}`, endDelay, 'items.endItem', coldItem)
}
})
2 changes: 2 additions & 0 deletions plugins/rundown/app/components/RundownGroupItem/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './style.css'
import { SharedContext } from '../../sharedContext'

import { RundownItemIndicatorsSection } from '../RundownItemIndicatorsSection'
import { RundownItemTimeSection } from '../RundownItemTimeSection'
import { RundownItemProgress } from '../RundownItemProgress'
import { RundownList } from '../RundownList'
import { Icon } from '../Icon'
Expand Down Expand Up @@ -147,6 +148,7 @@ export function RundownGroupItem ({ index, item }) {
</div>
<div className='RundownGroupItem-lastProperty'>
<RundownItemIndicatorsSection item={item} />
<RundownItemTimeSection item={item} />
</div>
<RundownItemProgress item={item} />
</div>
Expand Down
1 change: 1 addition & 0 deletions plugins/rundown/app/components/RundownGroupItem/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
}

.RundownGroupItem-lastProperty {
display: flex;
margin-left: auto;
}

Expand Down
96 changes: 50 additions & 46 deletions plugins/rundown/app/components/RundownItem/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { SharedContext } from '../../sharedContext'
import { useAsyncValue } from '../../hooks/useAsyncValue'

import { RundownItemProgress } from '../RundownItemProgress'
import { RundownItemTimeSection } from '../RundownItemTimeSection'
import { RundownItemIndicatorsSection } from '../RundownItemIndicatorsSection'

import * as Layout from '../Layout'
Expand Down Expand Up @@ -118,54 +119,57 @@ export function RundownItem ({ index, item }) {

return (
<div className='RundownItem'>
<Layout.Spread>
<div className='RundownItem-section'>
<div className='RundownItem-color' style={{ backgroundColor: item?.data?.color }} />
<div className='RundownItem-background' style={{ backgroundColor: item?.data?.color }} />
<div className='RundownItem-index'>
{index}
<div className='RundownItem-margin'>
<Layout.Spread>
<div className='RundownItem-section'>
<div className='RundownItem-color' style={{ backgroundColor: item?.data?.color }} />
<div className='RundownItem-background' style={{ backgroundColor: item?.data?.color }} />
<div className='RundownItem-index'>
{index}
</div>
<div className='RundownItem-name'>
{name}
</div>
{
displaySettings?.notes &&
(
<div className='RundownItem-notes'>
{item?.data?.notes}
</div>
)
}
</div>
<div className='RundownItem-name'>
{name}
<div className='RundownItem-section RundownItem-section--right'>
{
([...properties, ...typeProperties])
.filter(property => property.if)
.map((property, i) => {
/*
Either read the value directly from
the property or use its bind path
to get it from the item object
*/
let value = property?.value
if (property?.bind) {
value = objectPath.get(item, property?.bind)
}

return (
<div className='RundownItem-property' key={i}>
{
!property.hiddenName &&
<div className='RundownItem-propertyName'>{property.name}:</div>
}
<div>{value}</div>
</div>
)
})
}
</div>
{
displaySettings?.notes &&
(
<div className='RundownItem-notes'>
{item?.data?.notes}
</div>
)
}
</div>
<div className='RundownItem-section RundownItem-section--right'>
{
([...properties, ...typeProperties])
.filter(property => property.if)
.map((property, i) => {
/*
Either read the value directly from
the property or use its bind path
to get it from the item object
*/
let value = property?.value
if (property?.bind) {
value = objectPath.get(item, property?.bind)
}

return (
<div className='RundownItem-property' key={i}>
{
!property.hiddenName &&
<div className='RundownItem-propertyName'>{property.name}:</div>
}
<div>{value}</div>
</div>
)
})
}
</div>
<RundownItemIndicatorsSection item={item} />
</Layout.Spread>
<RundownItemIndicatorsSection item={item} />
<RundownItemTimeSection item={item} />
</Layout.Spread>
</div>
<RundownItemProgress item={item} />
</div>
)
Expand Down
6 changes: 5 additions & 1 deletion plugins/rundown/app/components/RundownItem/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
display: flex;
position: relative;
width: 100%;
padding: 1em;
}

.RundownItem-margin {
margin: 1em;
width: 100%;
}

.RundownItem-section {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Icon } from '../Icon'

import './style.css'

const ON_PLAY_ENUM = {
const ACTIONS_ENUM = {
'SELECT_NEXT_ITEM': '2',
'PLAY_NEXT_ITEM': '1'
}
Expand All @@ -13,11 +13,18 @@ export function RundownItemIndicatorsSection ({ item }) {
return (
<div className='RundownItemIndicatorsSection'>
{
item?.data?.onPlay === ON_PLAY_ENUM.SELECT_NEXT_ITEM &&
(
item?.data?.onPlay === ACTIONS_ENUM.SELECT_NEXT_ITEM ||
item?.data?.onEnd === ACTIONS_ENUM.SELECT_NEXT_ITEM
)
&&
<span className='RundownItemIndicatorsSection-icon'><Icon name='arrowDownSecondary' /></span>
}
{
item?.data?.onPlay === ON_PLAY_ENUM.PLAY_NEXT_ITEM &&
(
item?.data?.onPlay === ACTIONS_ENUM.PLAY_NEXT_ITEM ||
item?.data?.onEnd === ACTIONS_ENUM.PLAY_NEXT_ITEM
) &&
<span className='RundownItemIndicatorsSection-icon'><Icon name='arrowDownPlay' /></span>
}
{
Expand Down
Loading

0 comments on commit 5eb73b8

Please sign in to comment.