Skip to content

Dual Output Studio Mode #5343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions app/components-react/editor/elements/SceneSelector.m.less
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
align-items: center;
justify-content: flex-end;

>div:last-child {
> div:last-child {
top: 44px !important;
height: calc(100% - 44px);
}
Expand Down Expand Up @@ -34,7 +34,7 @@
}

:global(.no-top-padding) {
.top-container>div:last-child {
.top-container > div:last-child {
top: 32px !important;
height: calc(100% - 32px);
}
Expand Down Expand Up @@ -171,7 +171,7 @@
display: flex;
align-items: center;

>i {
> i {
margin-right: 8px;
opacity: 0;
}
Expand Down Expand Up @@ -257,9 +257,6 @@
}
}

.display-toggle {
i {
line-height: unset;
margin-left: 10px !important;
}
.editor-display-toggle {
margin-left: 10px;
}
107 changes: 10 additions & 97 deletions app/components-react/editor/elements/SceneSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState, useRef, useMemo } from 'react';
import React, { useState, useRef } from 'react';
import cx from 'classnames';
import { Dropdown, Tooltip as AntdTooltip, Tree, message } from 'antd';
import Tooltip from 'components-react/shared/Tooltip';
import { Dropdown, Tooltip, Tree } from 'antd';
import { DownOutlined } from '@ant-design/icons';
import * as remote from '@electron/remote';
import { Menu } from 'util/menus/Menu';
Expand All @@ -10,14 +9,14 @@ import { Services } from 'components-react/service-provider';
import { useVuex } from 'components-react/hooks';
import HelpTip from 'components-react/shared/HelpTip';
import Scrollable from 'components-react/shared/Scrollable';
import { DisplayToggle } from 'components-react/shared/DisplayToggle';
import { useTree, IOnDropInfo } from 'components-react/hooks/useTree';
import { $t } from 'services/i18n';
import { EDismissable } from 'services/dismissables';
import { ERenderingMode } from '../../../../obs-api';
import styles from './SceneSelector.m.less';
import useBaseElement from './hooks';
import { IScene } from 'services/scenes';
import { ISceneCollectionsManifestEntry } from 'services/scene-collections';

function SceneSelector() {
const {
Expand All @@ -32,9 +31,6 @@ function SceneSelector() {
} = Services;

const v = useVuex(() => ({
isHorizontal: DualOutputService.views.activeDisplays.horizontal,
isVertical: DualOutputService.views.activeDisplays.vertical,
toggleDisplay: DualOutputService.actions.toggleDisplay,
studioMode: TransitionsService.views.studioMode,
isMidStreamMode: StreamingService.views.isMidStreamMode,
showDualOutput: DualOutputService.views.dualOutputMode,
Expand All @@ -57,16 +53,6 @@ function SceneSelector() {
collections: SceneCollectionsService.collections,
}));

const horizontalTooltip = useMemo(
() => (v.isHorizontal ? $t('Hide horizontal display.') : $t('Show horizontal display.')),
[v.isHorizontal],
);

const verticalTooltip = useMemo(
() => (v.isVertical ? $t('Hide vertical display.') : $t('Show vertical display.')),
[v.isVertical],
);

function showContextMenu(info: { event: React.MouseEvent }) {
info.event.preventDefault();
info.event.stopPropagation();
Expand Down Expand Up @@ -147,27 +133,6 @@ function SceneSelector() {
setShowDropdown(false);
}

function showStudioModeErrorMessage() {
message.error({
content: $t('Cannot toggle dual output in Studio Mode.'),
className: styles.toggleError,
});
}

function showToggleDisplayErrorMessage() {
message.error({
content: $t('Cannot change displays while live.'),
className: styles.toggleError,
});
}

function showSelectiveRecordingMessage() {
message.error({
content: $t('Selective Recording can only be used with horizontal sources.'),
className: styles.toggleError,
});
}

const DropdownMenu = (
<div className={cx(styles.dropdownContainer, 'react')}>
<div className={styles.dropdownItem} onClick={manageCollections} style={{ marginTop: '6px' }}>
Expand Down Expand Up @@ -215,67 +180,15 @@ function SceneSelector() {
<span className={styles.activeScene}>{activeCollection?.name}</span>
</span>
</Dropdown>
<AntdTooltip title={$t('Add a new Scene.')} placement="bottomLeft">
<Tooltip title={$t('Add a new Scene.')} placement="bottomLeft">
<i className="icon-add-circle icon-button icon-button--lg" onClick={addScene} />
</AntdTooltip>

{v.showDualOutput && (
<Tooltip
id="toggle-horizontal-tooltip"
title={horizontalTooltip}
className={styles.displayToggle}
placement="bottomRight"
>
<i
id="horizontal-display-toggle"
onClick={() => {
if (v.isMidStreamMode) {
showToggleDisplayErrorMessage();
} else if (v.studioMode && v.isVertical) {
showStudioModeErrorMessage();
} else {
v.toggleDisplay(!v.isHorizontal, 'horizontal');
}
}}
className={cx('icon-desktop icon-button icon-button--lg', {
active: v.isHorizontal,
})}
/>
</Tooltip>
)}
</Tooltip>

{v.showDualOutput && (
<Tooltip
id="toggle-vertical-tooltip"
title={verticalTooltip}
className={styles.displayToggle}
placement="bottomRight"
disabled={v.selectiveRecording}
>
<i
id="vertical-display-toggle"
onClick={() => {
if (v.isMidStreamMode) {
showToggleDisplayErrorMessage();
} else if (v.studioMode && v.isHorizontal) {
showStudioModeErrorMessage();
} else if (v.selectiveRecording) {
showSelectiveRecordingMessage();
} else {
v.toggleDisplay(!v.isVertical, 'vertical');
}
}}
className={cx('icon-phone-case icon-button icon-button--lg', {
active: v.isVertical && !v.selectiveRecording,
disabled: v.selectiveRecording,
})}
/>
</Tooltip>
)}
{v.showDualOutput && <DisplayToggle className={styles.editorDisplayToggle} />}

<AntdTooltip title={$t('Edit Scene Transitions.')} placement="bottomRight">
<Tooltip title={$t('Edit Scene Transitions.')} placement="bottomRight">
<i className="icon-transition icon-button icon-button--lg" onClick={showTransitions} />
</AntdTooltip>
</Tooltip>
</div>
<Scrollable style={{ height: '100%' }} className={styles.scenesContainer}>
<Tree
Expand Down Expand Up @@ -306,9 +219,9 @@ function TreeNode(p: { scene: IScene; removeScene: (scene: IScene) => void }) {
return (
<div className={styles.sourceTitleContainer} data-name={p.scene.name} data-role="scene">
<span className={styles.sourceTitle}>{p.scene.name}</span>
<AntdTooltip title={$t('Remove Scene.')} placement="left">
<Tooltip title={$t('Remove Scene.')} placement="left">
<i onClick={() => p.removeScene(p.scene)} className="icon-trash" />
</AntdTooltip>
</Tooltip>
</div>
);
}
Expand Down
5 changes: 0 additions & 5 deletions app/components-react/editor/elements/SourceSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -660,11 +660,6 @@ class SourceSelectorController {
content: $t('Cannot toggle Dual Output while live.'),
className: styles.toggleError,
});
} else if (Services.TransitionsService.views.studioMode) {
message.error({
content: $t('Cannot toggle Dual Output while in Studio Mode.'),
className: styles.toggleError,
});
} else {
// only open video settings when toggling on dual output
const skipShowVideoSettings = this.dualOutputService.views.dualOutputMode === true;
Expand Down
50 changes: 37 additions & 13 deletions app/components-react/root/LiveDock.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import * as remote from '@electron/remote';
import cx from 'classnames';
import Animation from 'rc-animate';
import { Button, Menu } from 'antd';
import { Menu } from 'antd';
import pick from 'lodash/pick';
import { initStore, useController } from 'components-react/hooks/zustand';
import { EStreamingState } from 'services/streaming';
Expand All @@ -17,7 +17,6 @@ import PlatformAppPageView from 'components-react/shared/PlatformAppPageView';
import { useVuex } from 'components-react/hooks';
import { useRealmObject } from 'components-react/hooks/realm';
import { $i } from 'services/utils';
import { TikTokChatInfo } from './TiktokChatInfo';
import { ShareStreamLink } from './ShareStreamLink';

const LiveDockCtx = React.createContext<LiveDockController | null>(null);
Expand Down Expand Up @@ -221,12 +220,6 @@ class LiveDockController {
});
this.windowsService.actions.updateStyleBlockers('main', true);
this.customizationService.actions.setSettings({ livedockCollapsed });
setTimeout(() => {
this.store.setState(s => {
s.canAnimate = false;
});
this.windowsService.actions.updateStyleBlockers('main', false);
}, 300);
}

toggleViewerCount() {
Expand Down Expand Up @@ -265,29 +258,25 @@ function LiveDock(p: { onLeft: boolean }) {
isStreaming,
isRestreaming,
hasChatTabs,
chatTabs,
applicationLoading,
hideStyleBlockers,
currentViewers,
pageSlot,
canAnimate,
liveText,
isPopOutAllowed,
streamingStatus,
} = useVuex(() =>
pick(ctrl, [
'isPlatform',
'isStreaming',
'isRestreaming',
'hasChatTabs',
'chatTabs',
'applicationLoading',
'hideStyleBlockers',
'pageSlot',
'canAnimate',
'currentViewers',
'liveText',
'isPopOutAllowed',
'streamingStatus',
]),
);
Expand All @@ -298,6 +287,40 @@ function LiveDock(p: { onLeft: boolean }) {

const viewerCount = hideViewerCount ? $t('Viewers Hidden') : currentViewers;

const liveDockRef = useRef<HTMLDivElement>(null);

// Min and max widths accommodate a 1px border
const minWidth = p.onLeft ? 18 : 19;
const maxWidth = p.onLeft ? liveDockSize - minWidth : liveDockSize - minWidth + 2;

const resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => {
entries.forEach((entry: ResizeObserverEntry) => {
if (!entry || !entry?.contentRect) return;

const width = Math.floor(entry?.contentRect?.width);
if (width === minWidth || width === maxWidth) {
Services.WindowsService.actions.updateStyleBlockers('main', false);

ctrl.store.setState(s => {
s.canAnimate = false;
});
}
});
});

useEffect(() => {
let mounted = true;
if (liveDockRef && liveDockRef?.current) {
resizeObserver.observe(liveDockRef?.current);
}

return () => {
if (!liveDockRef || !liveDockRef?.current) return;
resizeObserver.unobserve(liveDockRef.current);
mounted = false;
};
}, [liveDockRef, liveDockRef?.current]);

useEffect(() => {
if (streamingStatus === EStreamingState.Starting && collapsed) {
ctrl.setCollapsed(false);
Expand Down Expand Up @@ -371,6 +394,7 @@ function LiveDock(p: { onLeft: boolean }) {
[styles.liveDockLeft]: p.onLeft,
})}
style={{ width: liveDockSize + 'px' }}
ref={liveDockRef}
>
<div className={styles.liveDockChevron} onClick={toggleCollapsed}>
<i
Expand Down
Loading
Loading