@norskvideo/norsk-studio-built-ins 1.27.0-2026-01-10-23683704 → 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.
Files changed (34) hide show
  1. package/client/info.js +1262 -404
  2. package/lib/input.srt-listener/_gen/types.d.ts +1 -0
  3. package/lib/input.srt-listener/_gen/yaml-docs.js +3 -0
  4. package/lib/input.srt-listener/_gen/yaml-docs.js.map +1 -1
  5. package/lib/input.srt-listener/_gen/zod.js +1 -0
  6. package/lib/input.srt-listener/_gen/zod.js.map +1 -1
  7. package/lib/input.srt-listener/info.d.ts +1 -0
  8. package/lib/input.srt-listener/info.js +9 -0
  9. package/lib/input.srt-listener/info.js.map +1 -1
  10. package/lib/input.srt-listener/runtime.d.ts +1 -0
  11. package/lib/input.srt-listener/runtime.js +7 -0
  12. package/lib/input.srt-listener/runtime.js.map +1 -1
  13. package/lib/input.srt-listener/types.yaml +2 -0
  14. package/lib/processor.browserOverlay/runtime.js +1 -1
  15. package/lib/processor.browserOverlay/runtime.js.map +1 -1
  16. package/lib/processor.onscreenGraphic/runtime.js +1 -1
  17. package/lib/processor.onscreenGraphic/runtime.js.map +1 -1
  18. package/lib/processor.smartSourceSwitch/inline-view.d.ts +3 -5
  19. package/lib/processor.smartSourceSwitch/inline-view.js +16 -4
  20. package/lib/processor.smartSourceSwitch/inline-view.js.map +1 -1
  21. package/lib/processor.videoCompose/fullscreen.js +28 -10
  22. package/lib/processor.videoCompose/fullscreen.js.map +1 -1
  23. package/lib/processor.videoCompose/preset-transition-panel.js +37 -8
  24. package/lib/processor.videoCompose/preset-transition-panel.js.map +1 -1
  25. package/lib/processor.videoCompose/presets.d.ts +8 -3
  26. package/lib/processor.videoCompose/presets.js +1215 -346
  27. package/lib/processor.videoCompose/presets.js.map +1 -1
  28. package/lib/processor.videoCompose/runtime.js +58 -44
  29. package/lib/processor.videoCompose/runtime.js.map +1 -1
  30. package/lib/processor.videoCompose/visual-preview.js +3 -1
  31. package/lib/processor.videoCompose/visual-preview.js.map +1 -1
  32. package/lib/processor.zoomTo/runtime.js +1 -1
  33. package/lib/processor.zoomTo/runtime.js.map +1 -1
  34. package/package.json +3 -3
@@ -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
  };