@norskvideo/norsk-studio-built-ins 1.27.0-2026-01-14-d7f304fc → 1.27.0-2026-01-15-ecb87e67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,7 +19,7 @@ exports.PRESETS = {
19
19
  }))
20
20
  }
21
21
  }),
22
- quarters: (sources, _config, res) => {
22
+ quarters: (sources, _config, res, transitionContext) => {
23
23
  const half_w = res.width / 2;
24
24
  const half_h = res.height / 2;
25
25
  const quarters = sources.slice(0, 4);
@@ -77,21 +77,76 @@ exports.PRESETS = {
77
77
  ]
78
78
  },
79
79
  to: {
80
- fullscreen: [
81
- {
82
- durationMs: 500,
83
- layout: {
84
- layers: quarters.map((s, i) => ({
85
- sourceName: s,
86
- zIndex: i,
87
- opacity: i === 0 ? 1.0 : 0.0,
88
- id: s,
89
- sourceRect: undefined,
90
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
91
- }))
92
- }
80
+ fullscreen: (() => {
81
+ const targetSources = transitionContext?.targetSources ?? [];
82
+ const targetSource = targetSources[0];
83
+ const targetInQuarters = targetSource && quarters.includes(targetSource);
84
+ if (targetInQuarters) {
85
+ return [
86
+ {
87
+ durationMs: 500,
88
+ layout: {
89
+ layers: quarters.map((s, i) => ({
90
+ sourceName: s,
91
+ zIndex: i,
92
+ opacity: s === targetSource ? 1.0 : 0.0,
93
+ id: s,
94
+ sourceRect: undefined,
95
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
96
+ }))
97
+ }
98
+ },
99
+ {
100
+ durationMs: 0,
101
+ layout: {
102
+ layers: [{
103
+ sourceName: targetSource,
104
+ zIndex: 0,
105
+ opacity: 1.0,
106
+ id: targetSource,
107
+ sourceRect: undefined,
108
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
109
+ }]
110
+ }
111
+ }
112
+ ];
93
113
  }
94
- ],
114
+ else {
115
+ return [
116
+ {
117
+ durationMs: 250,
118
+ layout: {
119
+ layers: quarters.map((s, i) => ({
120
+ sourceName: s,
121
+ zIndex: i,
122
+ opacity: 0.0,
123
+ id: s,
124
+ sourceRect: undefined,
125
+ destRect: {
126
+ x: (i % 2) * half_w,
127
+ y: Math.floor(i / 2) * half_h,
128
+ width: half_w,
129
+ height: half_h
130
+ }
131
+ }))
132
+ }
133
+ },
134
+ ...(targetSource ? [{
135
+ durationMs: 250,
136
+ layout: {
137
+ layers: [{
138
+ sourceName: targetSource,
139
+ zIndex: 0,
140
+ opacity: 1.0,
141
+ id: targetSource,
142
+ sourceRect: undefined,
143
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
144
+ }]
145
+ }
146
+ }] : [])
147
+ ];
148
+ }
149
+ })(),
95
150
  'mosaic-9': [
96
151
  {
97
152
  durationMs: 600,
@@ -204,8 +259,9 @@ exports.PRESETS = {
204
259
  } : undefined
205
260
  };
206
261
  },
207
- pip: (sources, config, res) => {
262
+ pip: (sources, config, res, transitionContext) => {
208
263
  const [mainSource, pipSource] = sources;
264
+ const pipSources = [mainSource, pipSource].filter(Boolean);
209
265
  const pipConfig = config;
210
266
  const corner = pipConfig?.corner ?? 'bottom-right';
211
267
  const pipWidth = pipConfig?.width ?? Math.round(res.width * 0.2);
@@ -336,36 +392,91 @@ exports.PRESETS = {
336
392
  ]
337
393
  },
338
394
  to: {
339
- fullscreen: [
340
- {
341
- durationMs: 500,
342
- layout: {
343
- layers: [
344
- {
345
- sourceName: mainSource,
346
- zIndex: 0,
347
- opacity: 1.0,
348
- id: mainSource,
349
- sourceRect: undefined,
350
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
351
- },
352
- {
353
- sourceName: pipSource,
354
- zIndex: 1,
355
- opacity: 0.0,
356
- id: pipSource,
357
- sourceRect: undefined,
358
- destRect: {
359
- x: res.width / 2 - pipWidth / 2,
360
- y: res.height / 2 - pipHeight / 2,
361
- width: pipWidth,
362
- height: pipHeight
363
- }
395
+ fullscreen: (() => {
396
+ const targetSources = transitionContext?.targetSources ?? [];
397
+ const targetSource = targetSources[0];
398
+ const targetInPip = targetSource && pipSources.includes(targetSource);
399
+ if (targetInPip) {
400
+ return [
401
+ {
402
+ durationMs: 500,
403
+ layout: {
404
+ layers: [
405
+ {
406
+ sourceName: mainSource,
407
+ zIndex: 0,
408
+ opacity: mainSource === targetSource ? 1.0 : 0.0,
409
+ id: mainSource,
410
+ sourceRect: undefined,
411
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
412
+ },
413
+ ...(pipSource ? [{
414
+ sourceName: pipSource,
415
+ zIndex: 1,
416
+ opacity: pipSource === targetSource ? 1.0 : 0.0,
417
+ id: pipSource,
418
+ sourceRect: undefined,
419
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
420
+ }] : [])
421
+ ]
364
422
  }
365
- ]
366
- }
423
+ },
424
+ {
425
+ durationMs: 0,
426
+ layout: {
427
+ layers: [{
428
+ sourceName: targetSource,
429
+ zIndex: 0,
430
+ opacity: 1.0,
431
+ id: targetSource,
432
+ sourceRect: undefined,
433
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
434
+ }]
435
+ }
436
+ }
437
+ ];
367
438
  }
368
- ],
439
+ else {
440
+ return [
441
+ {
442
+ durationMs: 250,
443
+ layout: {
444
+ layers: [
445
+ {
446
+ sourceName: mainSource,
447
+ zIndex: 0,
448
+ opacity: 0.0,
449
+ id: mainSource,
450
+ sourceRect: undefined,
451
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
452
+ },
453
+ ...(pipSource ? [{
454
+ sourceName: pipSource,
455
+ zIndex: 1,
456
+ opacity: 0.0,
457
+ id: pipSource,
458
+ sourceRect: undefined,
459
+ destRect: { x: pipX, y: pipY, width: pipWidth, height: pipHeight }
460
+ }] : [])
461
+ ]
462
+ }
463
+ },
464
+ ...(targetSource ? [{
465
+ durationMs: 250,
466
+ layout: {
467
+ layers: [{
468
+ sourceName: targetSource,
469
+ zIndex: 0,
470
+ opacity: 1.0,
471
+ id: targetSource,
472
+ sourceRect: undefined,
473
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
474
+ }]
475
+ }
476
+ }] : [])
477
+ ];
478
+ }
479
+ })(),
369
480
  'side-by-side': [
370
481
  {
371
482
  durationMs: 600,
@@ -450,8 +561,9 @@ exports.PRESETS = {
450
561
  } : undefined
451
562
  };
452
563
  },
453
- 'lower-third': (sources, config, res) => {
564
+ 'lower-third': (sources, config, res, transitionContext) => {
454
565
  const [mainSource, lowerThirdSource] = sources;
566
+ const lowerThirdSources = [mainSource, lowerThirdSource].filter(Boolean);
455
567
  const lowerThirdConfig = config;
456
568
  const alignment = lowerThirdConfig?.alignment ?? 'left';
457
569
  const lowerThirdWidth = lowerThirdConfig?.width ?? Math.round(res.width * 0.3);
@@ -543,31 +655,93 @@ exports.PRESETS = {
543
655
  ]
544
656
  },
545
657
  to: {
546
- fullscreen: [
547
- {
548
- durationMs: 500,
549
- layout: {
550
- layers: [
551
- {
552
- sourceName: mainSource,
553
- zIndex: 0,
554
- opacity: 1.0,
555
- id: mainSource,
556
- sourceRect: undefined,
557
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
558
- },
559
- {
560
- sourceName: lowerThirdSource,
561
- zIndex: 1,
562
- opacity: 1.0,
563
- id: lowerThirdSource,
564
- sourceRect: undefined,
565
- destRect: { x: lowerThirdX, y: res.height, width: lowerThirdWidth, height: lowerThirdHeight }
658
+ fullscreen: (() => {
659
+ const targetSources = transitionContext?.targetSources ?? [];
660
+ const targetSource = targetSources[0];
661
+ const targetInLowerThird = targetSource && lowerThirdSources.includes(targetSource);
662
+ if (targetInLowerThird) {
663
+ return [
664
+ {
665
+ durationMs: 500,
666
+ layout: {
667
+ layers: [
668
+ {
669
+ sourceName: mainSource,
670
+ zIndex: 0,
671
+ opacity: mainSource === targetSource ? 1.0 : 0.0,
672
+ id: mainSource,
673
+ sourceRect: undefined,
674
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
675
+ },
676
+ {
677
+ sourceName: lowerThirdSource,
678
+ zIndex: 1,
679
+ opacity: lowerThirdSource === targetSource ? 1.0 : 0.0,
680
+ id: lowerThirdSource,
681
+ sourceRect: undefined,
682
+ destRect: lowerThirdSource === targetSource
683
+ ? { x: 0, y: 0, width: res.width, height: res.height }
684
+ : { x: lowerThirdX, y: res.height, width: lowerThirdWidth, height: lowerThirdHeight }
685
+ }
686
+ ]
566
687
  }
567
- ]
568
- }
688
+ },
689
+ {
690
+ durationMs: 0,
691
+ layout: {
692
+ layers: [{
693
+ sourceName: targetSource,
694
+ zIndex: 0,
695
+ opacity: 1.0,
696
+ id: targetSource,
697
+ sourceRect: undefined,
698
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
699
+ }]
700
+ }
701
+ }
702
+ ];
569
703
  }
570
- ],
704
+ else {
705
+ return [
706
+ {
707
+ durationMs: 250,
708
+ layout: {
709
+ layers: [
710
+ {
711
+ sourceName: mainSource,
712
+ zIndex: 0,
713
+ opacity: 0.0,
714
+ id: mainSource,
715
+ sourceRect: undefined,
716
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
717
+ },
718
+ {
719
+ sourceName: lowerThirdSource,
720
+ zIndex: 1,
721
+ opacity: 0.0,
722
+ id: lowerThirdSource,
723
+ sourceRect: undefined,
724
+ destRect: { x: lowerThirdX, y: res.height, width: lowerThirdWidth, height: lowerThirdHeight }
725
+ }
726
+ ]
727
+ }
728
+ },
729
+ ...(targetSource ? [{
730
+ durationMs: 250,
731
+ layout: {
732
+ layers: [{
733
+ sourceName: targetSource,
734
+ zIndex: 0,
735
+ opacity: 1.0,
736
+ id: targetSource,
737
+ sourceRect: undefined,
738
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
739
+ }]
740
+ }
741
+ }] : [])
742
+ ];
743
+ }
744
+ })(),
571
745
  pip: [
572
746
  {
573
747
  durationMs: 500,
@@ -602,9 +776,10 @@ exports.PRESETS = {
602
776
  } : undefined
603
777
  };
604
778
  },
605
- 'side-by-side': (sources, _config, res) => {
779
+ 'side-by-side': (sources, _config, res, transitionContext) => {
606
780
  const [source1, source2] = sources.slice(0, 2);
607
781
  const halfWidth = res.width / 2;
782
+ const sideBySideSources = [source1, source2].filter(Boolean);
608
783
  return {
609
784
  name: 'side-by-side',
610
785
  finalConfig: {
@@ -704,31 +879,95 @@ exports.PRESETS = {
704
879
  ]
705
880
  },
706
881
  to: {
707
- fullscreen: [
708
- {
709
- durationMs: 500,
710
- layout: {
711
- layers: [
712
- {
713
- sourceName: source1,
714
- zIndex: 0,
715
- opacity: 1.0,
716
- id: source1,
717
- sourceRect: undefined,
718
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
719
- },
720
- {
721
- sourceName: source2,
722
- zIndex: 1,
723
- opacity: 1.0,
724
- id: source2,
725
- sourceRect: undefined,
726
- destRect: { x: res.width, y: 0, width: halfWidth, height: res.height }
882
+ fullscreen: (() => {
883
+ const targetSources = transitionContext?.targetSources ?? [];
884
+ const targetSource = targetSources[0];
885
+ const targetInSideBySide = targetSource && sideBySideSources.includes(targetSource);
886
+ if (targetInSideBySide) {
887
+ return [
888
+ {
889
+ durationMs: 500,
890
+ layout: {
891
+ layers: [
892
+ {
893
+ sourceName: source1,
894
+ zIndex: 0,
895
+ opacity: 1.0,
896
+ id: source1,
897
+ sourceRect: undefined,
898
+ destRect: source1 === targetSource
899
+ ? { x: 0, y: 0, width: res.width, height: res.height }
900
+ : { x: -halfWidth, y: 0, width: halfWidth, height: res.height }
901
+ },
902
+ ...(source2 ? [{
903
+ sourceName: source2,
904
+ zIndex: 1,
905
+ opacity: 1.0,
906
+ id: source2,
907
+ sourceRect: undefined,
908
+ destRect: source2 === targetSource
909
+ ? { x: 0, y: 0, width: res.width, height: res.height }
910
+ : { x: res.width, y: 0, width: halfWidth, height: res.height }
911
+ }] : [])
912
+ ]
727
913
  }
728
- ]
729
- }
914
+ },
915
+ {
916
+ durationMs: 0,
917
+ layout: {
918
+ layers: [{
919
+ sourceName: targetSource,
920
+ zIndex: 0,
921
+ opacity: 1.0,
922
+ id: targetSource,
923
+ sourceRect: undefined,
924
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
925
+ }]
926
+ }
927
+ }
928
+ ];
730
929
  }
731
- ],
930
+ else {
931
+ return [
932
+ {
933
+ durationMs: 250,
934
+ layout: {
935
+ layers: [
936
+ {
937
+ sourceName: source1,
938
+ zIndex: 0,
939
+ opacity: 0.0,
940
+ id: source1,
941
+ sourceRect: undefined,
942
+ destRect: { x: 0, y: 0, width: halfWidth, height: res.height }
943
+ },
944
+ ...(source2 ? [{
945
+ sourceName: source2,
946
+ zIndex: 1,
947
+ opacity: 0.0,
948
+ id: source2,
949
+ sourceRect: undefined,
950
+ destRect: { x: halfWidth, y: 0, width: halfWidth, height: res.height }
951
+ }] : [])
952
+ ]
953
+ }
954
+ },
955
+ ...(targetSource ? [{
956
+ durationMs: 250,
957
+ layout: {
958
+ layers: [{
959
+ sourceName: targetSource,
960
+ zIndex: 0,
961
+ opacity: 1.0,
962
+ id: targetSource,
963
+ sourceRect: undefined,
964
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
965
+ }]
966
+ }
967
+ }] : [])
968
+ ];
969
+ }
970
+ })(),
732
971
  pip: [
733
972
  {
734
973
  durationMs: 600,
@@ -813,13 +1052,14 @@ exports.PRESETS = {
813
1052
  } : undefined
814
1053
  };
815
1054
  },
816
- 'lbar': (sources, config, res) => {
1055
+ 'lbar': (sources, config, res, transitionContext) => {
817
1056
  const [videoSource, overlaySource] = sources;
818
1057
  const { videoWidth, videoHeight, videoX = 50, videoY = 50 } = config;
819
- return createLBarPresetDefinition([videoSource, overlaySource], { videoWidth, videoHeight, videoX, videoY }, res);
1058
+ return createLBarPresetDefinition([videoSource, overlaySource], { videoWidth, videoHeight, videoX, videoY }, res, transitionContext);
820
1059
  },
821
- 'qr-lbar': (sources, config, res) => {
1060
+ 'qr-lbar': (sources, config, res, transitionContext) => {
822
1061
  const [videoSource, qrSource, overlaySource] = sources;
1062
+ const qrLbarSources = [videoSource, qrSource, overlaySource].filter(Boolean);
823
1063
  const { qrDurationMs, qrCorner, qrWidth, qrHeight, qrPadding = 20, videoWidth, videoHeight, videoX = 0, videoY = 0 } = config;
824
1064
  const qrPosition = (() => {
825
1065
  switch (qrCorner) {
@@ -932,44 +1172,112 @@ exports.PRESETS = {
932
1172
  ]
933
1173
  },
934
1174
  to: {
935
- fullscreen: [
936
- {
937
- durationMs: 500,
938
- layout: {
939
- layers: [
940
- {
941
- sourceName: overlaySource,
942
- id: 'overlay',
943
- zIndex: 0,
944
- opacity: 0.0,
945
- sourceRect: undefined,
946
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
947
- },
948
- {
949
- sourceName: videoSource,
950
- id: 'video',
951
- zIndex: 1,
952
- opacity: 1.0,
953
- sourceRect: undefined,
954
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
955
- },
956
- {
957
- sourceName: qrSource,
958
- id: 'qr',
959
- zIndex: 2,
960
- opacity: 0.0,
961
- sourceRect: undefined,
962
- destRect: { x: qrPosition.x, y: qrPosition.y, width: qrWidth, height: qrHeight }
1175
+ fullscreen: (() => {
1176
+ const targetSources = transitionContext?.targetSources ?? [];
1177
+ const targetSource = targetSources[0];
1178
+ const targetInQrLbar = targetSource && qrLbarSources.includes(targetSource);
1179
+ if (targetInQrLbar) {
1180
+ return [
1181
+ {
1182
+ durationMs: 500,
1183
+ layout: {
1184
+ layers: [
1185
+ {
1186
+ sourceName: overlaySource,
1187
+ id: 'overlay',
1188
+ zIndex: 0,
1189
+ opacity: overlaySource === targetSource ? 1.0 : 0.0,
1190
+ sourceRect: undefined,
1191
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1192
+ },
1193
+ {
1194
+ sourceName: videoSource,
1195
+ id: 'video',
1196
+ zIndex: 1,
1197
+ opacity: videoSource === targetSource ? 1.0 : 0.0,
1198
+ sourceRect: undefined,
1199
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1200
+ },
1201
+ {
1202
+ sourceName: qrSource,
1203
+ id: 'qr',
1204
+ zIndex: 2,
1205
+ opacity: qrSource === targetSource ? 1.0 : 0.0,
1206
+ sourceRect: undefined,
1207
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1208
+ }
1209
+ ]
963
1210
  }
964
- ]
965
- }
1211
+ },
1212
+ {
1213
+ durationMs: 0,
1214
+ layout: {
1215
+ layers: [{
1216
+ sourceName: targetSource,
1217
+ zIndex: 0,
1218
+ opacity: 1.0,
1219
+ id: targetSource,
1220
+ sourceRect: undefined,
1221
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1222
+ }]
1223
+ }
1224
+ }
1225
+ ];
966
1226
  }
967
- ]
1227
+ else {
1228
+ return [
1229
+ {
1230
+ durationMs: 250,
1231
+ layout: {
1232
+ layers: [
1233
+ {
1234
+ sourceName: overlaySource,
1235
+ id: 'overlay',
1236
+ zIndex: 0,
1237
+ opacity: 0.0,
1238
+ sourceRect: undefined,
1239
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1240
+ },
1241
+ {
1242
+ sourceName: videoSource,
1243
+ id: 'video',
1244
+ zIndex: 1,
1245
+ opacity: 0.0,
1246
+ sourceRect: undefined,
1247
+ destRect: { x: videoX, y: videoY, width: videoWidth, height: videoHeight }
1248
+ },
1249
+ {
1250
+ sourceName: qrSource,
1251
+ id: 'qr',
1252
+ zIndex: 2,
1253
+ opacity: 0.0,
1254
+ sourceRect: undefined,
1255
+ destRect: { x: qrPosition.x, y: qrPosition.y, width: qrWidth, height: qrHeight }
1256
+ }
1257
+ ]
1258
+ }
1259
+ },
1260
+ ...(targetSource ? [{
1261
+ durationMs: 250,
1262
+ layout: {
1263
+ layers: [{
1264
+ sourceName: targetSource,
1265
+ zIndex: 0,
1266
+ opacity: 1.0,
1267
+ id: targetSource,
1268
+ sourceRect: undefined,
1269
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1270
+ }]
1271
+ }
1272
+ }] : [])
1273
+ ];
1274
+ }
1275
+ })()
968
1276
  }
969
1277
  }
970
1278
  };
971
1279
  },
972
- '3-split': (sources, _config, res) => {
1280
+ '3-split': (sources, _config, res, transitionContext) => {
973
1281
  const third_w = res.width / 3;
974
1282
  const splits = sources.slice(0, 3);
975
1283
  return {
@@ -1070,21 +1378,76 @@ exports.PRESETS = {
1070
1378
  ]
1071
1379
  },
1072
1380
  to: {
1073
- fullscreen: [
1074
- {
1075
- durationMs: 500,
1076
- layout: {
1077
- layers: splits.map((s, i) => ({
1078
- sourceName: s,
1079
- zIndex: i,
1080
- opacity: i === 0 ? 1.0 : 0.0,
1081
- id: s,
1082
- sourceRect: undefined,
1083
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1084
- }))
1085
- }
1381
+ fullscreen: (() => {
1382
+ const targetSources = transitionContext?.targetSources ?? [];
1383
+ const targetSource = targetSources[0];
1384
+ const targetInSplits = targetSource && splits.includes(targetSource);
1385
+ if (targetInSplits) {
1386
+ return [
1387
+ {
1388
+ durationMs: 500,
1389
+ layout: {
1390
+ layers: splits.map((s, i) => ({
1391
+ sourceName: s,
1392
+ zIndex: i,
1393
+ opacity: s === targetSource ? 1.0 : 0.0,
1394
+ id: s,
1395
+ sourceRect: undefined,
1396
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1397
+ }))
1398
+ }
1399
+ },
1400
+ {
1401
+ durationMs: 0,
1402
+ layout: {
1403
+ layers: [{
1404
+ sourceName: targetSource,
1405
+ zIndex: 0,
1406
+ opacity: 1.0,
1407
+ id: targetSource,
1408
+ sourceRect: undefined,
1409
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1410
+ }]
1411
+ }
1412
+ }
1413
+ ];
1086
1414
  }
1087
- ],
1415
+ else {
1416
+ return [
1417
+ {
1418
+ durationMs: 250,
1419
+ layout: {
1420
+ layers: splits.map((s, i) => ({
1421
+ sourceName: s,
1422
+ zIndex: i,
1423
+ opacity: 0.0,
1424
+ id: s,
1425
+ sourceRect: undefined,
1426
+ destRect: {
1427
+ x: i * third_w,
1428
+ y: 0,
1429
+ width: third_w,
1430
+ height: res.height
1431
+ }
1432
+ }))
1433
+ }
1434
+ },
1435
+ ...(targetSource ? [{
1436
+ durationMs: 250,
1437
+ layout: {
1438
+ layers: [{
1439
+ sourceName: targetSource,
1440
+ zIndex: 0,
1441
+ opacity: 1.0,
1442
+ id: targetSource,
1443
+ sourceRect: undefined,
1444
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1445
+ }]
1446
+ }
1447
+ }] : [])
1448
+ ];
1449
+ }
1450
+ })(),
1088
1451
  'side-by-side': [
1089
1452
  {
1090
1453
  durationMs: 500,
@@ -1153,9 +1516,10 @@ exports.PRESETS = {
1153
1516
  } : undefined
1154
1517
  };
1155
1518
  },
1156
- '2-crop': (sources, config, res) => {
1519
+ '2-crop': (sources, config, res, transitionContext) => {
1157
1520
  const [left, right] = sources.slice(0, 2);
1158
1521
  const half_w = res.width / 2;
1522
+ const cropSources = [left, right].filter(Boolean);
1159
1523
  const crops = config.crops || {};
1160
1524
  return {
1161
1525
  name: '2-crop',
@@ -1231,7 +1595,92 @@ exports.PRESETS = {
1231
1595
  ]
1232
1596
  },
1233
1597
  to: {
1234
- fullscreen: [
1598
+ fullscreen: (() => {
1599
+ const targetSources = transitionContext?.targetSources ?? [];
1600
+ const targetSource = targetSources[0];
1601
+ const targetInCrops = targetSource && cropSources.includes(targetSource);
1602
+ if (targetInCrops) {
1603
+ return [
1604
+ {
1605
+ durationMs: 500,
1606
+ layout: {
1607
+ layers: [
1608
+ {
1609
+ sourceName: left,
1610
+ zIndex: 0,
1611
+ opacity: left === targetSource ? 1.0 : 0.0,
1612
+ id: left,
1613
+ sourceRect: undefined,
1614
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1615
+ },
1616
+ {
1617
+ sourceName: right,
1618
+ zIndex: 1,
1619
+ opacity: right === targetSource ? 1.0 : 0.0,
1620
+ id: right,
1621
+ sourceRect: undefined,
1622
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1623
+ }
1624
+ ]
1625
+ }
1626
+ },
1627
+ {
1628
+ durationMs: 0,
1629
+ layout: {
1630
+ layers: [{
1631
+ sourceName: targetSource,
1632
+ zIndex: 0,
1633
+ opacity: 1.0,
1634
+ id: targetSource,
1635
+ sourceRect: undefined,
1636
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1637
+ }]
1638
+ }
1639
+ }
1640
+ ];
1641
+ }
1642
+ else {
1643
+ return [
1644
+ {
1645
+ durationMs: 250,
1646
+ layout: {
1647
+ layers: [
1648
+ {
1649
+ sourceName: left,
1650
+ zIndex: 0,
1651
+ opacity: 0.0,
1652
+ id: left,
1653
+ sourceRect: crops[left] || undefined,
1654
+ destRect: { x: 0, y: 0, width: half_w, height: res.height }
1655
+ },
1656
+ {
1657
+ sourceName: right,
1658
+ zIndex: 1,
1659
+ opacity: 0.0,
1660
+ id: right,
1661
+ sourceRect: crops[right] || undefined,
1662
+ destRect: { x: half_w, y: 0, width: half_w, height: res.height }
1663
+ }
1664
+ ]
1665
+ }
1666
+ },
1667
+ ...(targetSource ? [{
1668
+ durationMs: 250,
1669
+ layout: {
1670
+ layers: [{
1671
+ sourceName: targetSource,
1672
+ zIndex: 0,
1673
+ opacity: 1.0,
1674
+ id: targetSource,
1675
+ sourceRect: undefined,
1676
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1677
+ }]
1678
+ }
1679
+ }] : [])
1680
+ ];
1681
+ }
1682
+ })(),
1683
+ '3-crop': [
1235
1684
  {
1236
1685
  durationMs: 500,
1237
1686
  layout: {
@@ -1241,38 +1690,13 @@ exports.PRESETS = {
1241
1690
  zIndex: 0,
1242
1691
  opacity: 1.0,
1243
1692
  id: left,
1244
- sourceRect: undefined,
1245
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1693
+ sourceRect: { x: res.width * 0.33333, y: 0, width: res.width * 0.33333, height: res.height },
1694
+ destRect: { x: 0, y: 0, width: res.width / 3, height: res.height }
1246
1695
  },
1247
1696
  {
1248
1697
  sourceName: right,
1249
1698
  zIndex: 1,
1250
- opacity: 0.0,
1251
- id: right,
1252
- sourceRect: undefined,
1253
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1254
- }
1255
- ]
1256
- }
1257
- }
1258
- ],
1259
- '3-crop': [
1260
- {
1261
- durationMs: 500,
1262
- layout: {
1263
- layers: [
1264
- {
1265
- sourceName: left,
1266
- zIndex: 0,
1267
- opacity: 1.0,
1268
- id: left,
1269
- sourceRect: { x: res.width * 0.33333, y: 0, width: res.width * 0.33333, height: res.height },
1270
- destRect: { x: 0, y: 0, width: res.width / 3, height: res.height }
1271
- },
1272
- {
1273
- sourceName: right,
1274
- zIndex: 1,
1275
- opacity: 1.0,
1699
+ opacity: 1.0,
1276
1700
  id: right,
1277
1701
  sourceRect: { x: res.width * 0.33333, y: 0, width: res.width * 0.33333, height: res.height },
1278
1702
  destRect: { x: res.width / 3, y: 0, width: res.width / 3, height: res.height }
@@ -1310,7 +1734,7 @@ exports.PRESETS = {
1310
1734
  } : undefined
1311
1735
  };
1312
1736
  },
1313
- '3-crop': (sources, config, res) => {
1737
+ '3-crop': (sources, config, res, transitionContext) => {
1314
1738
  const third_w = res.width / 3;
1315
1739
  const cropSources = sources.slice(0, 3);
1316
1740
  const cropConfig = config.crops || {};
@@ -1368,21 +1792,76 @@ exports.PRESETS = {
1368
1792
  ]
1369
1793
  },
1370
1794
  to: {
1371
- fullscreen: [
1372
- {
1373
- durationMs: 500,
1374
- layout: {
1375
- layers: cropSources.map((s, i) => ({
1376
- sourceName: s,
1377
- zIndex: i,
1378
- opacity: i === 0 ? 1.0 : 0.0,
1379
- id: s,
1380
- sourceRect: undefined,
1381
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1382
- }))
1383
- }
1795
+ fullscreen: (() => {
1796
+ const targetSources = transitionContext?.targetSources ?? [];
1797
+ const targetSource = targetSources[0];
1798
+ const targetInCrops = targetSource && cropSources.includes(targetSource);
1799
+ if (targetInCrops) {
1800
+ return [
1801
+ {
1802
+ durationMs: 500,
1803
+ layout: {
1804
+ layers: cropSources.map((s, i) => ({
1805
+ sourceName: s,
1806
+ zIndex: i,
1807
+ opacity: s === targetSource ? 1.0 : 0.0,
1808
+ id: s,
1809
+ sourceRect: undefined,
1810
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1811
+ }))
1812
+ }
1813
+ },
1814
+ {
1815
+ durationMs: 0,
1816
+ layout: {
1817
+ layers: [{
1818
+ sourceName: targetSource,
1819
+ zIndex: 0,
1820
+ opacity: 1.0,
1821
+ id: targetSource,
1822
+ sourceRect: undefined,
1823
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1824
+ }]
1825
+ }
1826
+ }
1827
+ ];
1384
1828
  }
1385
- ],
1829
+ else {
1830
+ return [
1831
+ {
1832
+ durationMs: 250,
1833
+ layout: {
1834
+ layers: cropSources.map((s, i) => ({
1835
+ sourceName: s,
1836
+ zIndex: i,
1837
+ opacity: 0.0,
1838
+ id: s,
1839
+ sourceRect: cropConfig[s] || undefined,
1840
+ destRect: {
1841
+ x: i * third_w,
1842
+ y: 0,
1843
+ width: third_w,
1844
+ height: res.height
1845
+ }
1846
+ }))
1847
+ }
1848
+ },
1849
+ ...(targetSource ? [{
1850
+ durationMs: 250,
1851
+ layout: {
1852
+ layers: [{
1853
+ sourceName: targetSource,
1854
+ zIndex: 0,
1855
+ opacity: 1.0,
1856
+ id: targetSource,
1857
+ sourceRect: undefined,
1858
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
1859
+ }]
1860
+ }
1861
+ }] : [])
1862
+ ];
1863
+ }
1864
+ })(),
1386
1865
  '2-crop': [
1387
1866
  {
1388
1867
  durationMs: 500,
@@ -1416,8 +1895,9 @@ exports.PRESETS = {
1416
1895
  } : undefined
1417
1896
  };
1418
1897
  },
1419
- '2-crop-left': (sources, config, res) => {
1898
+ '2-crop-left': (sources, config, res, transitionContext) => {
1420
1899
  const [background, left, right] = sources.slice(0, 3);
1900
+ const cropLeftSources = [background, left, right].filter(Boolean);
1421
1901
  const cropConfig = config.crops || {};
1422
1902
  const leftW = res.width * 0.34;
1423
1903
  const rightW = res.width * 0.66666;
@@ -1522,39 +2002,107 @@ exports.PRESETS = {
1522
2002
  ]
1523
2003
  },
1524
2004
  to: {
1525
- fullscreen: [
1526
- {
1527
- durationMs: 500,
1528
- layout: {
1529
- layers: [
1530
- {
1531
- sourceName: background,
1532
- zIndex: 0,
1533
- opacity: 1.0,
1534
- id: background,
1535
- sourceRect: undefined,
1536
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1537
- },
1538
- {
1539
- sourceName: left,
1540
- zIndex: 1,
1541
- opacity: 0.0,
1542
- id: left,
1543
- sourceRect: undefined,
1544
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1545
- },
1546
- {
1547
- sourceName: right,
1548
- zIndex: 2,
1549
- opacity: 0.0,
1550
- id: right,
1551
- sourceRect: undefined,
1552
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
2005
+ fullscreen: (() => {
2006
+ const targetSources = transitionContext?.targetSources ?? [];
2007
+ const targetSource = targetSources[0];
2008
+ const targetInCrops = targetSource && cropLeftSources.includes(targetSource);
2009
+ if (targetInCrops) {
2010
+ return [
2011
+ {
2012
+ durationMs: 500,
2013
+ layout: {
2014
+ layers: [
2015
+ {
2016
+ sourceName: background,
2017
+ zIndex: 0,
2018
+ opacity: background === targetSource ? 1.0 : 0.0,
2019
+ id: background,
2020
+ sourceRect: undefined,
2021
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2022
+ },
2023
+ {
2024
+ sourceName: left,
2025
+ zIndex: 1,
2026
+ opacity: left === targetSource ? 1.0 : 0.0,
2027
+ id: left,
2028
+ sourceRect: undefined,
2029
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2030
+ },
2031
+ {
2032
+ sourceName: right,
2033
+ zIndex: 2,
2034
+ opacity: right === targetSource ? 1.0 : 0.0,
2035
+ id: right,
2036
+ sourceRect: undefined,
2037
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2038
+ }
2039
+ ]
1553
2040
  }
1554
- ]
1555
- }
2041
+ },
2042
+ {
2043
+ durationMs: 0,
2044
+ layout: {
2045
+ layers: [{
2046
+ sourceName: targetSource,
2047
+ zIndex: 0,
2048
+ opacity: 1.0,
2049
+ id: targetSource,
2050
+ sourceRect: undefined,
2051
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2052
+ }]
2053
+ }
2054
+ }
2055
+ ];
1556
2056
  }
1557
- ],
2057
+ else {
2058
+ return [
2059
+ {
2060
+ durationMs: 250,
2061
+ layout: {
2062
+ layers: [
2063
+ {
2064
+ sourceName: background,
2065
+ zIndex: 0,
2066
+ opacity: 0.0,
2067
+ id: background,
2068
+ sourceRect: undefined,
2069
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2070
+ },
2071
+ {
2072
+ sourceName: left,
2073
+ zIndex: 1,
2074
+ opacity: 0.0,
2075
+ id: left,
2076
+ sourceRect: cropConfig[left] || undefined,
2077
+ destRect: { x: 0, y: yPos, width: leftW, height: boxH }
2078
+ },
2079
+ {
2080
+ sourceName: right,
2081
+ zIndex: 2,
2082
+ opacity: 0.0,
2083
+ id: right,
2084
+ sourceRect: undefined,
2085
+ destRect: { x: rightX, y: yPos, width: rightW, height: boxH }
2086
+ }
2087
+ ]
2088
+ }
2089
+ },
2090
+ ...(targetSource ? [{
2091
+ durationMs: 250,
2092
+ layout: {
2093
+ layers: [{
2094
+ sourceName: targetSource,
2095
+ zIndex: 0,
2096
+ opacity: 1.0,
2097
+ id: targetSource,
2098
+ sourceRect: undefined,
2099
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2100
+ }]
2101
+ }
2102
+ }] : [])
2103
+ ];
2104
+ }
2105
+ })(),
1558
2106
  '2-crop-right': [
1559
2107
  {
1560
2108
  durationMs: 600,
@@ -1592,8 +2140,9 @@ exports.PRESETS = {
1592
2140
  } : undefined
1593
2141
  };
1594
2142
  },
1595
- '2-crop-right': (sources, config, res) => {
2143
+ '2-crop-right': (sources, config, res, transitionContext) => {
1596
2144
  const [background, left, right] = sources.slice(0, 3);
2145
+ const cropRightSources = [background, left, right].filter(Boolean);
1597
2146
  const cropConfig = config.crops || {};
1598
2147
  const leftW = res.width * 0.67;
1599
2148
  const rightW = res.width * 0.33333;
@@ -1698,39 +2247,107 @@ exports.PRESETS = {
1698
2247
  ]
1699
2248
  },
1700
2249
  to: {
1701
- fullscreen: [
1702
- {
1703
- durationMs: 500,
1704
- layout: {
1705
- layers: [
1706
- {
1707
- sourceName: background,
1708
- zIndex: 0,
1709
- opacity: 1.0,
1710
- id: background,
1711
- sourceRect: undefined,
1712
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1713
- },
1714
- {
1715
- sourceName: left,
1716
- zIndex: 1,
1717
- opacity: 0.0,
1718
- id: left,
1719
- sourceRect: undefined,
1720
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1721
- },
1722
- {
1723
- sourceName: right,
1724
- zIndex: 2,
1725
- opacity: 0.0,
1726
- id: right,
1727
- sourceRect: undefined,
1728
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
2250
+ fullscreen: (() => {
2251
+ const targetSources = transitionContext?.targetSources ?? [];
2252
+ const targetSource = targetSources[0];
2253
+ const targetInCrops = targetSource && cropRightSources.includes(targetSource);
2254
+ if (targetInCrops) {
2255
+ return [
2256
+ {
2257
+ durationMs: 500,
2258
+ layout: {
2259
+ layers: [
2260
+ {
2261
+ sourceName: background,
2262
+ zIndex: 0,
2263
+ opacity: background === targetSource ? 1.0 : 0.0,
2264
+ id: background,
2265
+ sourceRect: undefined,
2266
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2267
+ },
2268
+ {
2269
+ sourceName: left,
2270
+ zIndex: 1,
2271
+ opacity: left === targetSource ? 1.0 : 0.0,
2272
+ id: left,
2273
+ sourceRect: undefined,
2274
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2275
+ },
2276
+ {
2277
+ sourceName: right,
2278
+ zIndex: 2,
2279
+ opacity: right === targetSource ? 1.0 : 0.0,
2280
+ id: right,
2281
+ sourceRect: undefined,
2282
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2283
+ }
2284
+ ]
1729
2285
  }
1730
- ]
1731
- }
2286
+ },
2287
+ {
2288
+ durationMs: 0,
2289
+ layout: {
2290
+ layers: [{
2291
+ sourceName: targetSource,
2292
+ zIndex: 0,
2293
+ opacity: 1.0,
2294
+ id: targetSource,
2295
+ sourceRect: undefined,
2296
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2297
+ }]
2298
+ }
2299
+ }
2300
+ ];
1732
2301
  }
1733
- ],
2302
+ else {
2303
+ return [
2304
+ {
2305
+ durationMs: 250,
2306
+ layout: {
2307
+ layers: [
2308
+ {
2309
+ sourceName: background,
2310
+ zIndex: 0,
2311
+ opacity: 0.0,
2312
+ id: background,
2313
+ sourceRect: undefined,
2314
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2315
+ },
2316
+ {
2317
+ sourceName: left,
2318
+ zIndex: 1,
2319
+ opacity: 0.0,
2320
+ id: left,
2321
+ sourceRect: undefined,
2322
+ destRect: { x: 0, y: yPos, width: leftW, height: boxH }
2323
+ },
2324
+ {
2325
+ sourceName: right,
2326
+ zIndex: 2,
2327
+ opacity: 0.0,
2328
+ id: right,
2329
+ sourceRect: cropConfig[right] || undefined,
2330
+ destRect: { x: rightX, y: yPos, width: rightW, height: boxH }
2331
+ }
2332
+ ]
2333
+ }
2334
+ },
2335
+ ...(targetSource ? [{
2336
+ durationMs: 250,
2337
+ layout: {
2338
+ layers: [{
2339
+ sourceName: targetSource,
2340
+ zIndex: 0,
2341
+ opacity: 1.0,
2342
+ id: targetSource,
2343
+ sourceRect: undefined,
2344
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2345
+ }]
2346
+ }
2347
+ }] : [])
2348
+ ];
2349
+ }
2350
+ })(),
1734
2351
  '2-crop-left': [
1735
2352
  {
1736
2353
  durationMs: 600,
@@ -1768,9 +2385,10 @@ exports.PRESETS = {
1768
2385
  } : undefined
1769
2386
  };
1770
2387
  },
1771
- '5-split': (sources, config, res) => {
2388
+ '5-split': (sources, config, res, transitionContext) => {
1772
2389
  const [background, ...vidSources] = sources.slice(0, 6);
1773
2390
  const fiveSources = vidSources.slice(0, 5);
2391
+ const allSplitSources = [background, ...fiveSources].filter(Boolean);
1774
2392
  const cropConfig = config.crops || {};
1775
2393
  const boxW = res.width * 0.34;
1776
2394
  const boxH = res.height * 0.5;
@@ -1862,36 +2480,101 @@ exports.PRESETS = {
1862
2480
  ]
1863
2481
  },
1864
2482
  to: {
1865
- fullscreen: [
1866
- {
1867
- durationMs: 500,
1868
- layout: {
1869
- layers: [
1870
- {
1871
- sourceName: background,
1872
- zIndex: 0,
1873
- opacity: 1.0,
1874
- id: background,
1875
- sourceRect: undefined,
1876
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1877
- },
1878
- ...fiveSources.map((s, i) => ({
1879
- sourceName: s,
1880
- zIndex: i + 1,
1881
- opacity: 0.0,
1882
- id: s,
1883
- sourceRect: undefined,
1884
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1885
- }))
1886
- ]
1887
- }
2483
+ fullscreen: (() => {
2484
+ const targetSources = transitionContext?.targetSources ?? [];
2485
+ const targetSource = targetSources[0];
2486
+ const targetInSplit = targetSource && allSplitSources.includes(targetSource);
2487
+ if (targetInSplit) {
2488
+ return [
2489
+ {
2490
+ durationMs: 500,
2491
+ layout: {
2492
+ layers: [
2493
+ {
2494
+ sourceName: background,
2495
+ zIndex: 0,
2496
+ opacity: background === targetSource ? 1.0 : 0.0,
2497
+ id: background,
2498
+ sourceRect: undefined,
2499
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2500
+ },
2501
+ ...fiveSources.map((s, i) => ({
2502
+ sourceName: s,
2503
+ zIndex: i + 1,
2504
+ opacity: s === targetSource ? 1.0 : 0.0,
2505
+ id: s,
2506
+ sourceRect: undefined,
2507
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2508
+ }))
2509
+ ]
2510
+ }
2511
+ },
2512
+ {
2513
+ durationMs: 0,
2514
+ layout: {
2515
+ layers: [{
2516
+ sourceName: targetSource,
2517
+ zIndex: 0,
2518
+ opacity: 1.0,
2519
+ id: targetSource,
2520
+ sourceRect: undefined,
2521
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2522
+ }]
2523
+ }
2524
+ }
2525
+ ];
1888
2526
  }
1889
- ]
2527
+ else {
2528
+ return [
2529
+ {
2530
+ durationMs: 250,
2531
+ layout: {
2532
+ layers: [
2533
+ {
2534
+ sourceName: background,
2535
+ zIndex: 0,
2536
+ opacity: 0.0,
2537
+ id: background,
2538
+ sourceRect: undefined,
2539
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2540
+ },
2541
+ ...fiveSources.map((s, i) => {
2542
+ const isTopRow = i < 2;
2543
+ const xPos = isTopRow ? topPositions[i] : bottomPositions[i - 2];
2544
+ const yPosCalc = isTopRow ? topY : bottomY;
2545
+ return {
2546
+ sourceName: s,
2547
+ zIndex: i + 1,
2548
+ opacity: 0.0,
2549
+ id: s,
2550
+ sourceRect: cropConfig[s] || undefined,
2551
+ destRect: { x: xPos - boxW / 2, y: yPosCalc - boxH / 2, width: boxW, height: boxH }
2552
+ };
2553
+ })
2554
+ ]
2555
+ }
2556
+ },
2557
+ ...(targetSource ? [{
2558
+ durationMs: 250,
2559
+ layout: {
2560
+ layers: [{
2561
+ sourceName: targetSource,
2562
+ zIndex: 0,
2563
+ opacity: 1.0,
2564
+ id: targetSource,
2565
+ sourceRect: undefined,
2566
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2567
+ }]
2568
+ }
2569
+ }] : [])
2570
+ ];
2571
+ }
2572
+ })()
1890
2573
  }
1891
2574
  } : undefined
1892
2575
  };
1893
2576
  },
1894
- '6-split': (sources, config, res) => {
2577
+ '6-split': (sources, config, res, transitionContext) => {
1895
2578
  const sixSources = sources.slice(0, 6);
1896
2579
  const cropConfig = config.crops || {};
1897
2580
  const boxW = res.width * 0.34;
@@ -1955,21 +2638,77 @@ exports.PRESETS = {
1955
2638
  ]
1956
2639
  },
1957
2640
  to: {
1958
- fullscreen: [
1959
- {
1960
- durationMs: 500,
1961
- layout: {
1962
- layers: sixSources.map((s, i) => ({
1963
- sourceName: s,
1964
- zIndex: i,
1965
- opacity: i === 0 ? 1.0 : 0.0,
1966
- id: s,
1967
- sourceRect: undefined,
1968
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
1969
- }))
1970
- }
2641
+ fullscreen: (() => {
2642
+ const targetSources = transitionContext?.targetSources ?? [];
2643
+ const targetSource = targetSources[0];
2644
+ const targetInSplit = targetSource && sixSources.includes(targetSource);
2645
+ if (targetInSplit) {
2646
+ return [
2647
+ {
2648
+ durationMs: 500,
2649
+ layout: {
2650
+ layers: sixSources.map((s, i) => ({
2651
+ sourceName: s,
2652
+ zIndex: i,
2653
+ opacity: s === targetSource ? 1.0 : 0.0,
2654
+ id: s,
2655
+ sourceRect: undefined,
2656
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2657
+ }))
2658
+ }
2659
+ },
2660
+ {
2661
+ durationMs: 0,
2662
+ layout: {
2663
+ layers: [{
2664
+ sourceName: targetSource,
2665
+ zIndex: 0,
2666
+ opacity: 1.0,
2667
+ id: targetSource,
2668
+ sourceRect: undefined,
2669
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2670
+ }]
2671
+ }
2672
+ }
2673
+ ];
1971
2674
  }
1972
- ],
2675
+ else {
2676
+ return [
2677
+ {
2678
+ durationMs: 250,
2679
+ layout: {
2680
+ layers: sixSources.map((s, i) => {
2681
+ const row = Math.floor(i / 3);
2682
+ const col = i % 3;
2683
+ const yPos = row === 0 ? topY : bottomY;
2684
+ const xPos = xPositions[col];
2685
+ return {
2686
+ sourceName: s,
2687
+ zIndex: i,
2688
+ opacity: 0.0,
2689
+ id: s,
2690
+ sourceRect: cropConfig[s] || undefined,
2691
+ destRect: { x: xPos - boxW / 2, y: yPos - boxH / 2, width: boxW, height: boxH }
2692
+ };
2693
+ })
2694
+ }
2695
+ },
2696
+ ...(targetSource ? [{
2697
+ durationMs: 250,
2698
+ layout: {
2699
+ layers: [{
2700
+ sourceName: targetSource,
2701
+ zIndex: 0,
2702
+ opacity: 1.0,
2703
+ id: targetSource,
2704
+ sourceRect: undefined,
2705
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2706
+ }]
2707
+ }
2708
+ }] : [])
2709
+ ];
2710
+ }
2711
+ })(),
1973
2712
  'mosaic-9': [
1974
2713
  {
1975
2714
  durationMs: 600,
@@ -2083,8 +2822,9 @@ function createSideBySidePresetDefinition(sources, res) {
2083
2822
  }
2084
2823
  };
2085
2824
  }
2086
- function createLBarPresetDefinition(sources, config, res) {
2825
+ function createLBarPresetDefinition(sources, config, res, transitionContext) {
2087
2826
  const [videoSource, overlaySource] = sources;
2827
+ const lbarSources = [videoSource, overlaySource].filter(Boolean);
2088
2828
  const { videoWidth, videoHeight, videoX = 0, videoY = 0 } = config;
2089
2829
  return {
2090
2830
  name: 'lbar',
@@ -2160,37 +2900,98 @@ function createLBarPresetDefinition(sources, config, res) {
2160
2900
  ]
2161
2901
  },
2162
2902
  to: {
2163
- fullscreen: [
2164
- {
2165
- durationMs: 500,
2166
- layout: {
2167
- layers: [
2168
- {
2169
- sourceName: overlaySource,
2170
- id: 'overlay',
2171
- zIndex: 0,
2172
- opacity: 0.0,
2173
- sourceRect: undefined,
2174
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
2175
- },
2176
- {
2177
- sourceName: videoSource,
2178
- id: 'video',
2179
- zIndex: 1,
2180
- opacity: 1.0,
2181
- sourceRect: undefined,
2182
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
2903
+ fullscreen: (() => {
2904
+ const targetSources = transitionContext?.targetSources ?? [];
2905
+ const targetSource = targetSources[0];
2906
+ const targetInLbar = targetSource && lbarSources.includes(targetSource);
2907
+ if (targetInLbar) {
2908
+ return [
2909
+ {
2910
+ durationMs: 500,
2911
+ layout: {
2912
+ layers: [
2913
+ {
2914
+ sourceName: overlaySource,
2915
+ id: 'overlay',
2916
+ zIndex: 0,
2917
+ opacity: overlaySource === targetSource ? 1.0 : 0.0,
2918
+ sourceRect: undefined,
2919
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2920
+ },
2921
+ {
2922
+ sourceName: videoSource,
2923
+ id: 'video',
2924
+ zIndex: 1,
2925
+ opacity: videoSource === targetSource ? 1.0 : 0.0,
2926
+ sourceRect: undefined,
2927
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2928
+ }
2929
+ ]
2183
2930
  }
2184
- ]
2185
- }
2931
+ },
2932
+ {
2933
+ durationMs: 0,
2934
+ layout: {
2935
+ layers: [{
2936
+ sourceName: targetSource,
2937
+ zIndex: 0,
2938
+ opacity: 1.0,
2939
+ id: targetSource,
2940
+ sourceRect: undefined,
2941
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2942
+ }]
2943
+ }
2944
+ }
2945
+ ];
2186
2946
  }
2187
- ]
2947
+ else {
2948
+ return [
2949
+ {
2950
+ durationMs: 250,
2951
+ layout: {
2952
+ layers: [
2953
+ {
2954
+ sourceName: overlaySource,
2955
+ id: 'overlay',
2956
+ zIndex: 0,
2957
+ opacity: 0.0,
2958
+ sourceRect: undefined,
2959
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2960
+ },
2961
+ {
2962
+ sourceName: videoSource,
2963
+ id: 'video',
2964
+ zIndex: 1,
2965
+ opacity: 0.0,
2966
+ sourceRect: undefined,
2967
+ destRect: { x: videoX, y: videoY, width: videoWidth, height: videoHeight }
2968
+ }
2969
+ ]
2970
+ }
2971
+ },
2972
+ ...(targetSource ? [{
2973
+ durationMs: 250,
2974
+ layout: {
2975
+ layers: [{
2976
+ sourceName: targetSource,
2977
+ zIndex: 0,
2978
+ opacity: 1.0,
2979
+ id: targetSource,
2980
+ sourceRect: undefined,
2981
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
2982
+ }]
2983
+ }
2984
+ }] : [])
2985
+ ];
2986
+ }
2987
+ })()
2188
2988
  }
2189
2989
  }
2190
2990
  };
2191
2991
  }
2192
- function createQrLBarPresetDefinition(sources, config, res) {
2992
+ function createQrLBarPresetDefinition(sources, config, res, transitionContext) {
2193
2993
  const [videoSource, qrSource, overlaySource] = sources;
2994
+ const qrLbarSources = [videoSource, qrSource, overlaySource].filter(Boolean);
2194
2995
  const { qrDurationMs, qrCorner, qrWidth, qrHeight, qrPadding = 20, videoWidth, videoHeight, videoX = 0, videoY = 0 } = config;
2195
2996
  const qrPosition = (() => {
2196
2997
  switch (qrCorner) {
@@ -2302,39 +3103,107 @@ function createQrLBarPresetDefinition(sources, config, res) {
2302
3103
  ]
2303
3104
  },
2304
3105
  to: {
2305
- fullscreen: [
2306
- {
2307
- durationMs: 500,
2308
- layout: {
2309
- layers: [
2310
- {
2311
- sourceName: overlaySource,
2312
- id: 'overlay',
2313
- zIndex: 0,
2314
- opacity: 0.0,
2315
- sourceRect: undefined,
2316
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
2317
- },
2318
- {
2319
- sourceName: videoSource,
2320
- id: 'video',
2321
- zIndex: 1,
2322
- opacity: 1.0,
2323
- sourceRect: undefined,
2324
- destRect: { x: 0, y: 0, width: res.width, height: res.height }
2325
- },
2326
- {
2327
- sourceName: qrSource,
2328
- id: 'qr',
2329
- zIndex: 2,
2330
- opacity: 0.0,
2331
- sourceRect: undefined,
2332
- destRect: { x: qrPosition.x, y: qrPosition.y, width: qrWidth, height: qrHeight }
3106
+ fullscreen: (() => {
3107
+ const targetSources = transitionContext?.targetSources ?? [];
3108
+ const targetSource = targetSources[0];
3109
+ const targetInQrLbar = targetSource && qrLbarSources.includes(targetSource);
3110
+ if (targetInQrLbar) {
3111
+ return [
3112
+ {
3113
+ durationMs: 500,
3114
+ layout: {
3115
+ layers: [
3116
+ {
3117
+ sourceName: overlaySource,
3118
+ id: 'overlay',
3119
+ zIndex: 0,
3120
+ opacity: overlaySource === targetSource ? 1.0 : 0.0,
3121
+ sourceRect: undefined,
3122
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
3123
+ },
3124
+ {
3125
+ sourceName: videoSource,
3126
+ id: 'video',
3127
+ zIndex: 1,
3128
+ opacity: videoSource === targetSource ? 1.0 : 0.0,
3129
+ sourceRect: undefined,
3130
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
3131
+ },
3132
+ {
3133
+ sourceName: qrSource,
3134
+ id: 'qr',
3135
+ zIndex: 2,
3136
+ opacity: qrSource === targetSource ? 1.0 : 0.0,
3137
+ sourceRect: undefined,
3138
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
3139
+ }
3140
+ ]
2333
3141
  }
2334
- ]
2335
- }
3142
+ },
3143
+ {
3144
+ durationMs: 0,
3145
+ layout: {
3146
+ layers: [{
3147
+ sourceName: targetSource,
3148
+ zIndex: 0,
3149
+ opacity: 1.0,
3150
+ id: targetSource,
3151
+ sourceRect: undefined,
3152
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
3153
+ }]
3154
+ }
3155
+ }
3156
+ ];
2336
3157
  }
2337
- ]
3158
+ else {
3159
+ return [
3160
+ {
3161
+ durationMs: 250,
3162
+ layout: {
3163
+ layers: [
3164
+ {
3165
+ sourceName: overlaySource,
3166
+ id: 'overlay',
3167
+ zIndex: 0,
3168
+ opacity: 0.0,
3169
+ sourceRect: undefined,
3170
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
3171
+ },
3172
+ {
3173
+ sourceName: videoSource,
3174
+ id: 'video',
3175
+ zIndex: 1,
3176
+ opacity: 0.0,
3177
+ sourceRect: undefined,
3178
+ destRect: { x: videoX, y: videoY, width: videoWidth, height: videoHeight }
3179
+ },
3180
+ {
3181
+ sourceName: qrSource,
3182
+ id: 'qr',
3183
+ zIndex: 2,
3184
+ opacity: 0.0,
3185
+ sourceRect: undefined,
3186
+ destRect: { x: qrPosition.x, y: qrPosition.y, width: qrWidth, height: qrHeight }
3187
+ }
3188
+ ]
3189
+ }
3190
+ },
3191
+ ...(targetSource ? [{
3192
+ durationMs: 250,
3193
+ layout: {
3194
+ layers: [{
3195
+ sourceName: targetSource,
3196
+ zIndex: 0,
3197
+ opacity: 1.0,
3198
+ id: targetSource,
3199
+ sourceRect: undefined,
3200
+ destRect: { x: 0, y: 0, width: res.width, height: res.height }
3201
+ }]
3202
+ }
3203
+ }] : [])
3204
+ ];
3205
+ }
3206
+ })()
2338
3207
  }
2339
3208
  }
2340
3209
  };