Skip to content

Commit 6273f36

Browse files
committed
Merge branch 'master' into highlighter/subtitle-rendering-sharp
# Conflicts: # yarn.lock
2 parents 1518b0d + 0c7f1df commit 6273f36

File tree

11 files changed

+208
-79
lines changed

11 files changed

+208
-79
lines changed

Diff for: app/components-react/pages/RecordingHistory.tsx

+14-3
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,21 @@ export function RecordingHistory() {
225225
}));
226226

227227
useEffect(() => {
228+
let isMounted = true;
229+
228230
if (
229231
uploadInfo.error &&
230232
typeof uploadInfo.error === 'string' &&
231-
// We don't want to surface unexpected TS errors to the user
232233
!/TypeError/.test(uploadInfo.error)
233234
) {
234-
postError(uploadInfo.error);
235+
if (isMounted) {
236+
postError(uploadInfo.error);
237+
}
235238
}
239+
240+
return () => {
241+
isMounted = false;
242+
};
236243
}, [uploadInfo.error]);
237244

238245
function openMarkersSettings() {
@@ -293,7 +300,11 @@ export function RecordingHistory() {
293300
<div className={styles.recording} key={recording.timestamp}>
294301
<span style={{ marginRight: '8px' }}>{formattedTimestamp(recording.timestamp)}</span>
295302
<Tooltip title={$t('Show in folder')}>
296-
<span onClick={() => showFile(recording.filename)} className={styles.filename}>
303+
<span
304+
data-test="filename"
305+
onClick={() => showFile(recording.filename)}
306+
className={styles.filename}
307+
>
297308
{recording.filename}
298309
</span>
299310
</Tooltip>

Diff for: app/i18n/en-US/streaming.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -294,5 +294,6 @@
294294
"Failed Checks": "Failed Checks",
295295
"Video Transmission": "Video Transmission",
296296
"Optimized Settings": "Optimized Settings",
297-
"%{platform} Setup Error: Toggling off %{platform} to bypass and go live.": "%{platform} Setup Error: Toggling off %{platform} to bypass and go live."
297+
"%{platform} Setup Error: Toggling off %{platform} to bypass and go live.": "%{platform} Setup Error: Toggling off %{platform} to bypass and go live.",
298+
"Recording Audio Encoder": "Recording Audio Encoder"
298299
}

Diff for: app/services/settings/output/output-settings.ts

+2
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ enum EFileFormat {
4949
mkv = 'mkv',
5050
ts = 'ts',
5151
m3u8 = 'm3u8',
52+
mpegts = 'mpegts',
53+
hls = 'hls',
5254
}
5355

5456
export const QUALITY_ORDER = [

Diff for: app/services/streaming/streaming.ts

-1
Original file line numberDiff line numberDiff line change
@@ -1375,7 +1375,6 @@ export class StreamingService
13751375
});
13761376
this.recordingModeService.actions.addRecordingEntry(parsedFilename);
13771377
this.markersService.actions.exportCsv(parsedFilename);
1378-
this.recordingModeService.addRecordingEntry(parsedFilename);
13791378
this.latestRecordingPath.next(filename);
13801379
// Wrote signals come after Offline, so we return early here
13811380
// to not falsely set our state out of Offline

Diff for: main.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ async function startApp() {
365365
ipcMain.on('getWorkerWindowId', event => {
366366
if (workerWindow.isDestroyed()) {
367367
// prevent potential race-condition issues on app close
368-
// https://github.com/stream-labs/desktop/pull/4239
368+
// https://github.com/streamlabs/desktop/pull/4239
369369
return;
370370
}
371371
event.returnValue = workerWindow.webContents.id;

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@
198198
"serve-handler": "5.0.7",
199199
"shelljs": "0.8.5",
200200
"signtool": "1.0.0",
201-
"sl-vue-tree": "https://github.com/stream-labs/sl-vue-tree.git",
202-
"slap": "git+https://github.com/stream-labs/slap.git#v0.1.83",
201+
"sl-vue-tree": "https://github.com/streamlabs/sl-vue-tree.git",
202+
"slap": "git+https://github.com/streamlabs/slap.git#v0.1.83",
203203
"sockjs": "0.3.20",
204204
"sockjs-client": "1.6.1",
205205
"sortablejs": "1.13.0",

Diff for: test/helpers/modules/settings/settings.ts

+25-16
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
1-
import {
2-
click,
3-
clickButton,
4-
clickCheckbox,
5-
focusChild,
6-
useChildWindow,
7-
useMainWindow,
8-
} from '../core';
1+
import { click, clickButton, focusChild, useChildWindow, useMainWindow } from '../core';
92
import { mkdtemp } from 'fs-extra';
103
import { tmpdir } from 'os';
114
import * as path from 'path';
125
import { setInputValue } from '../forms/base';
13-
import { FormMonkey } from '../../form-monkey';
14-
import { TExecutionContext } from '../../../helpers/webdriver';
6+
import { setFormDropdown } from '../../webdriver/forms';
157

168
/**
179
* Open the settings window with a given category selected
1810
* If callback provided then focus the child window and execute the callback
1911
*/
2012
export async function showSettingsWindow(category: string, cb?: () => Promise<unknown>) {
13+
// not a react hook
14+
// eslint-disable-next-line react-hooks/rules-of-hooks
2115
await useMainWindow(async () => {
2216
await click('.side-nav .icon-settings');
2317

@@ -28,19 +22,34 @@ export async function showSettingsWindow(category: string, cb?: () => Promise<un
2822
});
2923

3024
if (cb) {
25+
// not a react hook
26+
// eslint-disable-next-line react-hooks/rules-of-hooks
3127
await useChildWindow(cb);
3228
}
3329
}
3430

3531
/**
3632
* Set recording path to a temp dir
33+
* @remark Currently, only advanced recording paths should be set
3734
*/
38-
export async function setTemporaryRecordingPath(): Promise<string> {
39-
const tmpDir = await mkdtemp(path.join(tmpdir(), 'slobs-recording-'));
40-
await showSettingsWindow('Output', async () => {
41-
await setInputValue('[data-name="FilePath"] input', tmpDir);
42-
await clickButton('Done');
43-
});
35+
export async function setTemporaryRecordingPath(
36+
advanced: boolean = false,
37+
dir?: string,
38+
): Promise<string> {
39+
const tmpDir = dir ?? (await mkdtemp(path.join(tmpdir(), 'slobs-recording-')));
40+
41+
if (advanced) {
42+
await showSettingsWindow('Output', async () => {
43+
await setFormDropdown('Output Mode', 'Advanced');
44+
await clickButton('Recording');
45+
await setInputValue('[data-name="RecFilePath"] input', tmpDir);
46+
});
47+
} else {
48+
await showSettingsWindow('Output', async () => {
49+
await setInputValue('[data-name="FilePath"] input', tmpDir);
50+
await clickButton('Done');
51+
});
52+
}
4453
return tmpDir;
4554
}
4655

Diff for: test/regular/recording.ts

+103-36
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,48 @@
11
import { readdir } from 'fs-extra';
2-
import { test, useWebdriver } from '../helpers/webdriver';
2+
import { test, TExecutionContext, useWebdriver } from '../helpers/webdriver';
33
import { sleep } from '../helpers/sleep';
44
import { startRecording, stopRecording } from '../helpers/modules/streaming';
5-
import { FormMonkey } from '../helpers/form-monkey';
65
import {
76
setOutputResolution,
87
setTemporaryRecordingPath,
98
showSettingsWindow,
109
} from '../helpers/modules/settings/settings';
11-
import { clickButton, focusMain } from '../helpers/modules/core';
10+
import {
11+
clickButton,
12+
clickWhenDisplayed,
13+
focusMain,
14+
getNumElements,
15+
waitForDisplayed,
16+
} from '../helpers/modules/core';
1217
import { logIn } from '../helpers/webdriver/user';
1318
import { toggleDualOutputMode } from '../helpers/modules/dual-output';
19+
import { setFormDropdown } from '../helpers/webdriver/forms';
20+
import { showPage } from '../helpers/modules/navigation';
1421

22+
// not a react hook
23+
// eslint-disable-next-line react-hooks/rules-of-hooks
1524
useWebdriver();
1625

1726
/**
18-
* Recording with one context active (horizontal)
27+
* Iterate over all formats and record a 0.5s video in each.
28+
* @param t - AVA test context
29+
* @param advanced - whether to use advanced settings
30+
* @returns number of formats
1931
*/
20-
21-
test('Recording', async t => {
22-
const tmpDir = await setTemporaryRecordingPath();
23-
24-
// low resolution reduces CPU usage
25-
await setOutputResolution('100x100');
26-
27-
const formats = ['flv', 'mp4', 'mov', 'mkv', 'ts', 'm3u8'];
32+
async function createRecordingFiles(advanced: boolean = false): Promise<number> {
33+
const formats = advanced
34+
? ['flv', 'mp4', 'mov', 'mkv', 'mpegts', 'hls']
35+
: ['flv', 'mp4', 'mov', 'mkv', 'mpegts'];
2836

2937
// Record 0.5s video in every format
3038
for (const format of formats) {
3139
await showSettingsWindow('Output', async () => {
32-
const form = new FormMonkey(t);
33-
await form.setInputValue(await form.getInputSelectorByTitle('Recording Format'), format);
40+
if (advanced) {
41+
await clickButton('Recording');
42+
}
43+
44+
await setFormDropdown('Recording Format', format);
45+
await sleep(500);
3446
await clickButton('Done');
3547
});
3648

@@ -39,15 +51,85 @@ test('Recording', async t => {
3951
await sleep(500);
4052
await stopRecording();
4153

42-
// Wait to ensure that output setting are editable
54+
// in advanced mode, it may take a little longer to save the recording
55+
if (advanced) {
56+
await sleep(1000);
57+
}
58+
59+
// Confirm notification has been shown and navigate to the recording history
60+
await focusMain();
61+
await clickWhenDisplayed('span=A new Recording has been completed. Click for more info');
62+
await waitForDisplayed('h1=Recordings', { timeout: 1000 });
4363
await sleep(500);
64+
await showPage('Editor');
4465
}
4566

67+
return Promise.resolve(formats.length);
68+
}
69+
70+
/**
71+
* Confirm correct number of files were created and that they are displayed in the recording history.
72+
* @param t - AVA test context
73+
* @param tmpDir - temporary directory where recordings are saved
74+
* @param numFormats - number of formats used to record
75+
*/
76+
async function validateRecordingFiles(
77+
t: TExecutionContext,
78+
tmpDir: string,
79+
numFormats: number,
80+
advanced: boolean = false,
81+
) {
4682
// Check that every file was created
4783
const files = await readdir(tmpDir);
4884

4985
// M3U8 creates multiple TS files in addition to the catalog itself.
50-
t.true(files.length >= formats.length, `Files that were created:\n${files.join('\n')}`);
86+
// The additional TS files created by M3U8 in advanced mode are not displayed in the recording history
87+
const numFiles = advanced ? files.length - 1 : files.length;
88+
89+
t.true(numFiles >= numFormats, `Files that were created:\n${files.join('\n')}`);
90+
91+
// Check that the recordings are displayed in the recording history
92+
await showPage('Recordings');
93+
waitForDisplayed('h1=Recordings');
94+
95+
const numRecordings = await getNumElements('[data-test=filename]');
96+
t.is(numRecordings, numFiles, 'All recordings show in history matches number of files recorded');
97+
}
98+
99+
/**
100+
* Recording with one context active (horizontal, simple)
101+
*/
102+
test('Recording', async t => {
103+
// low resolution reduces CPU usage
104+
await setOutputResolution('100x100');
105+
106+
// Simple Recording
107+
const tmpDir = await setTemporaryRecordingPath();
108+
const numSimpleFormats = await createRecordingFiles();
109+
await validateRecordingFiles(t, tmpDir, numSimpleFormats);
110+
111+
// Advanced Recording
112+
await setTemporaryRecordingPath(true, tmpDir);
113+
const numAdvancedFormats = await createRecordingFiles(true);
114+
await validateRecordingFiles(t, tmpDir, numSimpleFormats + numAdvancedFormats, true);
115+
116+
// Switches between Advanced and Simple Recording
117+
// Note: The recording path for Simple Recording should have persisted from before
118+
await sleep(2000);
119+
await showSettingsWindow('Output', async () => {
120+
await setFormDropdown('Output Mode', 'Simple');
121+
await clickButton('Done');
122+
});
123+
124+
await focusMain();
125+
await startRecording();
126+
// Record for 2s to prevent the recording from accidentally having the same key
127+
await sleep(2000);
128+
await stopRecording();
129+
await clickWhenDisplayed('span=A new Recording has been completed. Click for more info');
130+
await validateRecordingFiles(t, tmpDir, numSimpleFormats + numAdvancedFormats + 1, true);
131+
132+
t.pass();
51133
});
52134

53135
/**
@@ -57,26 +139,11 @@ test('Recording', async t => {
57139
test('Recording with two contexts active', async t => {
58140
await logIn(t);
59141
await toggleDualOutputMode();
60-
const tmpDir = await setTemporaryRecordingPath();
142+
61143
// low resolution reduces CPU usage
62144
await setOutputResolution('100x100');
63-
const formats = ['flv', 'mp4', 'mov', 'mkv', 'ts', 'm3u8'];
64-
// Record 0.5s video in every format
65-
for (const format of formats) {
66-
await showSettingsWindow('Output', async () => {
67-
const form = new FormMonkey(t);
68-
await form.setInputValue(await form.getInputSelectorByTitle('Recording Format'), format);
69-
await clickButton('Done');
70-
});
71-
await focusMain();
72-
await startRecording();
73-
await sleep(1000);
74-
await stopRecording();
75-
// Wait to ensure that output setting are editable
76-
await sleep(1000);
77-
}
78-
// Check that every file was created
79-
const files = await readdir(tmpDir);
80-
// M3U8 creates multiple TS files in addition to the catalog itself.
81-
t.true(files.length >= formats.length, `Files that were created:\n${files.join('\n')}`);
145+
const tmpDir = await setTemporaryRecordingPath(true);
146+
147+
const numFiles = await createRecordingFiles(true);
148+
await validateRecordingFiles(t, tmpDir, numFiles, true);
82149
});

0 commit comments

Comments
 (0)