Skip to content

Commit

Permalink
Merge pull request tv2#60 from olzzon/feature/casparcg-channel-settings
Browse files Browse the repository at this point in the history
feature: CasparCG channel settings
  • Loading branch information
olzzon authored Jul 15, 2019
2 parents d602daa + 9d75044 commit db58ee3
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 11 deletions.
17 changes: 17 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "start",
"type": "npm",
"script": "start",
"problemMatcher": [
"$tsc"
],
"isBackground": true,
"promptOnClose": false
}
]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"build": "yarn tsc && webpack --config webpack.build.config.js --mode production",
"build-now": "webpack --config webpack.build.config.js --mode production",
"package": "yarn run build",
"postpackage": "electron-packager ./ --out=./builds --overwrite",
"postpackage": "electron-packager --overwrite ./ --out=./builds --overwrite",
"package-linux": "electron-packager . sisyfos-audio-controller --overwrite --asar=true --platform=linux --arch=x64 --prune=true --out=builds",
"package-win": "electron-packager . sisyfos-audio-controller --overwrite --asar=true --platform=win32 --arch=x64 --prune=true --out=builds",
"package-release": "yarn package && yarn package-win && yarn package-linux"
Expand Down
4 changes: 4 additions & 0 deletions src/assets/css/ChannelSettings.css
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,8 @@
width: 90%;
font-size: 30px;
line-height: 50px;
}

.channel-settings-group > button.active {
border-color: rgb(17, 0, 255);
}
16 changes: 12 additions & 4 deletions src/components/ChannelSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import { MixerProtocolPresets } from '../constants/MixerProtocolPresets';
import { IMixerProtocolGeneric, ICasparCGMixerGeometry } from '../constants/MixerProtocolInterface';
import { Store } from 'redux';
import { connect } from 'react-redux';
import { IStore } from '../reducers/indexReducer';

interface IChannelSettingsInjectProps {
label: string,
mixerProtocol: string,
sourceOption: string
}

interface IChannelProps {
Expand Down Expand Up @@ -55,8 +57,14 @@ class ChannelSettings extends React.PureComponent<IChannelProps & IChannelSettin
return (
<div className="channel-settings-group" key={prop}>
{Object.getOwnPropertyNames(this.mixerProtocol!.sourceOptions!.options[prop]).map(option => {
console.log(prop, option)
return <button key={option} className="channel-settings-group-item" onClick={() => this.handleOption(prop, this.mixerProtocol!.sourceOptions!.options[prop][option])}>{option}</button>
return <button
key={option}
className={"channel-settings-group-item" +
(this.props.sourceOption === this.mixerProtocol!.sourceOptions!.options[prop][option] ? ' active' : '')
}
onClick={() => this.handleOption(prop, this.mixerProtocol!.sourceOptions!.options[prop][option])}>
{option}
</button>
}) || null}
</div>
)
Expand All @@ -67,10 +75,10 @@ class ChannelSettings extends React.PureComponent<IChannelProps & IChannelSettin
}

const mapStateToProps = (state: any, props: any): IChannelSettingsInjectProps => {
console.log(state.channels[0].channel, props.channelIndex);
return {
label: state.channels[0].channel[props.channelIndex].label,
mixerProtocol: state.settings[0].mixerProtocol
mixerProtocol: state.settings[0].mixerProtocol,
sourceOption: (state.channels[0].channel[props.channelIndex].private || {})['channel_layout']
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/Channels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Channels extends React.Component<IAppProps & Store> {
}

public shouldComponentUpdate(nextProps: IAppProps) {
return false;
return this.props.store.settings[0].showOptions !== nextProps.store.settings[0].showOptions;
}


Expand Down
8 changes: 4 additions & 4 deletions src/constants/MixerProtocolInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ export interface ICasparCGMixerGeometryFile {
MONITOR_CHANNEL_FADER_LEVEL: Array<ICasparCGChannelLayerPair[]>
}
sourceOptions?: {
sources: (ICasparCGChannelLayerPair & {
sources: Array<(ICasparCGChannelLayerPair & {
producer: string,
file: string
})
})>
options: {
[key: string]: { // producer property invocation
[key: string]: string // label: property
Expand All @@ -109,10 +109,10 @@ export interface ICasparCGMixerGeometry extends IMixerProtocolGeneric {
}
channelLabels?: string[],
sourceOptions?: {
sources: (ICasparCGChannelLayerPair & {
sources: Array<(ICasparCGChannelLayerPair & {
producer: string,
file: string
})
})>
options: {
[key: string]: { // producer property invocation
[key: string]: string // label: property
Expand Down
6 changes: 6 additions & 0 deletions src/constants/MixerProtocolPresets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import { MidasMaster } from './mixerProtocols/midasMaster';
import { GenericMidi } from './mixerProtocols/genericMidi';
import { LawoClient } from './mixerProtocols/EmberLawo';
import { CasparCGMaster } from './mixerProtocols/casparCGMaster';

interface IMessageProtocol {
mixerMessage: string,
value: any,
type: string
}
import { StuderVista1Master } from './mixerProtocols/StuderVista1Ember';
import { StuderVista5Master } from './mixerProtocols/StuderVista5Ember';
import { StuderVista9Master } from './mixerProtocols/StuderVista9Ember';
Expand Down
15 changes: 14 additions & 1 deletion src/reducers/channelsReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export interface IChannel {
pstOn: boolean,
pflOn: boolean,
showChannel: boolean,
snapOn: Array<boolean>
snapOn: Array<boolean>,
private?: {
[key: string]: string
}
}

interface IVuMeters {
Expand Down Expand Up @@ -134,6 +137,16 @@ export const channels = ((state = defaultChannelsReducerState([1]), action: any)
nextState[0].channel[index].pstOn = !!state[0].channel[index].snapOn[action.snapIndex];
});
return nextState;
case 'SET_PRIVATE':
if (!nextState[0].channel[action.channel].private) {
nextState[0].channel[action.channel].private = {};
}
nextState[0].channel[action.channel].private![action.tag] = action.value;
return nextState;
case 'SET_OPTION':
console.log(action);
window.mixerGenericConnection.updateChannelSettings(action.channel, action.prop, action.option);
return nextState;
default:
return nextState;
}
Expand Down
104 changes: 104 additions & 0 deletions src/utils/CasparCGConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ interface CommandChannelMap {
[key: string]: number
}

interface ICasparCGChannel extends IChannel {
producer?: string
source?: string
}

const OSC_PATH_PRODUCER = /\/channel\/(\d+)\/stage\/layer\/(\d+)\/producer\/type/
const OSC_PATH_PRODUCER_FILE_NAME = /\/channel\/(\d+)\/stage\/layer\/(\d+)\/file\/path/
const OSC_PATH_PRODUCER_CHANNEL_LAYOUT = /\/channel\/(\d+)\/stage\/layer\/(\d+)\/producer\/channel_layout/

export class CasparCGConnection {
store: IStore;
mixerProtocol: ICasparCGMixerGeometry;
Expand Down Expand Up @@ -73,6 +82,40 @@ export class CasparCGConnection {
// We therefore want to premultiply this to show useful information about audio levels
level: Math.min(1, message.args[0] * this.store.channels[0].channel[index].faderLevel)
});
} else if (this.mixerProtocol.sourceOptions) {
const m = message.address.split('/');

if (m[1] === 'channel' && m[6] === 'producer' && m[7] === 'type') {
const index = this.mixerProtocol.sourceOptions.sources.findIndex(i => i.channel === parseInt(m[2], 10) && i.layer === parseInt(m[5]))
if (index >= 0) {
window.storeRedux.dispatch({
type: 'SET_PRIVATE',
channel: index,
tag: 'producer',
value: message.args[0]
})
}
} else if (m[1] === 'channel' && m[6] === 'producer' && m[7] === 'channel_layout') {
const index = this.mixerProtocol.sourceOptions.sources.findIndex(i => i.channel === parseInt(m[2], 10) && i.layer === parseInt(m[5]))
if (index >= 0) {
window.storeRedux.dispatch({
type: 'SET_PRIVATE',
channel: index,
tag: 'channel_layout',
value: message.args[0]
})
}
} else if (m[1] === 'channel' && m[6] === 'file' && m[7] === 'path') {
const index = this.mixerProtocol.sourceOptions.sources.findIndex(i => i.channel === parseInt(m[2], 10) && i.layer === parseInt(m[5]))
if (index >= 0) {
window.storeRedux.dispatch({
type: 'SET_PRIVATE',
channel: index,
tag: 'file_path',
value: message.args[0]
})
}
}
}
})
.on('error', (error: any) => {
Expand Down Expand Up @@ -110,6 +153,15 @@ export class CasparCGConnection {
return undefined
}

findChannelIndex(channel: number, layer: number, channelLayerPairs: Array<ICasparCGChannelLayerPair[]>, matchFirst?: boolean): number {
return channelLayerPairs.findIndex((i) => {
if (matchFirst) {
return (i[0].channel === channel && i[0].layer === layer)
}
return !!i.find(j => j.channel === channel && j.layer === layer)
})
}

pingMixerCommand = () => {
//Ping OSC mixer if mixerProtocol needs it.
/* this.mixerProtocol.pingCommand.map((command) => {
Expand All @@ -127,12 +179,64 @@ export class CasparCGConnection {
})
}

controlChannelSetting = (channel: number, layer: number, producer: string, file: string, setting: string, value: string) => {
this.connection.stop(channel, layer)
.then(() => {
if (setting === 'CHANNEL_LAYOUT') {
switch (producer) {
case 'ffmpeg':
return this.connection.play(
channel,
layer,
file,
true,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
value);
case 'decklink':
return this.connection.playDecklink(
channel,
layer,
parseInt(file, 10),
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
value);
}
}
return Promise.reject('Unknown operation');
}).then(() => {

}).catch((e) => {
console.error('Failed to change channel setting', e);
})
}

setAllLayers = (pairs: ICasparCGChannelLayerPair[], value: number) => {
pairs.forEach((i) => {
this.controlVolume(i.channel, i.layer, value);
})
}

updateChannelSetting(channelIndex: number, setting: string, value: string) {
if (this.mixerProtocol.sourceOptions && this.store.channels[0].channel[channelIndex].private) {
const pair = this.mixerProtocol.sourceOptions.sources[channelIndex];
const producer = this.store.channels[0].channel[channelIndex].private!['producer'];
let filePath = this.store.channels[0].channel[channelIndex].private!['file_path'];
filePath = filePath.replace(/\.[\w\d]+$/, '')
this.controlChannelSetting(pair.channel, pair.layer, producer, filePath, setting, value);
}
}

updateOutLevel(channelIndex: number) {
if (channelIndex > this.mixerProtocol.toMixer.PGM_CHANNEL_FADER_LEVEL.length - 1) {
return
Expand Down
6 changes: 6 additions & 0 deletions src/utils/MixerConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ export class MixerGenericConnection {
this.mixerConnection.updateChannelName(channelIndex);
}

updateChannelSettings(channelIndex: number, setting: string, value: string) {
if (this.mixerProtocol.protocol === 'CasparCG') {
this.mixerConnection.updateChannelSetting(channelIndex, setting, value)
}
}

fadeInOut (channelIndex: number, fadeTime: number){
//Clear Old timer or set Fade to active:
if (this.store.channels[0].channel[channelIndex].fadeActive) {
Expand Down

0 comments on commit db58ee3

Please sign in to comment.