Skip to content
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

Dual output recording #4794

Open
wants to merge 47 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
3ce1a99
Vertical display recording in dual output mode with fixed settings.
michelinewu Oct 19, 2023
99b8c61
Move recording to new API.
michelinewu Nov 2, 2023
69e1f88
WIP: map simple recording.
michelinewu Nov 2, 2023
ad25131
Add checkbox for vertical recording.
michelinewu Nov 2, 2023
69d7d42
Migrate simple recording to new API.
michelinewu Nov 2, 2023
111a195
Fixed simple recording signals. Advanced recording WIP.
michelinewu Nov 2, 2023
968cb32
Refactor recording signal handling.
michelinewu Nov 3, 2023
bcaefea
Mapped output settings to advanced recording.
michelinewu Nov 3, 2023
edf3ae5
Refactor create recording.
michelinewu Nov 3, 2023
84d2831
WIP: Recordings show in recording history.
michelinewu Nov 7, 2023
23d5307
WIP: Recording history shows both recordings.
michelinewu Nov 7, 2023
3acaa29
Fix show recordings in recording history from cherry pick commit auto…
michelinewu Nov 16, 2023
4ba6f6c
Add vertical recording toggle to go live window.
michelinewu Nov 7, 2023
8f374e6
Allow record vertical as valid dual output destination.
michelinewu Nov 7, 2023
00ea81f
Stream horizontal and record vertical.
michelinewu Nov 8, 2023
ebaabed
Show both recordings in the recording history.
michelinewu Nov 8, 2023
c9cebfb
Fix recording file names missing in recording history.
michelinewu Nov 17, 2023
896c3bd
WIP: Recording.
michelinewu Nov 14, 2023
1cd98c5
Streaming and recording horizontal display while recording vertical l…
michelinewu Nov 15, 2023
a9bbb47
Fixed dual output stream and record logic.
michelinewu Nov 15, 2023
600bed3
WIP: hide recording button when dual output dual stream.
michelinewu Nov 15, 2023
32942f9
Fix close go live window after start stream in dual output mode.
michelinewu Nov 15, 2023
1e47b32
Finesse icons, buttons, and confirmation message for vertical recording.
michelinewu Nov 15, 2023
738d690
WIP: Recording dual output mode test.
michelinewu Nov 15, 2023
cb0ef5c
Recording tests passing.
michelinewu Nov 16, 2023
c21cb02
Fix null error in tests.
michelinewu Nov 17, 2023
db9053e
Fixed call on potentially null object.
michelinewu Nov 17, 2023
ac090fb
Fix selective recording test.
michelinewu Nov 17, 2023
816bb2c
WIP: recording tests.
michelinewu Nov 17, 2023
8b36c78
WIP
michelinewu Nov 17, 2023
3e1ef2f
Merge branch 'master' into mw_do_recording_mvp_2
michelinewu Nov 27, 2023
e0a792f
WIP
michelinewu Nov 27, 2023
19c70aa
Merge branch 'master' into mw_do_recording_mvp_2
michelinewu Nov 29, 2023
b5a1736
Always show recording button and add error for attempting to stream p…
michelinewu Nov 29, 2023
777fa64
Fix recording via api test.
michelinewu Nov 29, 2023
49d6dcf
Fix for strictnulls test.
michelinewu Nov 29, 2023
d242a9a
WIP: Dual output recording tests.
michelinewu Nov 29, 2023
564fde2
Fix for reactivity for record vertical toggle.
michelinewu Nov 29, 2023
d43c660
Split dual output recording tests.
michelinewu Dec 1, 2023
a0e9f8b
WIP: Recording tests.
michelinewu Dec 1, 2023
075d351
Proposed workaround for same settings as stream crash in dual output …
michelinewu Dec 1, 2023
be3654e
Show single stream chat when vertical is second destination.
michelinewu Dec 1, 2023
3e97c93
Single output mode uses old API, dual output mode uses new API.
michelinewu Dec 4, 2023
2fd1b8e
Recording tests. WIP: Streaming and Recording Dual Output.
michelinewu Dec 5, 2023
870e517
Fix missing data name in stream options.
michelinewu Dec 5, 2023
65f40f7
Skip test.
michelinewu Dec 5, 2023
7dbb848
Code review refactors and fixed recording test.
michelinewu Dec 5, 2023
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
5 changes: 4 additions & 1 deletion app/components-react/pages/RecordingHistory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,10 @@ export function RecordingHistory() {
<div className={styles.recording} key={recording.timestamp}>
<span style={{ marginRight: '8px' }}>{formattedTimestamp(recording.timestamp)}</span>
<Tooltip title={$t('Show in folder')}>
<span onClick={() => showFile(recording.filename)} className={styles.filename}>
<span
onClick={() => showFile(recording.filename)}
className={cx(styles.filename, 'file')}
>
{recording.filename}
</span>
</Tooltip>
Expand Down
3 changes: 2 additions & 1 deletion app/components-react/root/StartStreamingButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export default function StartStreamingButton(p: { disabled?: boolean }) {
} = Services;

const { streamingStatus, delayEnabled, delaySeconds } = useVuex(() => ({
streamingStatus: StreamingService.state.streamingStatus,
streamingStatus:
StreamingService.state.streamingStatus || StreamingService.state.verticalStreamingStatus,
delayEnabled: StreamingService.views.delayEnabled,
delaySeconds: StreamingService.views.delaySeconds,
}));
Expand Down
15 changes: 15 additions & 0 deletions app/components-react/root/StudioEditor.m.less
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,21 @@
text-decoration: none;
}
}

.stream-icon {
margin-right: 0px;
color: var(--icon-active);
}

.record-icon {
color: var(--red);
font-size: 18px;
margin-top: 3px;
}

.hidden {
opacity: 0;
}
}

.progress-bar {
Expand Down
71 changes: 64 additions & 7 deletions app/components-react/root/StudioEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,30 +347,58 @@ function StudioModeControls(p: { stacked: boolean }) {
}

function DualOutputControls(p: { stacked: boolean }) {
const { SettingsService, DualOutputService, StreamingService } = Services;
function openSettingsWindow() {
Services.SettingsService.actions.showSettings('Video');
SettingsService.actions.showSettings('Video');
}
const showHorizontal = Services.DualOutputService.views.showHorizontalDisplay;
const showVertical =
Services.DualOutputService.views.showVerticalDisplay &&
!Services.StreamingService.state.selectiveRecording;

const v = useVuex(() => ({
showHorizontal: DualOutputService.views.showHorizontalDisplay,
showVertical:
DualOutputService.views.showVerticalDisplay && !StreamingService.state.selectiveRecording,
isHorizontalRecording: StreamingService.views.isHorizontalRecording,
isVerticalRecording: StreamingService.views.isVerticalRecording,
isHorizontalStreaming: StreamingService.views.isStreaming,
isVerticalStreaming: StreamingService.views.isVerticalStreaming,
}));

const horizontalIconVisible = v.isHorizontalStreaming || v.isHorizontalRecording;
const verticalIconVisible = v.isVerticalStreaming || v.isVerticalRecording;

/**
* Note for the streaming and recording icons:
* To maintain the horizontal and vertical header icon and text positioning centered,
* conditionally change the opacity of the streaming and recording icons.
* For the horizontal recording, to maintain the same margin of the streaming and recording icons
* swap the icons shown conditionally so that when only recording, the recording icon shows next to
* the header text.
*/
return (
<div
id="dual-output-header"
className={cx(styles.dualOutputHeader, { [styles.stacked]: p.stacked })}
>
{showHorizontal && (
{v.showHorizontal && (
<div className={styles.horizontalHeader}>
<i className="icon-desktop" />
<span>{$t('Horizontal Output')}</span>
<DualOutputIcons
className={cx('icon-horizontal', { visible: horizontalIconVisible })}
showStream={v.isHorizontalStreaming}
showRecord={v.isHorizontalRecording}
/>
</div>
)}

{showVertical && (
{v.showVertical && (
<div className={styles.verticalHeader}>
<i className="icon-phone-case" />
<span>{$t('Vertical Output')}</span>
<DualOutputIcons
className={cx('icon-vertical', { visible: verticalIconVisible })}
showStream={v.isVerticalStreaming}
showRecord={v.isVerticalRecording}
/>
</div>
)}
<div className={styles.manageLink}>
Expand All @@ -380,6 +408,35 @@ function DualOutputControls(p: { stacked: boolean }) {
);
}

function DualOutputIcons(p: { className: string; showStream: boolean; showRecord: boolean }) {
return (
<>
{!p.showStream && p.showRecord ? (
<i className={cx(p.className, styles.recordIcon, 'icon-record')} />
) : (
<>
<i
className={cx(
p.className,
styles.streamIcon,
{ [styles.hidden]: !p.showStream },
'icon-studio',
)}
/>
<i
className={cx(
p.className,
styles.recordIcon,
{ [styles.hidden]: !p.showRecord },
'icon-record',
)}
/>
</>
)}
</>
);
}

function DualOutputProgressBar(p: { sceneId: string }) {
const { DualOutputService, ScenesService } = Services;

Expand Down
16 changes: 6 additions & 10 deletions app/components-react/root/StudioFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,11 @@ export default function StudioFooterComponent() {

function RecordingButton() {
const { StreamingService } = Services;
const { isRecording, recordingStatus } = useVuex(() => ({
const { isRecording, recordingStopping } = useVuex(() => ({
isRecording: StreamingService.views.isRecording,
recordingStatus: StreamingService.state.recordingStatus,
recordingStopping:
StreamingService.state.recordingStatus === ERecordingState.Stopping ||
StreamingService.state.verticalRecordingStatus === ERecordingState.Stopping,
}));

function toggleRecording() {
Expand All @@ -202,13 +204,7 @@ function RecordingButton() {
className={cx(styles.recordButton, 'record-button', { active: isRecording })}
onClick={toggleRecording}
>
<span>
{recordingStatus === ERecordingState.Stopping ? (
<i className="fa fa-spinner fa-pulse" />
) : (
<>REC</>
)}
</span>
<span>{recordingStopping ? <i className="fa fa-spinner fa-pulse" /> : <>REC</>}</span>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Should REC be translated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how it was originally but you bring up a good point. The limitation is that there is only room for three characters on the button. Let's bring it up to the team?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea the character limit is the issue here, as well as i'm not certain other languages have the same shorthand that we do for this term. i think looking into an icon replacement could have value here, as there is a generally accepted icon for recording

</button>
</Tooltip>
</div>
Expand All @@ -221,7 +217,7 @@ function RecordingTimer() {
const [recordingTime, setRecordingTime] = useState('');

const { isRecording } = useVuex(() => ({
isRecording: StreamingService.views.isRecording,
isRecording: StreamingService.views.isRecording || StreamingService.views.isVerticalRecording,
}));

useEffect(() => {
Expand Down
68 changes: 68 additions & 0 deletions app/components-react/windows/go-live/StreamOptions.m.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
@import '../../../styles/index.less';
@import './GoLive.m.less';

.stream-options {
display: flex;
flex-direction: column;
align-self: flex-end;
width: 100%;
}

.settings-row {
width: 100%;
margin: 0px !important;
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
align-self: flex-end;

:global(.ant-col) {
padding: 0px;
}
}

.switcher-label {
flex-grow: 1;
}

.recording-switcher {
justify-self: flex-end;
color: var(--background);

:global(.ant-form-item-label) {
display: none;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not worth requiring a change here but just a lil useful thing-- we have a boolean property nowrap on all of our react inputs that gets rid of the label portion


:global(.ant-form-item-control-input) {
min-height: unset;
}

:global(.ant-switch-small) {
min-width: 36px;
height: 21px;
line-height: 21px;
}

:global(.ant-switch-small .ant-switch-handle) {
width: 15px;
height: 15px;
top: 3px;
left: 3px;
}

:global(.ant-switch-small.ant-switch-checked .ant-switch-handle) {
left: calc(100% - 15px - 3px) !important;
}

:global(.ant-switch-inner i) {
color: var(--background);
display: flex;
align-self: center;
padding-right: 2px;
}

&:global(.ant-row.ant-form-item) {
margin-bottom: 0px !important;
}
}
83 changes: 83 additions & 0 deletions app/components-react/windows/go-live/StreamOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from 'react';
import styles from './StreamOptions.m.less';
import { Services } from 'components-react/service-provider';
import { $t } from 'services/i18n';
import { Row, Select } from 'antd';
import { SwitchInput } from 'components-react/shared/inputs';
import { useVuex } from 'components-react/hooks';
import { ERecordingQuality } from 'obs-studio-node';

/**
* Renders options for the stream
* - Vertical Recording Settings
**/
export default function StreamOptions() {
const { DualOutputService } = Services;

const { Option } = Select;

const recordingQualities = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these should be translated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
quality: ERecordingQuality.HighQuality,
name: $t('High, Medium File Size'),
},
{
quality: ERecordingQuality.HigherQuality,
name: $t('Indistinguishable, Large File Size'),
},
{
quality: ERecordingQuality.Lossless,
name: $t('Lossless, Tremendously Large File Size'),
},
];

const v = useVuex(() => ({
recordVertical: DualOutputService.views.recordVertical,
setRecordVertical: DualOutputService.actions.return.setRecordVertical,
recordingQuality: DualOutputService.views.recordingQuality,
setRecordingQuality: DualOutputService.actions.setDualOutputRecordingQuality,
}));

return (
<div className={styles.streamOptions}>
<Row
gutter={16}
className={styles.settingsRow}
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
paddingBottom: '0px',
}}
>
<div className={styles.switcherLabel} style={{ marginBottom: '2px' }}>
{$t('Recording Quality')}
</div>
<Select
data-name="recording-quality"
defaultValue={v.recordingQuality.dualOutput}
style={{ flex: 1, width: '100%' }}
onChange={option => v.setRecordingQuality('dual', option)}
value={v.recordingQuality.dualOutput}
>
{recordingQualities.map(option => (
<Option key={option.quality} value={option.quality}>
{option.name}
</Option>
))}
</Select>
</Row>
<Row gutter={16} className={styles.settingsRow}>
<div className={styles.switcherLabel}>{$t('Vertical Recording Only')}</div>
<SwitchInput
value={v.recordVertical}
name="record-vertical"
onChange={v.setRecordVertical}
uncontrolled
className={styles.recordingSwitcher}
checkedChildren={<i className="icon-check-mark" />}
/>
</Row>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
.left-column {
height: 100%;
padding: 0px !important;
display: flex !important;
flex-direction: column;
justify-content: space-between;

&:global(.ant-col) {
padding: 10px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import Spinner from 'components-react/shared/Spinner';
import GoLiveError from '../GoLiveError';
import UserSettingsUltra from './UserSettingsUltra';
import UserSettingsNonUltra from './UserSettingsNonUltra';
import StreamOptions from '../StreamOptions';

/**
* Renders settings for starting the stream
Expand Down Expand Up @@ -42,10 +43,11 @@ export default function DualOutputGoLiveSettings() {
<Row gutter={16} className={styles.settingsRow}>
{/*LEFT COLUMN*/}
<Col span={8} className={styles.leftColumn}>
<Scrollable style={{ height: '100%' }}>
<Scrollable style={{ minHeight: '82%', flexGrow: 1 }}>
{isPrime && <UserSettingsUltra />}
{!isPrime && <UserSettingsNonUltra />}
</Scrollable>
<StreamOptions />
</Col>

{/*RIGHT COLUMN*/}
Expand Down
Loading
Loading