Fix quality selection and latency stats using an asyncQuerySelector (#340)
* Fix quality select by waiting 50ms between clicks * Use an asyncQuerySelector instead * Use same fix for latency stats * await
This commit is contained in:
parent
796c4d8cc0
commit
feb20302d6
2 changed files with 139 additions and 72 deletions
|
|
@ -68,14 +68,7 @@ abstract class VideoStoreBase with Store {
|
|||
onMessageReceived: (message) async {
|
||||
if (settingsStore.defaultToHighestQuality &&
|
||||
_firstTimeSettingQuality) {
|
||||
await updateStreamQualities();
|
||||
|
||||
await setStreamQuality(
|
||||
_availableStreamQualities.firstWhere(
|
||||
(quality) => quality != 'Auto',
|
||||
orElse: () => 'Auto',
|
||||
),
|
||||
);
|
||||
await _setStreamQualityIndex(1);
|
||||
|
||||
_firstTimeSettingQuality = false;
|
||||
} else {
|
||||
|
|
@ -126,9 +119,13 @@ abstract class VideoStoreBase with Store {
|
|||
@readonly
|
||||
List<String> _availableStreamQualities = [];
|
||||
|
||||
// The current stream quality string
|
||||
// The current stream quality index
|
||||
@readonly
|
||||
String _streamQuality = 'Auto';
|
||||
int _streamQualityIndex = 0;
|
||||
|
||||
// The current stream quality string
|
||||
String get streamQuality =>
|
||||
_availableStreamQualities.elementAtOrNull(_streamQualityIndex) ?? 'Auto';
|
||||
|
||||
@readonly
|
||||
String? _latency;
|
||||
|
|
@ -190,12 +187,27 @@ abstract class VideoStoreBase with Store {
|
|||
try {
|
||||
await videoWebViewController.runJavaScript('''
|
||||
{
|
||||
document.querySelector('[data-a-target="player-settings-button"]').click();
|
||||
document.querySelector('[data-a-target="player-settings-menu-item-quality"]').click();
|
||||
const qualities = [...document.querySelectorAll('[data-a-target="player-settings-submenu-quality-option"] label div')].map((el) => el.textContent);
|
||||
document.querySelector('.tw-drop-down-menu-item-figure').click();
|
||||
document.querySelector('[data-a-target="player-settings-menu"] [role="menuitem"] button').click();
|
||||
StreamQualities.postMessage(JSON.stringify(qualities));
|
||||
const asyncQuerySelector = (selector) => new Promise((resolve) => {
|
||||
if (document.querySelector(selector)) {
|
||||
return resolve(document.querySelector(selector));
|
||||
}
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (document.querySelector(selector)) {
|
||||
observer.disconnect();
|
||||
resolve(document.querySelector(selector));
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
(async () => {
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-button"]')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-menu-item-quality"]')).click();
|
||||
await asyncQuerySelector('[data-a-target="player-settings-submenu-quality-option"] label div');
|
||||
const qualities = [...document.querySelectorAll('[data-a-target="player-settings-submenu-quality-option"] label div')].map((el) => el.textContent);
|
||||
StreamQualities.postMessage(JSON.stringify(qualities));
|
||||
(await asyncQuerySelector('.tw-drop-down-menu-item-figure')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-menu"] [role="menuitem"] button')).click();
|
||||
})();
|
||||
}
|
||||
''');
|
||||
} catch (e) {
|
||||
|
|
@ -207,60 +219,106 @@ abstract class VideoStoreBase with Store {
|
|||
Future<void> setStreamQuality(String newStreamQuality) async {
|
||||
final indexOfStreamQuality =
|
||||
_availableStreamQualities.indexOf(newStreamQuality);
|
||||
await videoWebViewController.runJavaScript('''
|
||||
await _setStreamQualityIndex(indexOfStreamQuality);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> _setStreamQualityIndex(int newStreamQualityIndex) async {
|
||||
try {
|
||||
await videoWebViewController.runJavaScript('''
|
||||
{
|
||||
document.querySelector('[data-a-target="player-settings-button"]').click();
|
||||
document.querySelector('[data-a-target="player-settings-menu-item-quality"]').click();
|
||||
[...document.querySelectorAll('[data-a-target="player-settings-submenu-quality-option"] input')][$indexOfStreamQuality].click();
|
||||
document.querySelector('.tw-drop-down-menu-item-figure').click();
|
||||
document.querySelector('[data-a-target="player-settings-menu"] [role="menuitem"] button').click();
|
||||
const asyncQuerySelector = (selector) => new Promise((resolve) => {
|
||||
if (document.querySelector(selector)) {
|
||||
return resolve(document.querySelector(selector));
|
||||
}
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (document.querySelector(selector)) {
|
||||
observer.disconnect();
|
||||
resolve(document.querySelector(selector));
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
(async () => {
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-button"]')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-menu-item-quality"]')).click();
|
||||
await asyncQuerySelector('[data-a-target="player-settings-submenu-quality-option"] input');
|
||||
[...document.querySelectorAll('[data-a-target="player-settings-submenu-quality-option"] input')][$newStreamQualityIndex].click();
|
||||
(await asyncQuerySelector('.tw-drop-down-menu-item-figure')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-menu"] [role="menuitem"] button')).click();
|
||||
})();
|
||||
}
|
||||
''');
|
||||
_streamQuality = newStreamQuality;
|
||||
_streamQualityIndex = newStreamQualityIndex;
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _hideDefaultOverlay() {
|
||||
videoWebViewController.runJavaScript('''
|
||||
{
|
||||
const hideElements = (...el) => {
|
||||
el.forEach((el) => {
|
||||
el?.style.setProperty("display", "none", "important");
|
||||
})
|
||||
Future<void> _hideDefaultOverlay() async {
|
||||
try {
|
||||
await videoWebViewController.runJavaScript('''
|
||||
{
|
||||
const hideElements = (...el) => {
|
||||
el.forEach((el) => {
|
||||
el?.style.setProperty("display", "none", "important");
|
||||
})
|
||||
}
|
||||
const hide = () => {
|
||||
const topBar = document.querySelector(".top-bar");
|
||||
const playerControls = document.querySelector(".player-controls");
|
||||
const channelDisclosures = document.querySelector("#channel-player-disclosures");
|
||||
hideElements(topBar, playerControls, channelDisclosures);
|
||||
}
|
||||
const observer = new MutationObserver(() => {
|
||||
const videoOverlay = document.querySelector('.video-player__overlay');
|
||||
if(!videoOverlay) return;
|
||||
hide();
|
||||
const videoOverlayObserver = new MutationObserver(hide);
|
||||
videoOverlayObserver.observe(videoOverlay, { childList: true, subtree: true });
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
const hide = () => {
|
||||
const topBar = document.querySelector(".top-bar");
|
||||
const playerControls = document.querySelector(".player-controls");
|
||||
const channelDisclosures = document.querySelector("#channel-player-disclosures");
|
||||
hideElements(topBar, playerControls, channelDisclosures);
|
||||
}
|
||||
const observer = new MutationObserver(() => {
|
||||
const videoOverlay = document.querySelector('.video-player__overlay');
|
||||
if(!videoOverlay) return;
|
||||
hide();
|
||||
const videoOverlayObserver = new MutationObserver(hide);
|
||||
videoOverlayObserver.observe(videoOverlay, { childList: true, subtree: true });
|
||||
observer.disconnect();
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
}
|
||||
''');
|
||||
''');
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _listenOnLatencyChanges() {
|
||||
videoWebViewController.runJavaScript('''
|
||||
{
|
||||
document.querySelector('[data-a-target="player-settings-button"]').click();
|
||||
document.querySelector('[data-a-target="player-settings-menu-item-advanced"]').click();
|
||||
document.querySelector('[data-a-target="player-settings-submenu-advanced-video-stats"] input').click();
|
||||
document.querySelector('.tw-drop-down-menu-item-figure').click();
|
||||
document.querySelector('[data-a-target="player-settings-menu"] [role="menuitem"] button').click();
|
||||
document.querySelector('[data-a-target="player-overlay-video-stats"]').style.display = "none";
|
||||
const observer = new MutationObserver((changes) => {
|
||||
Latency.postMessage(changes[0].target.textContent);
|
||||
})
|
||||
observer.observe(document.querySelector('[aria-label="Latency To Broadcaster"]'), { characterData: true, attributes: false, childList: false, subtree: true });
|
||||
Future<void> _listenOnLatencyChanges() async {
|
||||
try {
|
||||
await videoWebViewController.runJavaScript('''
|
||||
{
|
||||
const asyncQuerySelector = (selector) => new Promise((resolve) => {
|
||||
if (document.querySelector(selector)) {
|
||||
return resolve(document.querySelector(selector));
|
||||
}
|
||||
''');
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
if (document.querySelector(selector)) {
|
||||
observer.disconnect();
|
||||
resolve(document.querySelector(selector));
|
||||
}
|
||||
});
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
});
|
||||
(async () => {
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-button"]')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-menu-item-advanced"]')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-submenu-advanced-video-stats"] input')).click();
|
||||
(await asyncQuerySelector('.tw-drop-down-menu-item-figure')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-settings-menu"] [role="menuitem"] button')).click();
|
||||
(await asyncQuerySelector('[data-a-target="player-overlay-video-stats"]')).style.display = "none";
|
||||
const observer = new MutationObserver((changes) => {
|
||||
Latency.postMessage(changes[0].target.textContent);
|
||||
})
|
||||
observer.observe(document.querySelector('[aria-label="Latency To Broadcaster"]'), { characterData: true, attributes: false, childList: false, subtree: true });
|
||||
})();
|
||||
}
|
||||
''');
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the video webview.
|
||||
|
|
@ -282,8 +340,8 @@ abstract class VideoStoreBase with Store {
|
|||
});''',
|
||||
);
|
||||
if (settingsStore.showOverlay) {
|
||||
_hideDefaultOverlay();
|
||||
_listenOnLatencyChanges();
|
||||
await _hideDefaultOverlay();
|
||||
await _listenOnLatencyChanges();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint(e.toString());
|
||||
|
|
|
|||
|
|
@ -100,21 +100,21 @@ mixin _$VideoStore on VideoStoreBase, Store {
|
|||
});
|
||||
}
|
||||
|
||||
late final _$_streamQualityAtom =
|
||||
Atom(name: 'VideoStoreBase._streamQuality', context: context);
|
||||
late final _$_streamQualityIndexAtom =
|
||||
Atom(name: 'VideoStoreBase._streamQualityIndex', context: context);
|
||||
|
||||
String get streamQuality {
|
||||
_$_streamQualityAtom.reportRead();
|
||||
return super._streamQuality;
|
||||
int get streamQualityIndex {
|
||||
_$_streamQualityIndexAtom.reportRead();
|
||||
return super._streamQualityIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
String get _streamQuality => streamQuality;
|
||||
int get _streamQualityIndex => streamQualityIndex;
|
||||
|
||||
@override
|
||||
set _streamQuality(String value) {
|
||||
_$_streamQualityAtom.reportWrite(value, super._streamQuality, () {
|
||||
super._streamQuality = value;
|
||||
set _streamQualityIndex(int value) {
|
||||
_$_streamQualityIndexAtom.reportWrite(value, super._streamQualityIndex, () {
|
||||
super._streamQualityIndex = value;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -154,6 +154,15 @@ mixin _$VideoStore on VideoStoreBase, Store {
|
|||
.run(() => super.setStreamQuality(newStreamQuality));
|
||||
}
|
||||
|
||||
late final _$_setStreamQualityIndexAsyncAction =
|
||||
AsyncAction('VideoStoreBase._setStreamQualityIndex', context: context);
|
||||
|
||||
@override
|
||||
Future<void> _setStreamQualityIndex(int newStreamQualityIndex) {
|
||||
return _$_setStreamQualityIndexAsyncAction
|
||||
.run(() => super._setStreamQualityIndex(newStreamQualityIndex));
|
||||
}
|
||||
|
||||
late final _$initVideoAsyncAction =
|
||||
AsyncAction('VideoStoreBase.initVideo', context: context);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue