Skip to content
This repository was archived by the owner on Apr 18, 2024. It is now read-only.

Commit ca3586f

Browse files
hlomzikbmartelGondragos
authored
fix: LSDV-3012: Syncable with new simple approach (#1201)
New Syncable mixin to provide new and very simple and nice approach: - don't send event back - sync the whole state (time, speed, playing) - group tags into syncing groups - lock current sync window for any back-stream events - sync is pluggable thing, tag should work without sync methods (Audio is still tied with Sync) All tags are muted except Audio tags or the tag triggered even if no Audio is presented. That's because Audio is the only tag with controllable volume. May be changed in the future. Things done: - sync speed - sync play when audio restarted after reaching the end - sync time shortly after play? can be helpful for overloaded browser * fix: LSDV-3012: Syncable with new simple approach First try with some debugging and thoughts. New Syncable mixin to provide new and very simple and nice approach: - don't send event back - sync the whole state (time, speed, playing) - group tags in syncing groups - lock current frame for any back-stream events * Fix annoying react error about missing key * Fix types in SyncableMixin * adding some of the sync tests, fixing audio pause time sync * make sync optional, otherwise it breaks everything else * remove debug wait * adding test for synced looping audio * Sync Video; remove excess sync deps; use sync window - Implement Syncable for Video as well - Remove some dependencies on sync methods from usual flow - Add sync window to suppress all excess events in it - Sync states, not events * Do nothing if tag is not synced * Sync Paragraphs - rename model and view to be more consistent - use Syncable, implement just a few methods, required minimum - remove bunch of old code with sync misuse - Paragrpahs mostly sync other tags, and on external actions it only pauses * Fix time displaying during synced seek * Clean up; comments; data is main parameter * update test usage of helper dragAudioElement * fixing audio paragraphs test * not sure why this file was suddenly uppercase, putting back to lower * use a local file for test * allow alternative chromium to be used, otherwise the default install from playwright contains no media codecs to support mp4 files * put back the correct defaults for examples * use video file for example * use video for file ref in audio,video,paragraphs example config data * Fix backward compatibility for video<->audio sync Old way of referring each other names works! * Sync speed as well * fix video and audio references in sync tests, ensure video is not leaving leftover video elements * use only audio v3 for all tests and development * fixing audio config tests * fix paragraph syncing of playback, and end time handling along with test fixes * Mute other tags on play. And unmute the current one. Paragraphs tag doesn't react on play, so should not be muted. And when it's the origin of event it's not muted and others are. So no handlers there. * Fix muted for Video * Fix Paragraphs sync handling React on seek if the time within the current phrase. Also helps with late back-sync from video tag. * Fix renamed test helper dragAudioElement * Fix Audio -> Video sync misteriously broken * fix DomException when looping playback which also caused tests to fail * ensure fields are cleared before input is sent for audio controls * playback speed needs to visually update * Fix tests for Audio v3 * Record video of weird readonly test failure * Fix readonly audio regions test Behaviour was changed, so let it check the new one. Also fix for hotkeys enabled for tests. * Raise tests timeout * Ignore errors temporarily * Return back usual codecept values * Fix sync not triggerring when speed is 1 Because of early static handlers on waveform, rate was fixed at 1 in it. * Fix speed input change in test * Add threshold to time comparisons * Ignore DOMException, it won't affect UX This error should've been fixed, but stayed in some cases :( * Move synced time assert to fragment; fix test * Fix case for default overrides in flags.json * Trigger play only after it actually started to play This fixes problems with sync of region looped playing * Actually, DEV is correct one for old Audio FF * Fix sync for Audio v1/v2 * Clean up Paragraphs code, simplify and add comments * Add tests for Paragraphs mst model methods * Fix audio when synced tag jumped to the beginning * Fix muted state Only Audio has volume controls, so they should not be muted, while other synced tags should be muted, otherwise volume can't be controlled. But if there are no Audio tags in group, the tag triggered sync should be the main tag with volume active, and others should be muted. * Fix Paragraphs UX: play/pause synced and natural If you press pause and then play it will continue playing current region, even if that was triggered from other tag. Seek is synced inside current region. * Fix Paragraphs with overlapping times * Add feature flag All methods are groupped even in old code, so these groups are extracted to separate small models and composed conditionally by ff value. * Add more comments about setMuted behavior * Fix wrong flags combination * Use new FF in e2e tests * Move assertTimesInSync to Assertion helper * Fix unit test with some weird FF mock --------- Co-authored-by: Brandon Martel <brandonmartel@gmail.com> Co-authored-by: Sergey <sergey.koshevarov@heartex.com>
1 parent 284f27e commit ca3586f

File tree

41 files changed

+1800
-244
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1800
-244
lines changed

e2e/codecept.conf.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ module.exports.config = {
2626
windowSize: '1200x900',
2727
waitForNavigation: 'networkidle',
2828
browser: 'chromium',
29+
chromium: process.env.CHROMIUM_EXECUTABLE_PATH ? {
30+
executablePath: process.env.CHROMIUM_EXECUTABLE_PATH,
31+
} : {},
2932
trace: false,
3033
keepTraceForPassedTests: false,
3134
},
@@ -96,6 +99,12 @@ module.exports.config = {
9699
enabled: true,
97100
uncaughtErrorFilter: {
98101
interrupt: true,
102+
ignore: [
103+
/^ResizeObserver loop limit exceeded$/,
104+
// @todo: solve the problems below
105+
/^TypeError: Cannot read properties of null \(reading 'getBoundingClientRect'\)/,
106+
/The play\(\) request was interrupted/,
107+
],
99108
},
100109
consoleErrorFilter: {
101110
// @todo switch it on to feel the pain

e2e/fragments/AtAudioView.js

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,20 @@ module.exports = {
3535
async waitForAudio() {
3636
await I.executeScript(Helpers.waitForAudio);
3737
I.waitForInvisible(this._progressBarSelector);
38+
I.waitForDetached('loading-progress-bar', 10);
3839
},
39-
40-
getCurrentAudioTime() {
41-
return I.executeScript(Helpers.getCurrentAudioTime);
40+
getCurrentAudio() {
41+
return I.executeScript(Helpers.getCurrentMedia, 'audio');
4242
},
4343
/**
4444
* Mousedown - mousemove - mouseup drawing on the AudioView. Works in couple of lookForStage.
4545
* @example
4646
* await AtAudioView.lookForStage();
47-
* AtAudioView.dragAudioRegion(50, 200);
47+
* AtAudioView.dragAudioElement(50, 200);
4848
* @param x {number}
4949
* @param shiftX {number}
5050
*/
51-
dragAudioRegion(x, shiftX, shouldRelease = true) {
51+
dragAudioElement(x, shiftX, shouldRelease = true) {
5252
I.scrollPageToTop();
5353
I.moveMouse(this._stageBbox.x + x, this._stageBbox.y + this._stageBbox.height / 2);
5454
I.pressMouseDown();
@@ -73,6 +73,17 @@ module.exports = {
7373
I.wait(1); // We gotta wait here because clicks on the canvas are not processed immediately
7474
},
7575

76+
clickAtBeginning() {
77+
this.clickAt(0);
78+
},
79+
80+
clickAtEnd() {
81+
// Clicking on the end of the canvas doesn't quite work, so we click a bit before the end
82+
// to make sure we're it is not clicking outside the canvas, and move the cursor over.
83+
this.clickAt(this._stageBbox.width - 1);
84+
this.dragAudioElement(this._stageBbox.width - 1, 1);
85+
},
86+
7687
async createRegion(tagName, start, length) {
7788
const { x, y, height } = await this.getWrapperPosition(tagName);
7889

@@ -87,7 +98,9 @@ module.exports = {
8798

8899
async getWrapperPosition(tagName) {
89100
const wrapperPosition = await I.executeScript((tagName) => {
90-
const wrapper = Htx.annotationStore.selected.names.get(tagName)._ws.container;
101+
const _ws = Htx.annotationStore.selected.names.get(tagName)._ws;
102+
// `visualizer.wrapper` is for Audio v3
103+
const wrapper = _ws.visualizer?.wrapper ?? _ws.container;
91104
const bbox = wrapper.getBoundingClientRect();
92105

93106
return {
@@ -119,6 +132,31 @@ module.exports = {
119132
});
120133
},
121134

135+
async moveRegionV3(regionId, offset = 30) {
136+
const regionPosition = await I.executeScript(({ regionId, stageBbox }) => {
137+
const region = Htx.annotationStore.selected.regions.find(r => r.cleanId === regionId);
138+
139+
const wsRegion = region._ws_region;
140+
141+
if (!wsRegion.inViewport) {
142+
return null;
143+
}
144+
const { height } = wsRegion.visualizer;
145+
146+
return {
147+
x: (wsRegion.xStart + wsRegion.xEnd) / 2 + stageBbox.x,
148+
y: height / 2 + stageBbox.y,
149+
};
150+
}, { regionId, stageBbox: this._stageBbox });
151+
152+
if (!regionPosition) return;
153+
154+
return I.dragAndDropMouse(regionPosition, {
155+
x: regionPosition.x + offset,
156+
y: regionPosition.y,
157+
});
158+
},
159+
122160
/**
123161
* Toggle the audio control menu. This houses the volume slider and mute button.
124162
*/
@@ -193,6 +231,7 @@ module.exports = {
193231
*/
194232
setVolumeInput(value) {
195233
this.toggleControlsMenu();
234+
I.clearField(this._volumeInputSelector);
196235
I.fillField(this._volumeInputSelector, value);
197236
this.toggleControlsMenu();
198237
},
@@ -210,6 +249,7 @@ module.exports = {
210249

211250
I.seeInField(this._playbackSpeedInputSelector, value);
212251
I.seeInField(this._playbackSpeedSliderSelector, value);
252+
213253
const playbackSpeed = await I.grabAttributeFrom(this._audioElementSelector, 'playbackRate');
214254

215255
assert.equal(playbackSpeed, value, 'Playback speed doesn\'t match in audio element');
@@ -226,6 +266,9 @@ module.exports = {
226266
*/
227267
setPlaybackSpeedInput(value) {
228268
this.toggleSettingsMenu();
269+
// it was not easy to set this field, so we have to carefully remove value and put it
270+
I.doubleClick(locate(this._playbackSpeedInputSelector));
271+
I.pressKey('Backspace');
229272
I.fillField(this._playbackSpeedInputSelector, value);
230273
this.toggleSettingsMenu();
231274
},
@@ -256,6 +299,7 @@ module.exports = {
256299
*/
257300
setAmplitudeInput(value) {
258301
this.toggleSettingsMenu();
302+
I.clearField(this._amplitudeInputSelector);
259303
I.fillField(this._amplitudeInputSelector, value);
260304
this.toggleSettingsMenu();
261305
},

e2e/fragments/AtVideoView.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
const { I } = inject();
22

3+
const Helpers = require('../tests/helpers');
4+
35
/**
46
* @typedef BoundingClientRect
57
* @property {number} x
@@ -16,6 +18,11 @@ module.exports = {
1618
_positionSelector: '.lsf-seeker__position',
1719
_seekStepForwardSelector: '.lsf-timeline-controls__main-controls > div:nth-child(2) > button:nth-child(4)',
1820
_seekStepBackwardSelector: '.lsf-timeline-controls__main-controls > div:nth-child(2) > button:nth-child(2)',
21+
_playButtonSelector: '.lsf-timeline-controls__main-controls > .lsf-timeline-controls__group:nth-child(2) > button:nth-child(2)',
22+
23+
locateRootSelector() {
24+
return locate(this._rootSelector);
25+
},
1926

2027
locateVideoContainer() {
2128
return locate(this._videoRootSelector);
@@ -25,6 +32,22 @@ module.exports = {
2532
return locator ? locate(locator).inside(this.locateVideoContainer()) : this.locateVideoContainer();
2633
},
2734

35+
seekStepForwardSelector() {
36+
return locate(this._seekStepForwardSelector).inside(this.locateRootSelector());
37+
},
38+
39+
seekStepBackwardSelector() {
40+
return locate(this._seekStepBackwardSelector).inside(this.locateRootSelector());
41+
},
42+
43+
playButtonSelector() {
44+
return locate(this._playButtonSelector).inside(this.locateRootSelector());
45+
},
46+
47+
getCurrentVideo() {
48+
return I.executeScript(Helpers.getCurrentMedia, 'video');
49+
},
50+
2851
/**
2952
* Grab the bounding rect of the video track
3053
* @returns {Promise<BoundingClientRect>}
@@ -70,7 +93,7 @@ module.exports = {
7093
*/
7194
async clickSeekStepForward(steps = 1) {
7295
for (let i = 0; i < steps; i++) {
73-
I.click(this._seekStepForwardSelector);
96+
I.click(this.seekStepForwardSelector());
7497
}
7598
},
7699

@@ -79,9 +102,25 @@ module.exports = {
79102
* @param {number} steps
80103
* @returns {Promise<void>}
81104
*/
82-
async clickSeekStepBackward(steps = 1) {
105+
async clickSeekStepBackward(steps = 2) {
83106
for (let i = 0; i < steps; i++) {
84-
I.click(this._seekStepBackwardSelector);
107+
I.click(this.seekStepBackwardSelector());
85108
}
86109
},
110+
111+
/**
112+
* Click the video controls play button.
113+
* @returns {Promise<void>}
114+
*/
115+
async clickPlayButton() {
116+
I.click(this.playButtonSelector());
117+
},
118+
119+
/**
120+
* Click the video controls pause button.
121+
* @returns {Promise<void>}
122+
*/
123+
async clickPauseButton() {
124+
I.click(this.playButtonSelector());
125+
},
87126
};

e2e/helpers/Assertion.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ const Helpers = require('../tests/helpers');
22
const assert = require('assert');
33
const Helper = require('@codeceptjs/helper');
44

5+
// used in Audio/Video/Paragraphs sync
6+
const TIME_DIFF_THRESHOLD = 0.3;
7+
58
class AssertionHelper extends Helper {
69
assertDeepEqualWithTolerance(actual, expected, fractionDigits = 2, message) {
710
assert.deepStrictEqual(
@@ -17,6 +20,15 @@ class AssertionHelper extends Helper {
1720
message,
1821
);
1922
}
23+
/**
24+
* Asserts that two times are equal after sync (with some possible threshold)
25+
* @param {number} time1
26+
* @param {number} time2
27+
* @param {string} [message] for assertion
28+
*/
29+
assertTimesInSync(time1, time2, message = '') {
30+
assert.equal(Math.abs(time1 - time2) < TIME_DIFF_THRESHOLD, true, message);
31+
}
2032
}
2133

2234
module.exports = AssertionHelper;

e2e/setup/feature-flags.js

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
/*
2-
List of feature flags that should be switched on during every testing scenario
3-
*/
4-
5-
module.exports = {
6-
fflag_feat_front_lsdv_4659_skipduplicates_060323_short: true,
7-
};
1+
/*
2+
List of feature flags that should be switched on during every testing scenario
3+
*/
4+
5+
module.exports = {
6+
fflag_feat_front_lsdv_4659_skipduplicates_060323_short: true,
7+
fflag_fix_font_lsdv_1148_hotkeys_namespaces_01022023_short: true,
8+
};

e2e/tests/audio/audio-controls.test.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,6 @@ Scenario('Check the audio controls work', async function({ I, LabelStudio, AtAud
6767
LabelStudio.init(params);
6868

6969
await AtAudioView.waitForAudio();
70-
71-
I.waitForDetached('loading-progress-bar', 10);
72-
7370
await AtAudioView.lookForStage();
7471

7572
AtSidebar.seeRegions(1);

e2e/tests/audio/audio-regions.test.js

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,13 @@ Scenario('Check if regions are selected', async function({ I, LabelStudio, AtAud
8282
LabelStudio.init(params);
8383

8484
await AtAudioView.waitForAudio();
85-
86-
I.waitForDetached('loading-progress-bar', 10);
87-
8885
await AtAudioView.lookForStage();
8986

9087
AtSidebar.seeRegions(1);
9188

9289
// creating a new region
9390
I.pressKey('1');
94-
AtAudioView.dragAudioRegion(160,80);
91+
AtAudioView.dragAudioElement(160,80);
9592
I.pressKey('u');
9693

9794
AtSidebar.seeRegions(2);
@@ -100,7 +97,7 @@ Scenario('Check if regions are selected', async function({ I, LabelStudio, AtAud
10097
AtSidebar.seeSelectedRegion();
10198
AtAudioView.clickAt(170);
10299
AtSidebar.dontSeeSelectedRegion();
103-
AtAudioView.dragAudioRegion(170,40);
100+
AtAudioView.dragAudioElement(170,40);
104101
AtSidebar.seeSelectedRegion();
105102
AtAudioView.clickAt(220);
106103
AtSidebar.dontSeeSelectedRegion();
@@ -115,15 +112,12 @@ Scenario('Check if multiple regions are working changing labels', async function
115112
LabelStudio.init(paramsSpeech);
116113

117114
await AtAudioView.waitForAudio();
118-
119-
I.waitForDetached('loading-progress-bar', 10);
120-
121115
await AtAudioView.lookForStage();
122116

123117
for (let i = 0; i < 20; i++) {
124118
// creating a new region
125119
I.pressKey('1');
126-
AtAudioView.dragAudioRegion((40 * i) + 10,30);
120+
AtAudioView.dragAudioElement((40 * i) + 10,30);
127121
AtAudioView.clickAt((40 * i) + 20);
128122
I.pressKey('2');
129123
I.pressKey('1');
@@ -155,21 +149,18 @@ Scenario('Can select a region below a hidden region', async function({ I, LabelS
155149
LabelStudio.init(paramsSpeech);
156150

157151
await AtAudioView.waitForAudio();
158-
159-
I.waitForDetached('loading-progress-bar', 10);
160-
161152
await AtAudioView.lookForStage();
162153

163154
// create a new region
164155
I.pressKey('1');
165-
AtAudioView.dragAudioRegion(50, 80);
156+
AtAudioView.dragAudioElement(50, 80);
166157
I.pressKey('u');
167158

168159
AtSidebar.seeRegions(1);
169160

170161
// create a new region above the first one
171162
I.pressKey('2');
172-
AtAudioView.dragAudioRegion(49, 81);
163+
AtAudioView.dragAudioElement(49, 81);
173164
I.pressKey('u');
174165

175166
AtSidebar.seeRegions(2);
@@ -195,15 +186,12 @@ Scenario('Delete region by pressing delete hotkey', async function({ I, LabelStu
195186
LabelStudio.init(params);
196187

197188
await AtAudioView.waitForAudio();
198-
199-
I.waitForDetached('loading-progress-bar', 10);
200-
201189
await AtAudioView.lookForStage();
202190

203191
AtSidebar.seeRegions(1);
204192

205193
// creating a new region
206-
AtAudioView.dragAudioRegion(160,80);
194+
AtAudioView.dragAudioElement(160,80);
207195

208196
I.pressKey('Delete');
209197

@@ -221,20 +209,17 @@ Scenario('Check if there are ghost regions', async function({ I, LabelStudio, At
221209
LabelStudio.init(paramsSpeech);
222210

223211
await AtAudioView.waitForAudio();
224-
225-
I.waitForDetached('loading-progress-bar', 10);
226-
227212
await AtAudioView.lookForStage();
228213

229214
// creating a new region
230215
I.pressKey('1');
231-
AtAudioView.dragAudioRegion(300,80);
216+
AtAudioView.dragAudioElement(300,80);
232217
I.pressKey('u');
233218

234219

235220
// creating a ghost region
236221
I.pressKey('1');
237-
AtAudioView.dragAudioRegion(160,80, false);
222+
AtAudioView.dragAudioElement(160,80, false);
238223
I.pressKey('1');
239224
I.wait(1);
240225
I.pressMouseUp();

0 commit comments

Comments
 (0)