1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-07-31 15:08:31 +00:00
* Added: Metadata rendering for Mod View, including stream latency, the host menu, etc. Certain metadata such as up-time is not supported due to Mod View already displaying that information. Closes #984. Closes #950. Closes #862.
* Added: Setting to disable the "Hide Stream Info Stripe" button in Mod View to avoid metadata elements moving during interaction.
* Added: Re-enable the setting to disable the player speeding up. The new implementation overrides a property on the underlying `<video>` rather than directly interacting with Twitch's player code.
* Fixed: Channel Points UI getting cut off when chat width is set smaller than the default. Closes #965
* API Added: Metadata definitions now have a `modview` attribute for opting into inclusion on mod view pages.
This commit is contained in:
SirStendec 2021-02-03 16:34:22 -05:00
parent 046de0bb8a
commit b337b6abe3
10 changed files with 176 additions and 24 deletions

View file

@ -291,6 +291,7 @@ export default class Metadata extends Module {
this.definitions['player-stats'] = {
button: true,
inherit: true,
modview: true,
refresh() {
return this.settings.get('metadata.player-stats')

View file

@ -22,6 +22,7 @@ const CLASSES = {
'side-closed-rec-channels': '.side-nav--collapsed .recommended-channels,.side-nav--collapsed .side-nav-section + .side-nav-section:not(.online-friends)',
'side-offline-channels': '.ffz--side-nav-card-offline',
'side-rerun-channels': '.side-nav .ffz--side-nav-card-rerun',
'modview-hide-info': '.tw-flex.modview-player-widget__hide-stream-info',
'community-highlights': '.community-highlight-stack__card',
@ -62,6 +63,16 @@ export default class CSSTweaks extends Module {
// Layout
this.settings.add('metadata.modview.hide-info', {
default: false,
ui: {
path: 'Channel > Metadata >> Mod View',
title: 'Hide "Hide Stream Info Stripe" button.',
component: 'setting-check-box'
},
changed: val => this.toggleHide('modview-hide-info', val)
});
this.settings.add('metadata.viewers.no-native', {
requires: ['metadata.viewers'],
default: null,
@ -420,6 +431,7 @@ export default class CSSTweaks extends Module {
}
onEnable() {
this.toggleHide('modview-hide-info', this.settings.get('metadata.modview.hide-info'));
this.toggleHide('side-nav-viewers', this.settings.get('layout.side-nav.hide-viewers'));
this.toggle('hide-native-uptime', this.settings.get('metadata.uptime.no-native'));
this.toggle('hide-native-viewers', this.settings.get('metadata.viewers.no-native'));

View file

@ -2,6 +2,10 @@
min-width: unset !important;
}
.reward-center__content {
width: calc(var(--ffz-chat-width) - 2rem) !important;
}
body .whispers--theatre-mode.whispers--right-column-expanded-beside {
right: var(--ffz-chat-width);
}

View file

@ -49,6 +49,7 @@ export default class FeaturedFollow extends Module {
this.metadata.definitions.following = {
order: 150,
button: true,
modview: true,
popup: async (data, tip, refresh_fn, add_callback) => {
const vue = this.resolve('vue'),

View file

@ -59,6 +59,7 @@ export default class HostButton extends Module {
border: true,
button: true,
fade_in: true,
modview: true,
disabled: () => this._host_updating || this._host_error,

View file

@ -5,9 +5,7 @@
// ============================================================================
import Module from 'utilities/module';
import { Color } from 'utilities/color';
import {debounce} from 'utilities/object';
import {createElement, ClickOutside, setChildren} from 'utilities/dom';
export default class ModView extends Module {
@ -28,6 +26,7 @@ export default class ModView extends Module {
this.should_enable = true;
this._cached_channel = null;
this._cached_id = null;
this.Root = this.elemental.define(
'mod-view-root', '.moderation-view-page',
@ -38,10 +37,11 @@ export default class ModView extends Module {
this.ModInfoBar = this.elemental.define(
'mod-info-bar', '.modview-player-widget__stream-info .simplebar-content',
['mod-view'],
{childNodes: true, subtree: true}, 1
{childNodes: true, subtree: true}, 1, 30000, false
);
this.checkRoot = debounce(this.checkRoot, 250);
this.checkBar = debounce(this.checkBar, 250);
}
onEnable() {
@ -81,6 +81,10 @@ export default class ModView extends Module {
}
}
checkBar() {
this.ModInfoBar.clean();
}
checkRoot() {
this.Root.each(el => this.updateRoot(el));
}
@ -95,8 +99,9 @@ export default class ModView extends Module {
i++;
}
if ( channel?.id && this._cached_channel != channel.id ) {
this._cached_channel = channel.id;
if ( channel?.id && this._cached_id != channel.id ) {
this._cached_id = channel.id;
this._cached_channel = channel;
this.updateSubscription(channel.login);
this.getChannelColor(el, channel.id).then(color => {
@ -171,6 +176,7 @@ export default class ModView extends Module {
}
removeRoot() {
this._cached_id = null;
this._cached_channel = null;
this.updateSubscription();
this.channel.updateChannelColor();
@ -196,7 +202,7 @@ export default class ModView extends Module {
title = bcast?.title,
game = bcast?.game;
if ( channel?.id && channel.id != this._cached_channel )
if ( channel?.id && channel.id != this._cached_id )
this.checkRoot();
if ( title != el._cached_title || game?.id != el._cached_game ) {
@ -209,6 +215,16 @@ export default class ModView extends Module {
title
});
}
if ( container ) {
if ( ! container._ffz_cont ) {
const e = container._ffz_cont = container.querySelector('.modview-player-widget__viewcount');
if ( e )
e.classList.add('ffz--mod-tray');
}
this.updateMetadata(container);
}
}
removeBar(el) {
@ -218,18 +234,68 @@ export default class ModView extends Module {
title: null
});
if ( el._ffz_cont )
el._ffz_cont.classList.remove('ffz--meta-tray');
const container = el.closest('.modview-player-widget__stream-info');
if ( ! container )
return;
el._ffz_cont = null;
if ( el._ffz_meta_timers ) {
for(const val of Object.values(el._ffz_meta_timers))
if ( container._ffz_cont )
container._ffz_cont.classList.remove('ffz--mod-tray');
container._ffz_cont = null;
if ( container._ffz_meta_timers ) {
for(const val of Object.values(container._ffz_meta_timers))
clearTimeout(val);
el._ffz_meta_timers = null;
container._ffz_meta_timers = null;
}
el._ffz_update = null;
container._ffz_update = null;
}
updateMetadata(el, keys) {
const cont = el._ffz_cont,
channel = this._cached_channel;
//root = this.fine.getReactInstance(el);
/*let channel = null, state = root?.return?.memoizedState, i = 0;
while(state != null && channel == null && i < 50 ) {
state = state?.next;
channel = state?.memoizedState?.current?.previousData?.result?.data?.channel;
i++;
}*/
if ( ! cont || ! document.contains(cont) ) {
this.checkBar();
return;
}
if ( ! channel?.id )
return;
if ( ! keys )
keys = this.metadata.keys;
else if ( ! Array.isArray(keys) )
keys = [keys];
const timers = el._ffz_meta_timers = el._ffz_meta_timers || {},
refresh_fn = key => this.updateMetadata(el, key),
data = {
channel: {
id: channel.id,
login: channel.login,
display_name: channel.displayName
},
el,
getViewerCount: () => 0,
getUserSelfImmediate: () => null,
getUserSelf: () => null,
getBroadcastID: () => null
};
for(const key of keys)
if ( this.metadata.definitions[key].modview )
this.metadata.renderLegacy(key, data, cont, timers, refresh_fn);
}
}

View file

@ -7,6 +7,7 @@
import Module from 'utilities/module';
import {createElement, on, off} from 'utilities/dom';
import {debounce} from 'utilities/object';
import { IS_FIREFOX } from 'src/utilities/constants';
export const PLAYER_ROUTES = [
'front-page', 'user', 'video', 'user-video', 'user-clip', 'user-videos',
@ -15,6 +16,11 @@ export const PLAYER_ROUTES = [
'mod-view', 'user-home'
];
const HAS_PITCH = (() => {
const el = createElement('video');
return el.preservesPitch != null || el.mozPreservesPitch != null
})();
const HAS_COMPRESSOR = window.AudioContext && window.DynamicsCompressorNode != null;
const STYLE_VALIDATOR = createElement('span');
@ -217,9 +223,6 @@ export default class Player extends Module {
});
}
/*
// This is currently broken due to changes Twitch has made in the player
// backend. Removing it for now to avoid user confusion.
this.settings.add('player.allow-catchup', {
default: true,
ui: {
@ -229,11 +232,8 @@ export default class Player extends Module {
component: 'setting-check-box'
},
changed: val => {
for(const inst of this.Player.instances)
this.updateAutoPlaybackRate(inst, val);
}
});*/
changed: () => this.updatePlaybackRates()
});
this.settings.add('player.mute-click', {
default: false,
@ -809,16 +809,19 @@ export default class Player extends Module {
this.updateGUI(inst);
this.compressPlayer(inst);
this.updatePlaybackRate(inst);
}
});
this.Player.on('mount', inst => {
this.updateGUI(inst);
this.compressPlayer(inst);
this.updatePlaybackRate(inst);
});
this.Player.on('update', inst => {
this.updateGUI(inst);
this.compressPlayer(inst);
this.updatePlaybackRate(inst);
});
this.Player.on('unmount', inst => {
@ -1058,7 +1061,7 @@ export default class Player extends Module {
return;
}
let icon, tip, extra, btn, cont = container.querySelector('.ffz--player-comp');
let icon, tip, extra, ff_el, btn, cont = container.querySelector('.ffz--player-comp');
if ( ! has_comp ) {
if ( cont )
cont.remove();
@ -1083,6 +1086,7 @@ export default class Player extends Module {
<div>
{tip = (<div class="ffz--p-tip" />)}
{extra = (<div class="ffz--p-extra tw-pd-t-05 ffz--tooltip-explain" />)}
{ff_el = IS_FIREFOX ? (<div class="ffz--p-ff tw-pd-t-05 ffz--tooltip-explain" />) : null}
</div>
</div>
</div>);
@ -1107,6 +1111,9 @@ export default class Player extends Module {
extra.textContent = this.i18n.t('player.comp_button.help', 'See the FFZ Control Center for details. If audio breaks, please reset the player.');
if ( ff_el )
ff_el.textContent += `\n${this.i18n.t('player.comp_button.firefox', 'Playback Speed controls will not function for Firefox users when the Compressor has been enabled.')}`;
icon.classList.toggle('ffz-i-comp-on', comp_active);
icon.classList.toggle('ffz-i-comp-off', ! comp_active);
btn.disabled = ! can_apply;
@ -1234,6 +1241,52 @@ export default class Player extends Module {
comp.release.value = this.settings.get('player.compressor.release');
}
updatePlaybackRates() {
for(const inst of this.Player.instances)
this.updatePlaybackRate(inst);
}
updatePlaybackRate(inst) {
const video = inst.props.mediaPlayerInstance?.mediaSinkManager?.video ||
inst.props.mediaPlayerInstance?.core?.mediaSinkManager?.video;
if ( ! video.setFFZPlaybackRate )
this.installPlaybackRate(video);
video.setFFZPlaybackRate(video.playbackRate);
}
installPlaybackRate(video) {
if ( video.setFFZPlaybackRate )
return;
let pbrate = video.playbackRate;
const t = this,
installProperty = () => {
if ( t.settings.get('player.allow-catchup') )
return;
Object.defineProperty(video, 'playbackRate', {
configurable: true,
get() {
return pbrate;
},
set(val) {
if ( val === 1 || val < 1 || val >= 1.1 )
video.setFFZPlaybackRate(val);
}
});
}
video.setFFZPlaybackRate = rate => {
delete video.playbackRate;
pbrate = rate;
video.playbackRate = rate;
installProperty();
};
}
addPiPButton(inst, tries = 0) {
const outer = inst.props.containerRef || this.fine.getChildNode(inst),
@ -1506,6 +1559,7 @@ export default class Player extends Module {
muted = player.isMuted();
new_vid.volume = muted ? 0 : vol;
new_vid.playsInline = true;
this.installPlaybackRate(new_vid);
video.replaceWith(new_vid);
player.attachHTMLVideoElement(new_vid);
setTimeout(() => {

View file

@ -60,6 +60,16 @@
margin-bottom: -.7rem !important;
}
.ffz--mod-tray {
margin-right: -1rem;
& > :not(.ffz-stat) {
order: 1;
margin-right: 1rem;
}
}
.ffz--meta-tray:not(.tw-flex-wrap) {
& > *:not(.ffz-stat) {
order: 500;

View file

@ -152,10 +152,13 @@ export default class Elemental extends Module {
}
listen(inst) {
listen(inst, ensure_live = true) {
if ( this._watching.has(inst) )
return;
if ( ensure_live )
this._timer = Date.now();
this._watching.add(inst);
this._updateLiveWatching();
}