Skip to content

Commit

Permalink
Merge branch 'upstream/studio-settings-for-features' of github.com:bb…
Browse files Browse the repository at this point in the history
…c/sofie-core into release52
  • Loading branch information
nytamin committed Dec 10, 2024
2 parents 3f63362 + ea2cae1 commit 45fa3d2
Show file tree
Hide file tree
Showing 18 changed files with 178 additions and 10 deletions.
3 changes: 3 additions & 0 deletions meteor/__mocks__/defaultCollectionObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ export function defaultStudio(_id: StudioId): DBStudio {
mediaPreviewsUrl: '',
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
fallbackPartDuration: DEFAULT_FALLBACK_PART_DURATION,
allowHold: false,
allowPieceDirectPlay: false,
enableBuckets: false,
},
_rundownVersionHash: '',
routeSetsWithOverrides: wrapDefaultObject({}),
Expand Down
16 changes: 12 additions & 4 deletions meteor/server/api/rest/v1/typeConversion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
StudioId,
} from '@sofie-automation/corelib/dist/dataModel/Ids'
import { DBStudio, IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio'
import { assertNever, getRandomId, literal } from '@sofie-automation/corelib/dist/lib'
import { assertNever, Complete, getRandomId, literal } from '@sofie-automation/corelib/dist/lib'
import { protectString, unprotectString } from '@sofie-automation/corelib/dist/protectedString'
import {
applyAndValidateOverrides,
Expand Down Expand Up @@ -333,7 +333,7 @@ export async function studioFrom(apiStudio: APIStudio, existingId?: StudioId): P
}
}

export async function APIStudioFrom(studio: DBStudio): Promise<APIStudio> {
export async function APIStudioFrom(studio: DBStudio): Promise<Complete<APIStudio>> {
const studioSettings = APIStudioSettingsFrom(studio.settings)

return {
Expand All @@ -346,7 +346,7 @@ export async function APIStudioFrom(studio: DBStudio): Promise<APIStudio> {
}
}

export function studioSettingsFrom(apiStudioSettings: APIStudioSettings): IStudioSettings {
export function studioSettingsFrom(apiStudioSettings: APIStudioSettings): Complete<IStudioSettings> {
return {
frameRate: apiStudioSettings.frameRate,
mediaPreviewsUrl: apiStudioSettings.mediaPreviewsUrl,
Expand All @@ -362,10 +362,14 @@ export function studioSettingsFrom(apiStudioSettings: APIStudioSettings): IStudi
enableQuickLoop: apiStudioSettings.enableQuickLoop,
forceQuickLoopAutoNext: forceQuickLoopAutoNextFrom(apiStudioSettings.forceQuickLoopAutoNext),
fallbackPartDuration: apiStudioSettings.fallbackPartDuration ?? DEFAULT_FALLBACK_PART_DURATION,
allowAdlibTestingSegment: apiStudioSettings.allowAdlibTestingSegment,
allowHold: apiStudioSettings.allowHold ?? true, // Backwards compatible
allowPieceDirectPlay: apiStudioSettings.allowPieceDirectPlay ?? true, // Backwards compatible
enableBuckets: apiStudioSettings.enableBuckets ?? true, // Backwards compatible
}
}

export function APIStudioSettingsFrom(settings: IStudioSettings): APIStudioSettings {
export function APIStudioSettingsFrom(settings: IStudioSettings): Complete<APIStudioSettings> {
return {
frameRate: settings.frameRate,
mediaPreviewsUrl: settings.mediaPreviewsUrl,
Expand All @@ -381,6 +385,10 @@ export function APIStudioSettingsFrom(settings: IStudioSettings): APIStudioSetti
enableQuickLoop: settings.enableQuickLoop,
forceQuickLoopAutoNext: APIForceQuickLoopAutoNextFrom(settings.forceQuickLoopAutoNext),
fallbackPartDuration: settings.fallbackPartDuration,
allowAdlibTestingSegment: settings.allowAdlibTestingSegment,
allowHold: settings.allowHold,
allowPieceDirectPlay: settings.allowPieceDirectPlay,
enableBuckets: settings.enableBuckets,
}
}

Expand Down
3 changes: 3 additions & 0 deletions meteor/server/api/studio/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export async function insertStudioInner(organizationId: OrganizationId | null, n
frameRate: 25,
mediaPreviewsUrl: '',
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: false,
allowPieceDirectPlay: false,
enableBuckets: true,
},
_rundownVersionHash: '',
routeSetsWithOverrides: wrapDefaultObject({}),
Expand Down
4 changes: 4 additions & 0 deletions meteor/server/lib/rest/v1/studios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,8 @@ export interface APIStudioSettings {
forceQuickLoopAutoNext?: 'disabled' | 'enabled_when_valid_duration' | 'enabled_forcing_min_duration'
minimumTakeSpan?: number
fallbackPartDuration?: number
allowAdlibTestingSegment?: boolean
allowHold?: boolean
allowPieceDirectPlay?: boolean
enableBuckets?: boolean
}
3 changes: 3 additions & 0 deletions meteor/server/migration/0_1_0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,9 @@ export const addSteps = addMigrationSteps('0.1.0', [
frameRate: 25,
mediaPreviewsUrl: '',
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: false,
allowPieceDirectPlay: false,
enableBuckets: true,
},
mappingsWithOverrides: wrapDefaultObject({}),
blueprintConfigWithOverrides: wrapDefaultObject({}),
Expand Down
37 changes: 37 additions & 0 deletions meteor/server/migration/X_X_X.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,41 @@ export const addSteps = addMigrationSteps(CURRENT_SYSTEM_VERSION, [
}
},
},

{
id: `add studio settings allowHold & allowPieceDirectPlay`,
canBeRunAutomatically: true,
validate: async () => {
const studios = await Studios.findFetchAsync({
$or: [
{ 'settings.allowHold': { $exists: false } },
{ 'settings.allowPieceDirectPlay': { $exists: false } },
],
})

if (studios.length > 0) {
return 'studios must have settings.allowHold and settings.allowPieceDirectPlay defined'
}

return false
},
migrate: async () => {
const studios = await Studios.findFetchAsync({
$or: [
{ 'settings.allowHold': { $exists: false } },
{ 'settings.allowPieceDirectPlay': { $exists: false } },
],
})

for (const studio of studios) {
// Populate the settings to be backwards compatible
await Studios.updateAsync(studio._id, {
$set: {
'settings.allowHold': true,
'settings.allowPieceDirectPlay': true,
},
})
}
},
},
])
9 changes: 9 additions & 0 deletions meteor/server/migration/__tests__/migrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ describe('Migrations', () => {
mediaPreviewsUrl: '',
frameRate: 25,
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: true,
allowPieceDirectPlay: true,
enableBuckets: true,
},
mappingsWithOverrides: wrapDefaultObject({}),
blueprintConfigWithOverrides: wrapDefaultObject({}),
Expand Down Expand Up @@ -164,6 +167,9 @@ describe('Migrations', () => {
mediaPreviewsUrl: '',
frameRate: 25,
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: true,
allowPieceDirectPlay: true,
enableBuckets: true,
},
mappingsWithOverrides: wrapDefaultObject({}),
blueprintConfigWithOverrides: wrapDefaultObject({}),
Expand Down Expand Up @@ -202,6 +208,9 @@ describe('Migrations', () => {
mediaPreviewsUrl: '',
frameRate: 25,
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: true,
allowPieceDirectPlay: true,
enableBuckets: true,
},
mappingsWithOverrides: wrapDefaultObject({}),
blueprintConfigWithOverrides: wrapDefaultObject({}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,7 @@ describe('lib/mediaObjects', () => {
test('getAcceptedFormats', () => {
const acceptedFormats = getAcceptedFormats({
supportedMediaFormats: '1920x1080i5000, 1280x720, i5000, i5000tff',
mediaPreviewsUrl: '',
frameRate: 25,
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
})
expect(acceptedFormats).toEqual([
['1920', '1080', 'i', '5000', undefined],
Expand Down Expand Up @@ -252,6 +250,9 @@ describe('lib/mediaObjects', () => {
supportedAudioStreams: '4',
frameRate: 25,
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: false,
allowPieceDirectPlay: false,
enableBuckets: false,
}

const mockDefaultStudio = defaultStudio(protectString('studio0'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ export function acceptFormat(format: string, formats: Array<Array<string>>): boo
* [undefined, undefined, i, 5000, tff]
* ]
*/
export function getAcceptedFormats(settings: IStudioSettings | undefined): Array<Array<string>> {
export function getAcceptedFormats(
settings: Pick<IStudioSettings, 'supportedMediaFormats' | 'frameRate'> | undefined
): Array<Array<string>> {
const formatsConfigField = settings ? settings.supportedMediaFormats : ''
const formatsString: string =
(formatsConfigField && formatsConfigField !== '' ? formatsConfigField : '1920x1080i5000') + ''
Expand Down
18 changes: 18 additions & 0 deletions packages/corelib/src/dataModel/Studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,24 @@ export interface IStudioSettings {
* Default: 3000
*/
fallbackPartDuration?: number

/**
* Whether to allow hold operations for Rundowns in this Studio
* When disabled, any action-triggers that would normally trigger a hold operation will be silently ignored
* This should only block entering hold, to ensure Sofie doesn't get stuck if it somehow gets into hold
*/
allowHold: boolean

/**
* Whether to allow direct playing of a piece in the rundown
* This behaviour is usally triggered by double-clicking on a piece in the GUI
*/
allowPieceDirectPlay: boolean

/**
* Enable buckets - the default behavior is to have buckets.
*/
enableBuckets: boolean
}

export type StudioLight = Omit<DBStudio, 'mappingsWithOverrides' | 'blueprintConfigWithOverrides'>
Expand Down
3 changes: 3 additions & 0 deletions packages/job-worker/src/__mocks__/defaultCollectionObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ export function defaultStudio(_id: StudioId): DBStudio {
mediaPreviewsUrl: '',
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowAdlibTestingSegment: true,
allowHold: true,
allowPieceDirectPlay: true,
enableBuckets: true,
},
routeSetsWithOverrides: wrapDefaultObject({}),
routeSetExclusivityGroupsWithOverrides: wrapDefaultObject({}),
Expand Down
6 changes: 6 additions & 0 deletions packages/job-worker/src/blueprints/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ describe('Test blueprint config', () => {
mediaPreviewsUrl: '',
frameRate: 25,
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: true,
allowPieceDirectPlay: true,
enableBuckets: true,
},
blueprintConfigWithOverrides: wrapDefaultObject({ sdfsdf: 'one', another: 5 }),
})
Expand All @@ -38,6 +41,9 @@ describe('Test blueprint config', () => {
mediaPreviewsUrl: '',
frameRate: 25,
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: true,
allowPieceDirectPlay: true,
enableBuckets: true,
},
blueprintConfigWithOverrides: wrapDefaultObject({ sdfsdf: 'one', another: 5 }),
})
Expand Down
6 changes: 6 additions & 0 deletions packages/job-worker/src/playout/adlibJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ import { PlayoutPieceInstanceModel } from './model/PlayoutPieceInstanceModel'
* Play an existing Piece in the Rundown as an AdLib
*/
export async function handleTakePieceAsAdlibNow(context: JobContext, data: TakePieceAsAdlibNowProps): Promise<void> {
if (!context.studio.settings.allowPieceDirectPlay) {
// Piece direct play isn't allowed, making this a noop
logger.debug(`Piece direct play isn't allowed, skipping`)
return
}

return runJobWithPlayoutModel(
context,
data,
Expand Down
9 changes: 9 additions & 0 deletions packages/job-worker/src/playout/holdJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,18 @@ import { ActivateHoldProps, DeactivateHoldProps } from '@sofie-automation/coreli
import { JobContext } from '../jobs'
import { runJobWithPlayoutModel } from './lock'
import { updateTimeline } from './timeline/generate'
import { logger } from '../logging'

/**
* Activate Hold
*/
export async function handleActivateHold(context: JobContext, data: ActivateHoldProps): Promise<void> {
if (!context.studio.settings.allowHold) {
// Hold isn't allowed, making this a noop
logger.debug(`Hold isn't allowed, skipping`)
return
}

return runJobWithPlayoutModel(
context,
data,
Expand Down Expand Up @@ -59,6 +66,8 @@ export async function handleActivateHold(context: JobContext, data: ActivateHold
* Deactivate Hold
*/
export async function handleDeactivateHold(context: JobContext, data: DeactivateHoldProps): Promise<void> {
// This should be possible even when hold is not allowed, as it is a way to get out of a stuck state

return runJobWithPlayoutModel(
context,
data,
Expand Down
9 changes: 9 additions & 0 deletions packages/openapi/api/definitions/studios.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,15 @@ components:
fallbackPartDuration:
type: number
description: The duration to apply on too short Parts Within QuickLoop when forceQuickLoopAutoNext is set to `enabled_forcing_min_duration`
allowAdlibTestingSegment:
type: boolean
description: Whether to allow adlib testing mode, before a Part is playing in a Playlist
allowHold:
type: boolean
description: Whether to allow hold operations for Rundowns in this Studio
allowPieceDirectPlay:
type: boolean
description: Whether to allow direct playing of a piece in the rundown

required:
- frameRate
Expand Down
3 changes: 3 additions & 0 deletions packages/webui/src/__mocks__/defaultCollectionObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export function defaultStudio(_id: StudioId): DBStudio {
frameRate: 25,
mediaPreviewsUrl: '',
minimumTakeSpan: DEFAULT_MINIMUM_TAKE_SPAN,
allowHold: true,
allowPieceDirectPlay: true,
enableBuckets: true,
},
_rundownVersionHash: '',
routeSetsWithOverrides: wrapDefaultObject({}),
Expand Down
8 changes: 5 additions & 3 deletions packages/webui/src/client/ui/RundownView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ const RundownHeader = withTranslation()(
{this.props.playlist.activationId ? (
<MenuItem onClick={(e) => this.take(e)}>{t('Take')}</MenuItem>
) : null}
{this.props.playlist.activationId ? (
{this.props.studio.settings.allowHold && this.props.playlist.activationId ? (
<MenuItem onClick={(e) => this.hold(e)}>{t('Hold')}</MenuItem>
) : null}
{this.props.playlist.activationId && canClearQuickLoop ? (
Expand Down Expand Up @@ -1457,7 +1457,8 @@ const RundownViewContent = translateWithTracker<IPropsWithReady, IState, ITracke
rundownHeaderLayoutId: protectString((params['rundownHeaderLayout'] as string) || ''),
miniShelfLayoutId: protectString((params['miniShelfLayout'] as string) || ''),
shelfDisplayOptions: {
enableBuckets: displayOptions.includes('buckets'),
// If buckets are enabled in Studiosettings, it can also be filtered in the URLs display options.
enableBuckets: !!studio?.settings.enableBuckets && displayOptions.includes('buckets'),
enableLayout: displayOptions.includes('layout') || displayOptions.includes('shelfLayout'),
enableInspector: displayOptions.includes('inspector'),
},
Expand Down Expand Up @@ -2234,7 +2235,8 @@ const RundownViewContent = translateWithTracker<IPropsWithReady, IState, ITracke
item &&
item.instance &&
this.props.playlist &&
this.props.playlist.currentPartInfo
this.props.playlist.currentPartInfo &&
this.props.studio?.settings.allowPieceDirectPlay
) {
const idToCopy = item.instance.isTemporary ? item.instance.piece._id : item.instance._id
const playlistId = this.props.playlist._id
Expand Down
Loading

0 comments on commit 45fa3d2

Please sign in to comment.