mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-31 15:08:31 +00:00
4.20.58
* 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:
parent
046de0bb8a
commit
b337b6abe3
10 changed files with 176 additions and 24 deletions
|
@ -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')
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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(() => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue