mirror of
https://github.com/danbulant/dots-hyprland
synced 2026-05-19 04:08:48 +00:00
media controls: visualizer
This commit is contained in:
parent
4473129599
commit
a3818322a6
4 changed files with 114 additions and 7 deletions
|
|
@ -26,6 +26,7 @@ Scope {
|
|||
property real contentPadding: 13
|
||||
property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.elevationMargin + 1
|
||||
property real artRounding: Appearance.rounding.verysmall
|
||||
property list<real> visualizerPoints: []
|
||||
|
||||
property bool hasPlasmaIntegration: false
|
||||
function isRealPlayer(player) {
|
||||
|
|
@ -68,13 +69,32 @@ Scope {
|
|||
return filtered;
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cavaProc
|
||||
running: mediaControlsLoader.active
|
||||
onRunningChanged: {
|
||||
if (!cavaProc.running) {
|
||||
root.visualizerPoints = [];
|
||||
}
|
||||
}
|
||||
command: ["cava", "-p", `${FileUtils.trimFileProtocol(Directories.config)}/quickshell/scripts/cava/raw_output_config.txt`]
|
||||
stdout: SplitParser {
|
||||
onRead: data => {
|
||||
// Parse `;`-separated values into the visualizerPoints array
|
||||
let allPoints = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p));
|
||||
let points = allPoints.slice(Math.floor(allPoints.length / 2), allPoints.length);
|
||||
root.visualizerPoints = points;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: mediaControlsLoader
|
||||
active: false
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: mediaControlsRoot
|
||||
visible: mediaControlsLoader.active
|
||||
visible: true
|
||||
|
||||
exclusiveZone: 0
|
||||
implicitWidth: (
|
||||
|
|
@ -112,6 +132,7 @@ Scope {
|
|||
delegate: PlayerControl {
|
||||
required property MprisPlayer modelData
|
||||
player: modelData
|
||||
visualizerPoints: root.visualizerPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ Item { // Player instance
|
|||
property string artFilePath: `${artDownloadLocation}/${artFileName}`
|
||||
property color artDominantColor: colorQuantizer?.colors[0] || Appearance.m3colors.m3secondaryContainer
|
||||
property bool downloaded: false
|
||||
property list<real> visualizerPoints: []
|
||||
property real maxVisualizerValue: 1000 // Max value in the data points
|
||||
|
||||
implicitWidth: widgetWidth
|
||||
implicitHeight: widgetHeight
|
||||
|
|
@ -150,6 +152,69 @@ Item { // Player instance
|
|||
}
|
||||
}
|
||||
|
||||
Canvas { // Visualizer
|
||||
id: visualizerCanvas
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
var points = playerController.visualizerPoints;
|
||||
var maxVal = playerController.maxVisualizerValue || 1;
|
||||
var h = height;
|
||||
var w = width;
|
||||
var n = points.length;
|
||||
if (n < 2) return;
|
||||
|
||||
// Smoothing: simple moving average (optional)
|
||||
var smoothPoints = [];
|
||||
var smoothWindow = 3; // adjust for more/less smoothing
|
||||
for (var i = 0; i < n; ++i) {
|
||||
var sum = 0, count = 0;
|
||||
for (var j = -smoothWindow; j <= smoothWindow; ++j) {
|
||||
var idx = Math.max(0, Math.min(n - 1, i + j));
|
||||
sum += points[idx];
|
||||
count++;
|
||||
}
|
||||
smoothPoints.push(sum / count);
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, h);
|
||||
for (var i = 0; i < n; ++i) {
|
||||
var x = i * w / (n - 1);
|
||||
var y = h - (smoothPoints[i] / maxVal) * h;
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.lineTo(w, h);
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = Qt.rgba(
|
||||
blendedColors.colPrimary.r,
|
||||
blendedColors.colPrimary.g,
|
||||
blendedColors.colPrimary.b,
|
||||
0.25
|
||||
);
|
||||
ctx.fill();
|
||||
}
|
||||
Connections {
|
||||
target: playerController
|
||||
function onVisualizerPointsChanged() {
|
||||
visualizerCanvas.requestPaint()
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect { // Blur a tiny bit to obscure away the points
|
||||
source: visualizerCanvas
|
||||
saturation: 0.2
|
||||
blurEnabled: true
|
||||
blurMax: 6
|
||||
blur: 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: root.contentPadding
|
||||
|
|
@ -160,7 +225,7 @@ Item { // Player instance
|
|||
Layout.fillHeight: true
|
||||
implicitWidth: height
|
||||
radius: root.artRounding
|
||||
color: blendedColors.colLayer1
|
||||
color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5)
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
|
|
@ -235,12 +300,18 @@ Item { // Player instance
|
|||
iconName: "skip_previous"
|
||||
onClicked: playerController.player?.previous()
|
||||
}
|
||||
StyledProgressBar {
|
||||
id: slider
|
||||
Item {
|
||||
id: progressBarContainer
|
||||
Layout.fillWidth: true
|
||||
highlightColor: blendedColors.colPrimary
|
||||
trackColor: blendedColors.colSecondaryContainer
|
||||
value: playerController.player?.position / playerController.player?.length
|
||||
implicitHeight: progressBar.implicitHeight
|
||||
|
||||
StyledProgressBar {
|
||||
id: progressBar
|
||||
anchors.fill: parent
|
||||
highlightColor: blendedColors.colPrimary
|
||||
trackColor: blendedColors.colSecondaryContainer
|
||||
value: playerController.player?.position / playerController.player?.length
|
||||
}
|
||||
}
|
||||
TrackChangeButton {
|
||||
iconName: "skip_next"
|
||||
|
|
|
|||
14
.config/quickshell/scripts/cava/raw_output_config.txt
Normal file
14
.config/quickshell/scripts/cava/raw_output_config.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[general]
|
||||
mode = waves
|
||||
framerate = 60
|
||||
autosens = 1
|
||||
bars = 60
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
|
||||
[smoothing]
|
||||
noise_reduction = 20
|
||||
|
||||
|
|
@ -5,6 +5,7 @@ pkgdesc='Illogical Impulse Audio Dependencies'
|
|||
arch=(any)
|
||||
license=(None)
|
||||
depends=(
|
||||
cava
|
||||
pavucontrol-qt
|
||||
wireplumber
|
||||
libdbusmenu-gtk3
|
||||
|
|
|
|||
Loading…
Reference in a new issue