Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into chore/tsr-r49
Browse files Browse the repository at this point in the history
  • Loading branch information
ianshade committed Oct 6, 2023
2 parents 8959ff4 + a43a451 commit 7ff8a5c
Show file tree
Hide file tree
Showing 28 changed files with 14,197 additions and 13,847 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/create-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ jobs:
build-macos-binary-superconductor:
name: Build MacOS Binary - SuperConductor
runs-on: macos-latest
needs: [create-release]
needs: [create-release, build-macos-binary-TSR-bridge]
steps:
- uses: actions/checkout@v3
- name: Use Node.js 18
Expand Down
37 changes: 19 additions & 18 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
node_modules
dist

# env vars
/.env

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.vscode/
node_modules
dist

# env vars
/.env

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.vscode/*
!.vscode/launch.json
11 changes: 11 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Electron Debug",
"type": "node",
"request": "attach",
"port": 9229
}
]
}
2 changes: 1 addition & 1 deletion apps/app/nodemon.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"watch": ["src/**/*"],
"ignore": ["*.test.ts", "src/react/*", "README"],
"exec": "tsc -p tsconfig.electron.json && electron ./dist/main.js",
"exec": "tsc -p tsconfig.electron.json && electron ./dist/main.js --inspect=9229",
"ext": "ts"
}
13 changes: 7 additions & 6 deletions apps/app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "superconductor",
"private": true,
"version": "0.12.0-alpha.0",
"version": "0.12.0-alpha.2",
"description": "A playout client for Windows, Linux or MacOS to control CasparCG, Atem, OBS and more!",
"main": "dist/main.js",
"engines": {
Expand Down Expand Up @@ -73,17 +73,18 @@
"@koa/router": "^12.0.0",
"@mui/icons-material": "^5.10.14",
"@mui/material": "^5.10.14",
"@shared/api": "^0.12.0-alpha.0",
"@shared/lib": "^0.12.0-alpha.0",
"@shared/models": "^0.12.0-alpha.0",
"@shared/server-lib": "^0.12.0-alpha.0",
"@shared/tsr-bridge": "^0.12.0-alpha.0",
"@shared/api": "^0.12.0-alpha.2",
"@shared/lib": "^0.12.0-alpha.2",
"@shared/models": "^0.12.0-alpha.2",
"@shared/server-lib": "^0.12.0-alpha.2",
"@shared/tsr-bridge": "^0.12.0-alpha.2",
"@sofie-automation/sorensen": "^1.4.2",
"axios": "^1.1.3",
"bufferutil": "^4.0.7",
"casparcg-connection": "^6.0.6",
"classnames": "^2.3.2",
"deep-extend": "^0.6.0",
"deepmerge-ts": "^5.1.0",
"electron-is-dev": "^2.0.0",
"electron-updater": "^5.3.0",
"file-loader": "^6.2.0",
Expand Down
3 changes: 2 additions & 1 deletion apps/app/src/electron/IPCServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
allowMovingPartIntoGroup,
copyGroup,
copyPart,
deepExtendRemovingUndefined,
deleteGroup,
deletePart,
deleteTimelineObj,
Expand Down Expand Up @@ -1613,7 +1614,7 @@ export class IPCServer
const timelineObjPreChange = deepClone(timelineObj)
const timelineObjIndex = findTimelineObjIndex(part, arg.timelineObjId)

if (arg.timelineObj.obj !== undefined) deepExtend(timelineObj.obj, arg.timelineObj.obj)
if (arg.timelineObj.obj !== undefined) deepExtendRemovingUndefined(timelineObj.obj, arg.timelineObj.obj)

postProcessPart(part)
this._saveUpdates({ rundownId: arg.rundownId, rundown, group })
Expand Down
170 changes: 170 additions & 0 deletions apps/app/src/lib/playout/__tests__/preparedGroupPlayData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,176 @@ describe('prepareGroupPlayData', () => {
expect(Object.keys(playData.countdowns)).toHaveLength(0)
}
})
test('When Group and Part A are looping, return all the way to Part A', () => {
const group0 = getTestGroup()
expect(group0.oneAtATime).toBeTruthy()
group0.loop = true

const partA = getPart(group0, 'partA')
partA.loop = true

const partB = getPart(group0, 'partB')

// Play Part B:
RundownActions.playPart(group0, partB, 1000)
postProcessGroup(group0, 1000)

{
const prepared = prepareGroupPlayData(group0, 1001)
if (!prepared) throw new Error('Prepared is falsy')

{
const playData = getGroupPlayData(prepared, 1001)

expect(playData).toMatchObject({
groupIsPlaying: true,
anyPartIsPlaying: true,
allPartsArePaused: false,
sectionTimeToEnd: 8999,
sectionEndTime: 10000,
sectionEndAction: SectionEndAction.NEXT_SECTION,
})
}
// Check that After B, C and D, it goes back to A:
{
const playData = getGroupPlayData(prepared, 10001)

expect(playData).toMatchObject({
groupIsPlaying: true,
anyPartIsPlaying: true,
allPartsArePaused: false,
sectionTimeToEnd: 999,
sectionEndTime: 11000,
sectionEndAction: SectionEndAction.LOOP_SELF,
})
expect(Object.keys(playData.playheads)).toHaveLength(1)
expect(playData.playheads['partA']).toMatchObject({
playheadTime: 1,
partStartTime: 10000,
partPauseTime: undefined,
partEndTime: 11000,
partDuration: 1000,
partId: 'partA',
endAction: PlayPartEndAction.LOOP_SELF,
fromSchedule: false,
})
expect(Object.keys(playData.countdowns)).toStrictEqual(['partA'])
}
}
})
test('When Group and Part B are looping, return all the way to Part B', () => {
const group0 = getTestGroup()
expect(group0.oneAtATime).toBeTruthy()
group0.loop = true

const partB = getPart(group0, 'partB')
partB.loop = true

const partC = getPart(group0, 'partC')

// Play Part C:
RundownActions.playPart(group0, partC, 3000)
postProcessGroup(group0, 3000)

{
const prepared = prepareGroupPlayData(group0, 3001)
if (!prepared) throw new Error('Prepared is falsy')

{
const playData = getGroupPlayData(prepared, 3001)

expect(playData).toMatchObject({
groupIsPlaying: true,
anyPartIsPlaying: true,
allPartsArePaused: false,
sectionTimeToEnd: 6999,
sectionEndTime: 10000,
sectionEndAction: SectionEndAction.NEXT_SECTION,
})
}
// Check that After C and D, it goes go back to A:
{
const playData = getGroupPlayData(prepared, 10001)

expect(playData).toMatchObject({
groupIsPlaying: true,
anyPartIsPlaying: true,
allPartsArePaused: false,
sectionTimeToEnd: 999,
sectionEndTime: 11000,
sectionEndAction: SectionEndAction.NEXT_SECTION,
})
expect(Object.keys(playData.playheads)).toHaveLength(1)
expect(playData.playheads['partA']).toMatchObject({
playheadTime: 1,
partStartTime: 10000,
partPauseTime: undefined,
partEndTime: 11000,
partDuration: 1000,
partId: 'partA',
endAction: PlayPartEndAction.NEXT_PART,
fromSchedule: false,
})
expect(Object.keys(playData.countdowns)).toStrictEqual(['partB'])
}
// Check that After A, it plays B:
{
const playData = getGroupPlayData(prepared, 11001)

expect(playData).toMatchObject({
groupIsPlaying: true,
anyPartIsPlaying: true,
allPartsArePaused: false,
sectionTimeToEnd: 1999,
sectionEndTime: 13000,
sectionEndAction: SectionEndAction.LOOP_SELF,
})
expect(Object.keys(playData.playheads)).toHaveLength(1)
expect(playData.playheads['partB']).toMatchObject({
playheadTime: 1,
partStartTime: 11000,
partPauseTime: undefined,
partEndTime: 13000,
partDuration: 2000,
partId: 'partB',
endAction: PlayPartEndAction.LOOP_SELF,
fromSchedule: false,
})
expect(Object.keys(playData.countdowns)).toStrictEqual(['partB'])
}
}
})
test('Group with looping, w/o autoPlay, stops after first started Part', () => {
const group0 = getTestGroup()
expect(group0.oneAtATime).toBeTruthy()
group0.autoPlay = false
group0.loop = true

const partB = getPart(group0, 'partB')

// Play Part B:
RundownActions.playPart(group0, partB, 1000)
postProcessGroup(group0, 1000)

{
const prepared = prepareGroupPlayData(group0, 1001)
if (!prepared) throw new Error('Prepared is falsy')

expect(prepared.sections.length).toBe(1)
{
const playData = getGroupPlayData(prepared, 1001)

expect(playData).toMatchObject({
groupIsPlaying: true,
anyPartIsPlaying: true,
allPartsArePaused: false,
sectionTimeToEnd: 1999,
sectionEndTime: 3000,
sectionEndAction: SectionEndAction.STOP,
})
}
}
})
})

// Test cases to add:
Expand Down
8 changes: 5 additions & 3 deletions apps/app/src/lib/playout/preparedGroupPlayData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export function prepareGroupPlayData(group: Group, now?: number): GroupPreparedP

saveSection(data.sections, section)

if (group.loop && section.endTime !== null && !endLoopingPart) {
if (group.loop && group.autoPlay && section.endTime !== null && !endLoopingPart) {
// Looping parts:

const loopSection: GroupPreparedPlayDataSection = {
Expand All @@ -214,7 +214,7 @@ export function prepareGroupPlayData(group: Group, now?: number): GroupPreparedP
for (const part of playableParts) {
if (part.loop) {
endLoopingPart = part
section.repeating = false
loopSection.repeating = false
break
}

Expand All @@ -233,7 +233,9 @@ export function prepareGroupPlayData(group: Group, now?: number): GroupPreparedP
break
}
}
saveSection(data.sections, loopSection)
if (loopSection.parts.length) {
saveSection(data.sections, loopSection)
}
}

// Handle the case when a part is looping:
Expand Down
12 changes: 12 additions & 0 deletions apps/app/src/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { assertNever, deepClone } from '@shared/lib'
import shortUUID from 'short-uuid'
import _ from 'lodash'
import { describeTimelineObject } from './TimelineObj'
import { deepmergeIntoCustom, getKeys } from 'deepmerge-ts'

export const findGroup = (rundown: Rundown, groupId: string): Group | undefined => {
return rundown.groups.find((g) => g.id === groupId)
Expand Down Expand Up @@ -844,3 +845,14 @@ export function unReplaceUndefined(obj: any): any {
}
return obj
}

export const deepExtendRemovingUndefined = deepmergeIntoCustom({
mergeRecords(m_target, values, utils, meta) {
utils.defaultMergeFunctions.mergeRecords(m_target, values, utils, meta)
for (const key of getKeys(values)) {
if (values[1] && key in values[1] && values[1][key] === undefined) {
delete m_target.value[key]
}
}
},
})
Loading

0 comments on commit 7ff8a5c

Please sign in to comment.