├── .gitignore
├── .python-version
├── LICENSE
├── README.md
├── assets
├── music_black.png
└── music_white.png
├── requirements.txt
├── showcase
├── lyrics.png
├── notification.png
├── progress_bar.gif
└── tooltip.png
└── src
├── config.json
├── mediaplayer
├── mkstyle
├── style-catppuccin.css
└── style.css
/.gitignore:
--------------------------------------------------------------------------------
1 | .syncedlyrics/
2 | .python_version
3 |
--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | waybar
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Raffaele Mancuso
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # waybar-mediaplayer
2 |
3 | ## Retirement notice
4 |
5 | I don't use this project anymore and therefore I'm archiving it.
6 |
7 | Feel free to fork it.
8 |
9 | ## Introduction
10 |
11 | This is a mediaplayer for [waybar](https://github.com/Alexays/Waybar).
12 |
13 | Widget with album art and progress bar:
14 |
15 | 
16 |
17 | Notification with album art, track ticle and track artist
18 |
19 | 
20 |
21 | Tooltip with track title, track artist and track album:
22 |
23 | 
24 |
25 | Synced lyrics in tooltip:
26 |
27 | 
28 |
29 | It features:
30 | 1. Progress bar
31 | 2. Tooltip that displays title, author and album
32 | 3. Synced lyrics in tooltip
33 | 4. Album cover art (click to zoom)
34 | 5. Optional notification on song change
35 | 6. Click to play/pause, scroll up/down to scroll up/down on the playlist
36 |
37 | ## Requirements
38 |
39 | [playerctl](https://github.com/altdesktop/playerctl) must be installed.
40 |
41 | The default configuration use Nerd Fonts, so it requires waybar to use a Nerd Font.
42 |
43 | The default configuration use [feh](https://github.com/derf/feh) to open the album art image.
44 |
45 | ## Install
46 |
47 | 1. Create a virtual environment within which to run waybar. For example, using pyenv this would be:
48 |
49 | ```
50 | pyenv virtualenv 3.12.0 waybar
51 | pyenv activate waybar
52 | ```
53 |
54 | 1. Create a wrapper script to run waybar inside this virtual environment.
55 |
56 | For example, always using pyenv, create the file `start_waybar` and put it in your PATH:
57 | ```
58 | #!/usr/bin/env bash
59 | . ~/.pyenv/versions/waybar/bin/activate
60 | waybar
61 | ```
62 |
63 | 1. Clone this repo:
64 |
65 | ```
66 | git clone https://github.com/raffaem/waybar-mediaplayer "$HOME/.config/waybar/waybar-mediaplayer"
67 | cd "$HOME/.config/waybar/waybar-mediaplayer"
68 | ```
69 |
70 | 1. Install the required python packages inside your virtual environment:
71 |
72 | ```
73 | pip3 install -r requirements.txt
74 | ```
75 |
76 | 1. You need to find the name of your player. To do so, run `playerctl --list-all` while your player is running.
77 |
78 | 1. Open `"$HOME/.config/waybar/waybar-mediaplayer/src/config.json` and change `player_name` with the name of your player.
79 |
80 | 1. Put the following in `$HOME/.config/waybar/config`:
81 |
82 | ```
83 | "modules-left": ["image", "custom/mediaplayer"],
84 | ```
85 |
86 | ```
87 | "image": {
88 | "path": "/tmp/waybar-mediaplayer-art",
89 | "size": 32,
90 | "signal": 4,
91 | "on-click": "feh --auto-zoom --borderless --title 'feh-float' /tmp/waybar-mediaplayer-art"
92 | },
93 |
94 | "custom/mediaplayer": {
95 | "exec": "$HOME/.config/waybar/waybar-mediaplayer/src/mediaplayer monitor",
96 | "return-type": "json",
97 | "format": "{}",
98 | "on-click": "$HOME/.config/waybar/waybar-mediaplayer/src/mediaplayer play-pause",
99 | "on-scroll-up": "$HOME/.config/waybar/waybar-mediaplayer/src/mediaplayer next",
100 | "on-scroll-down": "$HOME/.config/waybar/waybar-mediaplayer/src/mediaplayer previous",
101 | "min-length": 20,
102 | "max-length": 20
103 | },
104 | ```
105 |
106 | Put the following in `$HOME/.config/waybar/style.css`:
107 |
108 | ```
109 | #custom-mediaplayer
110 | {
111 | font-size: 16px;
112 | border-radius: 2%;
113 | }
114 | @import "./waybar-mediaplayer/src/style.css";
115 | ```
116 |
117 | 1. Start waybar using the wrapper script `start_waybar`.
118 |
119 | The mediaplayer should work. Click on the progress bar to start/stop playing, and scroll on it to change song.
120 |
121 | ## Update
122 |
123 | ```
124 | cd "$HOME/.config/waybar/waybar-mediaplayer"
125 | git pull
126 | ```
127 |
128 | ## Personalization
129 |
130 | To disable notifications, put `is_notification=false` in `config.json`.
131 |
132 | To change widget's length, set `min-length` and `max-length` in `$HOME/.config/waybar/config`, and set `widget_length` in `$HOME/.config/waybar/waybar-mediaplayer/src/config.json`. These 3 variables MUST be set to the same value.
133 |
134 | In order for the album art to automatically update on song change, it's important that the `signal` variable of the `image` module in `$HOME/.config/waybar/config` matches the `image_signal` variable in `$HOME/.config/waybar/waybar-mediaplayer/src/config.json`.
135 |
136 | If you change the colors of the bar in `$HOME/.config/waybar/waybar-mediaplayer/src/config.json`, make sure to apply the changes by running `$HOME/.config/waybar/waybar-mediaplayer/src/mkstyle` and restart waybar.
137 |
138 | ## Troubleshooting
139 |
140 | ### Firefox users
141 |
142 | Although Firefox reports MPRIS metadata, the metadata it reports is not sufficient, as it doesn't report song's length. Please install the [Plasma Integration](https://addons.mozilla.org/en-US/firefox/addon/plasma-integration) add-on and use `plasma-browser-integration` as `player_name` in `$HOME/.config/waybar/waybar-mediaplayer/src/config.json`.
143 |
144 | To have album art, make sure to set `convert_to_jpeg` to `true` in `$HOME/.config/waybar/waybar-mediaplayer/src/config.json` (this option decreases performance, don't use it if not necessary).
145 |
146 | ### Me progress bar doesn't work
147 |
148 | It's likely cause by the player not reporting song length or position back to us. Run `$HOME/.config/waybar/waybar-mediaplayer/src/mediaplayer -vvv` to debug.
149 |
150 | ### Player reports its name with instance number
151 |
152 | If the player reports an instance after its name, please provide only the player name without the instance number. For example, [kew](https://github.com/ravachol/kew) may report itself as `kew123456`, where `123456` is an instance number which will change with different runs of `kew`. In this case, we report only `kew` without the instance number. This software will check whether the reported player name _starts_ with the name you provide to bind the correct player.
153 |
154 | ### Me title and tooltip are empty
155 |
156 | It's likely your song file doesn't contain metadata.
157 |
158 | Run `exiftool SONG.mp3` and check the `Title`, `Album` and `Artist` fields.
159 |
160 | ### Me album art doesn't change on song change
161 |
162 | Make sure that the `signal` in the `image` module in `$HOME/.config/waybar/config` matches the number provided by `image_signal` in `$HOME/.config/waybar/waybar-mediaplayer/src/config.json`.
163 |
164 |
--------------------------------------------------------------------------------
/assets/music_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raffaem/waybar-mediaplayer/ff5f9ad675f3726461d1d55e348c320201aa5d6b/assets/music_black.png
--------------------------------------------------------------------------------
/assets/music_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raffaem/waybar-mediaplayer/ff5f9ad675f3726461d1d55e348c320201aa5d6b/assets/music_white.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Pillow
2 | pycairo
3 | PyGObject
4 | syncedlyrics
5 |
--------------------------------------------------------------------------------
/showcase/lyrics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raffaem/waybar-mediaplayer/ff5f9ad675f3726461d1d55e348c320201aa5d6b/showcase/lyrics.png
--------------------------------------------------------------------------------
/showcase/notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raffaem/waybar-mediaplayer/ff5f9ad675f3726461d1d55e348c320201aa5d6b/showcase/notification.png
--------------------------------------------------------------------------------
/showcase/progress_bar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raffaem/waybar-mediaplayer/ff5f9ad675f3726461d1d55e348c320201aa5d6b/showcase/progress_bar.gif
--------------------------------------------------------------------------------
/showcase/tooltip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/raffaem/waybar-mediaplayer/ff5f9ad675f3726461d1d55e348c320201aa5d6b/showcase/tooltip.png
--------------------------------------------------------------------------------
/src/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "refresh_interval": 500,
3 | "is_notification": false,
4 | "notification_min_interval": 2,
5 | "widget_length": 20,
6 | "sepchar": "",
7 | "surface_color": "gray",
8 | "overlay_color": "cyan",
9 | "interval": 1,
10 | "text_rot_int": 1000,
11 | "image_signal": 4,
12 | "length_factor": 1,
13 | "player_name": "spotify",
14 | "convert_to_jpeg": false,
15 | "album_art_placeholder": "no",
16 | "lyrics_providers": [
17 | "Lrclib",
18 | "Musixmatch",
19 | "NetEase",
20 | "Megalobiz"
21 | ],
22 | "lyrics_span_before": 2,
23 | "lyrics_span_after": 2
24 | }
25 |
--------------------------------------------------------------------------------
/src/mediaplayer:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Copyright 2024 Raffaele Mancuso
3 | # SPDX-License-Identifier: MIT
4 | # Inspired by: https://github.com/Alexays/Waybar/blob/
5 | # master/resources/custom_modules/mediaplayer.py
6 | import argparse
7 | import io
8 | import json
9 | import logging
10 | import math
11 | import re
12 | import shutil
13 | import subprocess
14 | import sys
15 | import time
16 | import urllib
17 | from collections import namedtuple
18 | from functools import partial
19 | from pathlib import Path
20 |
21 | import gi
22 | import requests
23 | import syncedlyrics
24 | from PIL import Image
25 |
26 | gi.require_version("Playerctl", "2.0")
27 | from gi.repository import GLib, Playerctl # noqa: E402
28 |
29 | # Internal global variables
30 | config = None
31 | logger = logging.getLogger(__name__)
32 | artfp = Path("/tmp/waybar-mediaplayer-art")
33 | last_metadata = None
34 | last_art_url = None
35 | last_rot = 0
36 | last_rot_time = 0
37 | last_notification_time = 0
38 | # Whether refresh_interval callback is registered
39 | is_refint = False
40 | format_class = None
41 | icons = {"play": " ", "stop": " "}
42 | is_text_rotating = None
43 | lyrics = dict()
44 |
45 |
46 | def on_player_appeared(manager, player, requested_player):
47 | logger.debug("I was called")
48 | if player and (player.name == requested_player):
49 | init_player(manager, player)
50 | else:
51 | logger.debug(
52 | "New player appeared, but it's not the selected player, skipping"
53 | )
54 |
55 | logger.debug("Returning True")
56 | return True
57 |
58 |
59 | def delete_album_art():
60 | p = Path(__file__)
61 | if config["album_art_placeholder"] == "dark":
62 | phfp = p.parents[1] / "assets" / "music_black.png"
63 | elif config["album_art_placeholder"] == "light":
64 | phfp = p.parents[1] / "assets" / "music_white.png"
65 | elif config["album_art_placeholder"] == "no":
66 | phfp = None
67 | else:
68 | raise Exception(
69 | f"ERROR: Invalid value {config['album_art_placeholder']} "
70 | "for `album_art_placeholder` option"
71 | )
72 | if phfp:
73 | shutil.copy(phfp, artfp)
74 | else:
75 | if artfp.is_file():
76 | artfp.unlink()
77 | signal_album_art_change()
78 |
79 |
80 | def on_player_vanished(manager, player):
81 | logger.info("I was called")
82 | write_output()
83 | delete_album_art()
84 | logger.info("Returning True")
85 | return True
86 |
87 |
88 | def on_metadata(player, metadata, manager):
89 | logger.debug("I was called")
90 | update_metadata(player, metadata, manager)
91 | logger.debug("Returning True")
92 | return True
93 |
94 |
95 | def on_playback_status(player, status, manager):
96 | logger.info("I was called")
97 | logger.debug(f"status={status}")
98 | # Update the icon in the progressbar (play/pause)
99 | logger.debug("Calling register_refresh_interval_callback")
100 | register_refresh_interval_callback(player, manager)
101 | logger.debug("Calling update_progressbar")
102 | update_progressbar(manager, player)
103 | logger.debug("Returning True")
104 | return True
105 |
106 |
107 | def on_refresh_interval(manager):
108 | global is_refint
109 | logger.debug("I was called")
110 | # If there are no players, stop calling this handler
111 | if len(manager.props.players) == 0:
112 | logger.debug("Returning False")
113 | is_refint = False
114 | return False
115 | player = manager.props.players[0]
116 | # If we are not playing, stop calling this handler
117 | is_playing = player.props.status == "Playing"
118 | if not is_playing:
119 | logger.debug("Returning False")
120 | is_refint = False
121 | return False
122 | # Update progressbar and return
123 | update_progressbar(manager, player)
124 | logger.debug("Returning True")
125 | return True
126 |
127 |
128 | def write_output(text=None, class_=None, tooltip=""):
129 | global config
130 | # Default values if not explicitly set
131 | if not text:
132 | text = "" + (" " * (config["widget_length"] - 1))
133 | if not class_:
134 | class_ = gen_class(0.0)
135 | if not tooltip:
136 | tooltip = ""
137 | # HTML entities need to be escaped
138 | text = text.replace("&", "&")
139 | tooltip = tooltip.replace("&", "&")
140 | # Write to stdout
141 | output = {
142 | "text": text,
143 | "class": class_,
144 | "tooltip": "" + tooltip + "",
145 | }
146 | sys.stdout.write(json.dumps(output) + "\n")
147 | sys.stdout.flush()
148 |
149 |
150 | def register_refresh_interval_callback(player, manager):
151 | global is_refint
152 | logger.info("I was called")
153 | is_playing = player.props.status == "Playing"
154 | if is_playing and (not is_refint):
155 | logger.debug("Registering `refresh_interval` callback")
156 | GLib.timeout_add(
157 | config["refresh_interval"], on_refresh_interval, manager
158 | )
159 | is_refint = True
160 |
161 |
162 | def rotate_str_left(s, rot):
163 | return s[rot:] + s[:rot]
164 |
165 |
166 | def gen_widget_text(player):
167 | global last_rot, last_rot_time, is_text_rotating
168 |
169 | metadata = player.props.metadata
170 | # artist = player.get_artist()
171 | # artist = metadata["xesam:artist"][0]
172 | title = player.get_title() or ""
173 | # title = metadata["xesam:title"]
174 |
175 | icon = icons["play"] if player.props.status != "Playing" else icons["stop"]
176 |
177 | if (
178 | player.props.player_name == "spotify"
179 | and "mpris:trackid" in metadata.keys()
180 | and ":ad:" in metadata["mpris:trackid"]
181 | ):
182 | song_info = "AD PLAYING"
183 | else:
184 | song_info = title.strip()
185 |
186 | if not is_text_rotating:
187 | text = left_text(icon + song_info)
188 | else:
189 | song_info += " " + config["sepchar"] + " "
190 | elapsed = (time.time() - last_rot_time) * 1000
191 | rot_needs_update = elapsed > config["text_rot_int"]
192 | if rot_needs_update:
193 | to_add = math.floor(elapsed / config["text_rot_int"])
194 | l_rot = (last_rot + to_add) % len(song_info)
195 | last_rot = l_rot
196 | last_rot_time = time.time()
197 | else:
198 | l_rot = last_rot
199 | song_info = rotate_str_left(song_info, l_rot)
200 | text = icon + song_info
201 |
202 | return text
203 |
204 |
205 | def signal_album_art_change():
206 | cmd = ["pkill", "-RTMIN+" + str(config["image_signal"]), "waybar"]
207 | res = subprocess.run(cmd)
208 | logger.debug(f"pkill returned: {res}")
209 |
210 |
211 | def download_art(art_url):
212 | logger.debug("Updating album art")
213 | if art_url.startswith("http"):
214 | resp = requests.get(art_url)
215 | with open(artfp, "wb") as fh:
216 | fh.write(resp.content)
217 | logger.debug(
218 | f"Album art '{art_url}' downloaded and saved into '{artfp}'"
219 | )
220 | elif art_url.startswith("file://"):
221 | art_url = art_url[7:]
222 | art_url = urllib.parse.unquote(art_url)
223 | # symlinking the file doesn't work
224 | # apparently symlinks are not followed
225 | # artfp.symlink_to(art_url)
226 | shutil.copy(art_url, artfp)
227 | logger.debug(f"Album art '{art_url}' copied to '{artfp}'")
228 | elif Path(art_url).is_file():
229 | shutil.copy(art_url, artfp)
230 | logger.debug(f"Album art '{art_url}' copied to '{artfp}'")
231 | else:
232 | logger.debug(
233 | f"Invalid schema for {art_url}. Please report this as a bug."
234 | )
235 | return
236 | # Convert image to JPEG
237 | if config["convert_to_jpeg"]:
238 | artfp2 = artfp.parent / (artfp.stem + ".jpeg")
239 | with Image.open(artfp) as im:
240 | im.save(artfp2)
241 | shutil.move(artfp2, artfp)
242 | # Update album art on bar
243 | signal_album_art_change()
244 |
245 |
246 | def send_notification(summary, body):
247 | global last_notification_time
248 | cmd = [
249 | "notify-send",
250 | "--app-name=waybar-mediaplayer",
251 | "--icon=" + artfp,
252 | summary,
253 | body,
254 | ]
255 | logger.error(f"Running {cmd}")
256 | subprocess.run(cmd)
257 | last_notification_time = time.time()
258 |
259 |
260 | def update_metadata(player, metadata, manager):
261 | global last_notification_time, last_art_url, is_text_rotating
262 | logger.info("I was called")
263 | metadata = dict(metadata)
264 | # Update progressbar
265 | updated = update_progressbar(manager, player)
266 | if not updated:
267 | logger.debug("Progress bar not updated. Returning")
268 | return False
269 | # Download album art
270 | art_url = metadata.get("mpris:artUrl", None)
271 | if art_url and art_url != last_art_url:
272 | download_art(art_url)
273 | last_art_url = art_url
274 | # Notification
275 | notexp = time.time() - last_notification_time
276 | if config["is_notification"] and (
277 | notexp >= config["notification_min_interval"]
278 | ):
279 | send_notification(player.get_title(), player.get_artist())
280 | # Does text need rotation?
281 | title = player.get_title() or ""
282 | is_text_rotating = (
283 | len(title) + len(icons["stop"]) + 1 > config["widget_length"]
284 | )
285 | # Lyrics
286 | artist = player.get_artist() or ""
287 | get_new_lyrics(artist, title)
288 | logger.debug("Returning")
289 |
290 |
291 | def download_lyrics(artist, title):
292 | stream = io.StringIO()
293 | lyrlogger = syncedlyrics.logger
294 | lyrlogger.propagate = False
295 | lyrlogger.handlers.clear()
296 | lyrlogger.addHandler(logging.StreamHandler(stream))
297 | lyrlogger.setLevel(logging.INFO)
298 | try:
299 | lt = syncedlyrics.search(
300 | f"{artist} {title}",
301 | synced_only=True,
302 | providers=config["lyrics_providers"],
303 | )
304 | except Exception:
305 | logger.debug(
306 | "ERROR: syncedlyrics.search threw an exception. Returning None.")
307 | return None
308 | if lt is None:
309 | logger.debug(
310 | "ERROR: syncedlyrics.search returned None. Returning None.")
311 | return None
312 | lt = lt.replace("\n", "")
313 | lt = lt.split("[")
314 | lt = ["["+x for x in lt if x]
315 | provider = stream.getvalue().split("on ")[1].strip()
316 | stream.close()
317 | SyncedLyrics = namedtuple("SyncedLyrics", ["lt", "provider"])
318 | return SyncedLyrics(lt, provider)
319 |
320 |
321 | def get_new_lyrics(artist, title):
322 | global lyrics
323 | lyrics = {
324 | "secs": [0],
325 | "text": ["[INTRO]"],
326 | "last_ix": -1,
327 | "max_len": 0,
328 | "provider": None,
329 | }
330 | lyr = download_lyrics(artist, title)
331 | if lyr is None:
332 | logger.debug("ERROR: download_lyrics returned None. Returning.")
333 | lyrics = None
334 | return
335 | lyrics["provider"] = lyr.provider
336 | for i, line in enumerate(lyr.lt):
337 | m = re.match(r"\[(.*)\](.*)", line)
338 | if not m:
339 | continue
340 | # convert time from min:sec into sec
341 | timing = m.group(1)
342 | timing2 = timing.split(":")
343 | if len(timing2) != 2:
344 | logger.debug(f"ERROR: timing2={timing2} is not of length 2")
345 | continue
346 | try:
347 | secs = int(timing2[0]) * 60.0 + float(timing2[1])
348 | except ValueError:
349 | logger.debug(f"ERROR: ValueError while converting timing2={
350 | timing2} to seconds")
351 | continue
352 | # get lyrics text
353 | text = m.group(2).strip()
354 | if text == "":
355 | text = "[INTERMISSION]"
356 | lyrics["secs"].append(secs)
357 | lyrics["text"].append(text)
358 | lyrics["max_len"] = max(lyrics["max_len"], len(text))
359 | # DEBUG
360 | logger.debug(str(i) + str(line) + "->" + "[" + str(secs) + "] " + text)
361 | lyrics["text"].append("[OUTRO]")
362 | lyrics["secs"].append(lyrics["secs"][-1] + 1)
363 |
364 |
365 | def left_text(s):
366 | le = len(s)
367 | if le > config["widget_length"]:
368 | return s
369 | missing = config["widget_length"] - le
370 | s = s + " " * (missing)
371 | return s
372 |
373 |
374 | def center_text(s, ml=None):
375 | if not ml:
376 | ml = max([len(x) for x in s])
377 | for i in range(0, len(s)):
378 | delta = max(math.floor((ml - len(s[i])) / 2), 0)
379 | s[i] = (" " * delta) + s[i] + (" " * delta)
380 | return s
381 |
382 |
383 | def gen_class(perc):
384 | if not isinstance(perc, float):
385 | raise Exception(f"{perc} ({type(perc)}) is not a float")
386 | return "perc" + format_class(perc).replace(".", "-")
387 |
388 |
389 | # Called
390 | # 1. By `on_refresh_interval`
391 | # 2. By `on_metadata`
392 | # 3. By `on_playback_status`
393 | def update_progressbar(manager, player):
394 | global last_metadata, lyrics
395 | logger.debug("I was called")
396 | # Get percentage in the track
397 | pos = player.get_position()
398 | logger.debug(f"pos={pos}")
399 | # If it doesn't work, use CLI utility
400 | if pos == 0:
401 | cmd = ["playerctl", "--player=" + player.props.player_name, "position"]
402 | pos = subprocess.run(cmd, capture_output=True, text=True).stdout
403 | pos = float(pos) * (10**6)
404 | logger.debug(f"pos from CLI={pos}")
405 | pmetadata = player.props.metadata
406 | logger.debug(f"pmetadata={pmetadata}")
407 | try:
408 | # cast to int is important as some softwares,
409 | # like Amberol, are reporting a string
410 | # If the length of the track is known,
411 | # it should be provided in the metadata property
412 | # with the "mpris:length" key.
413 | # The length must be given in microseconds,
414 | # and be represented as a signed 64-bit integer
415 | # see: https://specifications.freedesktop.org/mpris-spec/2.2/Track_List_Interface.html # noqa: E501
416 | length = int(pmetadata["mpris:length"])
417 | except KeyError:
418 | logger.debug(
419 | "ERROR: The player is not reporting the length of the song to us. "
420 | "Progress bar won't work."
421 | )
422 | length = 100
423 | pos = 0
424 | else:
425 | logger.debug(f"length={length}")
426 | if length == 0:
427 | logger.debug(
428 | "ERROR: Length of song is 0. Progress bar won't work."
429 | )
430 | length = 100
431 | pos = 0
432 | # Compute song percentage
433 | perc = (pos / (length / config["length_factor"])) * 100
434 | perc = min(perc, 100.0)
435 | metadata = {
436 | "title": player.get_title() or "",
437 | "artist": player.get_artist() or "",
438 | "album": player.get_album() or "",
439 | "pos": pos,
440 | "length": length,
441 | "perc": perc,
442 | }
443 | # Check if a rotating text needs to be updated
444 | elapsed = (time.time() - last_rot_time) * 1000
445 | rot_needs_update = elapsed > config["text_rot_int"]
446 | # Check if progress bar needs to be updated
447 | diff = metadata["perc"] - last_metadata["perc"] if last_metadata else 99999
448 | pbar_needs_update = diff > config["interval"]
449 | # Check if lyrics needs an update
450 | if lyrics:
451 | # pos is in microseconds
452 | lyr_pos = float(pos / (10**6))
453 | lyrics_ix = sum(lyr_pos >= x for x in lyrics["secs"]) - 1
454 | lyrics_same = lyrics_ix == lyrics["last_ix"]
455 | else:
456 | lyrics_same = True
457 | # Check if song is the same (otherwise text needs to be updated)
458 | same_song = (
459 | last_metadata
460 | and metadata["title"] == last_metadata["title"]
461 | and metadata["artist"] == last_metadata["artist"]
462 | and metadata["album"] == last_metadata["album"]
463 | and metadata["length"] == last_metadata["length"]
464 | and lyrics_same
465 | )
466 | # Check if we need to update
467 | if same_song and \
468 | not pbar_needs_update and \
469 | not rot_needs_update:
470 | logger.debug(
471 | "Song is the same and neither the text "
472 | "nor the progressbar nor the lyrics needs an update. Returning"
473 | )
474 | return False
475 | # Write output
476 | # - Widget text
477 | widget_text = gen_widget_text(player)
478 | # - Tooltip text
479 | s = [metadata["title"], metadata["artist"], metadata["album"]]
480 | # -- Lyrics
481 | curr_line = None
482 | text_len = None
483 | if lyrics:
484 | cand = [len(x) for x in s]
485 | cand.append(lyrics.get("max_len", 3))
486 | text_len = max(cand)
487 | s.append("-" * text_len)
488 | provider = lyrics.get("provider", "")
489 | if provider is None:
490 | provider = "NO PROVIDER"
491 | s.append("["+provider+"]")
492 |
493 | # DEBUG
494 | # print(f"lyrics['secs']={lyrics['secs']}")
495 | # print(f"pos={pos}; length={length}")
496 | # print(f"lyr_pos={lyr_pos}; lyrics_ix={lyrics_ix}")
497 |
498 | lyrics["last_ix"] = lyrics_ix
499 | span_before = config["lyrics_span_before"]
500 | span_after = config["lyrics_span_after"]
501 | # Make sure we always have the same number of lines
502 | # also at start and end of the song
503 | span_before += max(lyrics_ix + span_after - len(lyrics["text"]) + 1, 0)
504 | span_after += max(span_before - lyrics_ix, 0)
505 | # Loop over lyrics window
506 | to_put = range(
507 | max(lyrics_ix - span_before, 0),
508 | min(lyrics_ix + span_after, len(lyrics["text"]) - 1) + 1,
509 | )
510 | for t in to_put:
511 | s.append(lyrics["text"][t])
512 | if t == lyrics_ix:
513 | curr_line = len(s) - 1
514 | # -- Tooltip
515 | center_text(s, text_len)
516 | if curr_line:
517 | s[curr_line] = "" + s[curr_line] + ""
518 | tooltip = f"{s[0]}"
519 | tooltip += f"\n{s[1]}"
520 | # Skip album in tooltip if we don't have it
521 | s[2] = s[2].strip()
522 | if s[2]:
523 | tooltip += f"\n{s[2]}"
524 | if len(s) >= 4 and s[3].strip():
525 | tooltip += "\n"
526 | tooltip += "\n".join(s[3:])
527 | write_output(widget_text, gen_class(perc), tooltip)
528 | # It's important to return True to keep the handler going
529 | last_metadata = metadata
530 | logger.debug("Returning True")
531 | return True
532 |
533 |
534 | def init_player(manager, name):
535 | logger.debug("I was called")
536 | logger.debug(f"name.name={name.name}")
537 | # Register handlers
538 | player = Playerctl.Player.new_from_name(name)
539 | player.connect("playback-status", on_playback_status, manager)
540 | player.connect("metadata", on_metadata, manager)
541 | manager.manage_player(player)
542 | # Call update_metadata to update progress bar and send notification
543 | logger.debug("Calling update_metadata")
544 | update_metadata(player, player.props.metadata, manager)
545 | # If we are playing, register refresh interval callback
546 | register_refresh_interval_callback(player, manager)
547 | logger.debug("Returning None")
548 |
549 |
550 | def signal_handler(sig, frame):
551 | logger.debug("I was called")
552 | sys.stdout.write("\n")
553 | sys.stdout.flush()
554 | # loop.quit()
555 | sys.exit(0)
556 |
557 |
558 | def parse_arguments():
559 | parser = argparse.ArgumentParser()
560 | # Increase verbosity with every occurrence of -v
561 | parser.add_argument("command", action="store", default="error")
562 | parser.add_argument("-v", "--verbose", action="count", default=0)
563 | return parser.parse_args()
564 |
565 |
566 | def main():
567 | arguments = parse_arguments()
568 |
569 | # Initialize logging
570 | logging.basicConfig(
571 | stream=sys.stderr,
572 | level=logging.DEBUG,
573 | format="[%(name)s] [%(funcName)s] [%(levelname)s] %(message)s",
574 | )
575 |
576 | # Logging is set by default to WARN and higher.
577 | # With every occurrence of -v it's lowered by one
578 | # until it reaches 0
579 | loglevel = max((3 - arguments.verbose) * 10, 0)
580 | logger.setLevel(loglevel)
581 | logging.getLogger("urllib3").setLevel(loglevel)
582 | logging.getLogger("syncedlyrics").setLevel(loglevel)
583 |
584 | logging.getLogger("syncedlyrics").setLevel(loglevel)
585 |
586 | # Log the sent command line arguments
587 | logger.info("I was called")
588 | logger.debug("Arguments received {}".format(vars(arguments)))
589 |
590 | # Read configuration from file
591 | fp = Path(sys.argv[0]).parent.resolve() / "config.json"
592 | if not fp.is_file():
593 | logging.critical(f"ERROR: Configuration file {fp} not found")
594 | sys.exit(1)
595 | with open(fp, "r") as fh:
596 | global config
597 | config = json.load(fh)
598 | logger.debug(f"config={config}")
599 | assert isinstance(config, dict)
600 |
601 | # Remove album art if present
602 | delete_album_art()
603 |
604 | # Function to generate CSS classes
605 | global format_class
606 | interval = config["interval"]
607 | digits = math.floor(math.log10(interval))
608 | digits = max(0, math.fabs(digits))
609 | digits = int(digits)
610 | format_class = partial(lambda x, y: str(round(x, y)), y=digits)
611 |
612 | manager = Playerctl.PlayerManager()
613 |
614 | requested_player = config["player_name"].split(".")[0]
615 | logger.debug(
616 | f"Splitting player_name on `.`. requested_player={requested_player}"
617 | )
618 |
619 | ini = False
620 | for player in manager.props.player_names:
621 | logger.debug(f"Found player '{player.name}'")
622 | if not player.name.startswith(requested_player):
623 | logger.debug("This is not the filtered player, skipping it")
624 | continue
625 | if arguments.command == "play-pause":
626 | Playerctl.Player.new(player.name).play_pause()
627 | sys.exit(0)
628 | elif arguments.command == "next":
629 | Playerctl.Player.new(player.name).next()
630 | sys.exit(0)
631 | elif arguments.command == "previous":
632 | Playerctl.Player.new(player.name).previous()
633 | sys.exit(0)
634 | elif arguments.command == "monitor":
635 | logger.debug("Initializing player")
636 | init_player(manager, player)
637 | ini = True
638 | else:
639 | logger.critical(f"Invalid argument {sys.argv[1]}")
640 | sys.exit(1)
641 |
642 | if not ini:
643 | logger.debug("No player found. Printing empty")
644 | write_output()
645 |
646 | loop = GLib.MainLoop()
647 |
648 | on_player_appeared_inst = partial(
649 | on_player_appeared, requested_player=requested_player
650 | )
651 | # manager.connect("player-appeared", on_player_appeared)
652 | manager.connect("name-appeared", on_player_appeared_inst)
653 | manager.connect("player-vanished", on_player_vanished)
654 |
655 | # signal.signal(signal.SIGINT, signal_handler)
656 | # signal.signal(signal.SIGTERM, signal_handler)
657 | # signal.signal(signal.SIGPIPE, signal.SIG_DFL)
658 |
659 | loop.run()
660 |
661 |
662 | if __name__ == "__main__":
663 | main()
664 |
--------------------------------------------------------------------------------
/src/mkstyle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import argparse
4 | import json
5 | import math
6 | import sys
7 | from functools import partial
8 | from pathlib import Path
9 |
10 | import numpy as np
11 |
12 | # Read config file
13 | fp = Path(sys.argv[0]).parent.resolve() / "config.json"
14 | if not fp.is_file():
15 | print(f"ERROR: Configuration file {fp} not found")
16 | sys.exit(1)
17 | with open(fp, "r") as fh:
18 | global config
19 | config = json.load(fh)
20 | assert isinstance(config, dict)
21 |
22 | overlay_color = config["overlay_color"]
23 | surface_color = config["surface_color"]
24 | interval = config["interval"]
25 | digits = math.floor(math.log10(interval))
26 | digits = max(0, math.fabs(digits))
27 | digits = int(digits)
28 | print(f"Decimal points: {digits}")
29 |
30 |
31 | def myround(x, y):
32 | return str(round(x, y))
33 |
34 |
35 | # Internals
36 | format_pc = partial(myround, y=digits + 1)
37 | format_class = partial(myround, y=1)
38 |
39 |
40 | def parse_arguments():
41 | parser = argparse.ArgumentParser()
42 | parser.add_argument("--output-file", action="store", default="style.css")
43 | return parser.parse_args()
44 |
45 |
46 | args = parse_arguments()
47 | outfp = args.output_file
48 | print(f"Writing output to {outfp}")
49 |
50 | outfh = open(outfp, "w", encoding="utf8")
51 |
52 | for i in np.arange(0, 100 + interval, interval):
53 | i = float(i)
54 | class_ = "perc" + format_class(i).replace(".", "-")
55 | i2 = i + (interval / 10)
56 | outfh.write(
57 | f"""#custom-mediaplayer.{class_} {{
58 | background-image: linear-gradient(
59 | to right,
60 | {overlay_color} {format_pc(i)}%,
61 | {surface_color} {format_pc(i2)}%
62 | );
63 | }}\n"""
64 | )
65 |
66 | outfh.close()
67 |
--------------------------------------------------------------------------------
/src/style-catppuccin.css:
--------------------------------------------------------------------------------
1 | #custom-mediaplayer.perc0-0 {
2 | background-image: linear-gradient(
3 | to right,
4 | @overlay0 0.0%,
5 | @surface0 0.1%
6 | );
7 | }
8 | #custom-mediaplayer.perc1-0 {
9 | background-image: linear-gradient(
10 | to right,
11 | @overlay0 1.0%,
12 | @surface0 1.1%
13 | );
14 | }
15 | #custom-mediaplayer.perc2-0 {
16 | background-image: linear-gradient(
17 | to right,
18 | @overlay0 2.0%,
19 | @surface0 2.1%
20 | );
21 | }
22 | #custom-mediaplayer.perc3-0 {
23 | background-image: linear-gradient(
24 | to right,
25 | @overlay0 3.0%,
26 | @surface0 3.1%
27 | );
28 | }
29 | #custom-mediaplayer.perc4-0 {
30 | background-image: linear-gradient(
31 | to right,
32 | @overlay0 4.0%,
33 | @surface0 4.1%
34 | );
35 | }
36 | #custom-mediaplayer.perc5-0 {
37 | background-image: linear-gradient(
38 | to right,
39 | @overlay0 5.0%,
40 | @surface0 5.1%
41 | );
42 | }
43 | #custom-mediaplayer.perc6-0 {
44 | background-image: linear-gradient(
45 | to right,
46 | @overlay0 6.0%,
47 | @surface0 6.1%
48 | );
49 | }
50 | #custom-mediaplayer.perc7-0 {
51 | background-image: linear-gradient(
52 | to right,
53 | @overlay0 7.0%,
54 | @surface0 7.1%
55 | );
56 | }
57 | #custom-mediaplayer.perc8-0 {
58 | background-image: linear-gradient(
59 | to right,
60 | @overlay0 8.0%,
61 | @surface0 8.1%
62 | );
63 | }
64 | #custom-mediaplayer.perc9-0 {
65 | background-image: linear-gradient(
66 | to right,
67 | @overlay0 9.0%,
68 | @surface0 9.1%
69 | );
70 | }
71 | #custom-mediaplayer.perc10-0 {
72 | background-image: linear-gradient(
73 | to right,
74 | @overlay0 10.0%,
75 | @surface0 10.1%
76 | );
77 | }
78 | #custom-mediaplayer.perc11-0 {
79 | background-image: linear-gradient(
80 | to right,
81 | @overlay0 11.0%,
82 | @surface0 11.1%
83 | );
84 | }
85 | #custom-mediaplayer.perc12-0 {
86 | background-image: linear-gradient(
87 | to right,
88 | @overlay0 12.0%,
89 | @surface0 12.1%
90 | );
91 | }
92 | #custom-mediaplayer.perc13-0 {
93 | background-image: linear-gradient(
94 | to right,
95 | @overlay0 13.0%,
96 | @surface0 13.1%
97 | );
98 | }
99 | #custom-mediaplayer.perc14-0 {
100 | background-image: linear-gradient(
101 | to right,
102 | @overlay0 14.0%,
103 | @surface0 14.1%
104 | );
105 | }
106 | #custom-mediaplayer.perc15-0 {
107 | background-image: linear-gradient(
108 | to right,
109 | @overlay0 15.0%,
110 | @surface0 15.1%
111 | );
112 | }
113 | #custom-mediaplayer.perc16-0 {
114 | background-image: linear-gradient(
115 | to right,
116 | @overlay0 16.0%,
117 | @surface0 16.1%
118 | );
119 | }
120 | #custom-mediaplayer.perc17-0 {
121 | background-image: linear-gradient(
122 | to right,
123 | @overlay0 17.0%,
124 | @surface0 17.1%
125 | );
126 | }
127 | #custom-mediaplayer.perc18-0 {
128 | background-image: linear-gradient(
129 | to right,
130 | @overlay0 18.0%,
131 | @surface0 18.1%
132 | );
133 | }
134 | #custom-mediaplayer.perc19-0 {
135 | background-image: linear-gradient(
136 | to right,
137 | @overlay0 19.0%,
138 | @surface0 19.1%
139 | );
140 | }
141 | #custom-mediaplayer.perc20-0 {
142 | background-image: linear-gradient(
143 | to right,
144 | @overlay0 20.0%,
145 | @surface0 20.1%
146 | );
147 | }
148 | #custom-mediaplayer.perc21-0 {
149 | background-image: linear-gradient(
150 | to right,
151 | @overlay0 21.0%,
152 | @surface0 21.1%
153 | );
154 | }
155 | #custom-mediaplayer.perc22-0 {
156 | background-image: linear-gradient(
157 | to right,
158 | @overlay0 22.0%,
159 | @surface0 22.1%
160 | );
161 | }
162 | #custom-mediaplayer.perc23-0 {
163 | background-image: linear-gradient(
164 | to right,
165 | @overlay0 23.0%,
166 | @surface0 23.1%
167 | );
168 | }
169 | #custom-mediaplayer.perc24-0 {
170 | background-image: linear-gradient(
171 | to right,
172 | @overlay0 24.0%,
173 | @surface0 24.1%
174 | );
175 | }
176 | #custom-mediaplayer.perc25-0 {
177 | background-image: linear-gradient(
178 | to right,
179 | @overlay0 25.0%,
180 | @surface0 25.1%
181 | );
182 | }
183 | #custom-mediaplayer.perc26-0 {
184 | background-image: linear-gradient(
185 | to right,
186 | @overlay0 26.0%,
187 | @surface0 26.1%
188 | );
189 | }
190 | #custom-mediaplayer.perc27-0 {
191 | background-image: linear-gradient(
192 | to right,
193 | @overlay0 27.0%,
194 | @surface0 27.1%
195 | );
196 | }
197 | #custom-mediaplayer.perc28-0 {
198 | background-image: linear-gradient(
199 | to right,
200 | @overlay0 28.0%,
201 | @surface0 28.1%
202 | );
203 | }
204 | #custom-mediaplayer.perc29-0 {
205 | background-image: linear-gradient(
206 | to right,
207 | @overlay0 29.0%,
208 | @surface0 29.1%
209 | );
210 | }
211 | #custom-mediaplayer.perc30-0 {
212 | background-image: linear-gradient(
213 | to right,
214 | @overlay0 30.0%,
215 | @surface0 30.1%
216 | );
217 | }
218 | #custom-mediaplayer.perc31-0 {
219 | background-image: linear-gradient(
220 | to right,
221 | @overlay0 31.0%,
222 | @surface0 31.1%
223 | );
224 | }
225 | #custom-mediaplayer.perc32-0 {
226 | background-image: linear-gradient(
227 | to right,
228 | @overlay0 32.0%,
229 | @surface0 32.1%
230 | );
231 | }
232 | #custom-mediaplayer.perc33-0 {
233 | background-image: linear-gradient(
234 | to right,
235 | @overlay0 33.0%,
236 | @surface0 33.1%
237 | );
238 | }
239 | #custom-mediaplayer.perc34-0 {
240 | background-image: linear-gradient(
241 | to right,
242 | @overlay0 34.0%,
243 | @surface0 34.1%
244 | );
245 | }
246 | #custom-mediaplayer.perc35-0 {
247 | background-image: linear-gradient(
248 | to right,
249 | @overlay0 35.0%,
250 | @surface0 35.1%
251 | );
252 | }
253 | #custom-mediaplayer.perc36-0 {
254 | background-image: linear-gradient(
255 | to right,
256 | @overlay0 36.0%,
257 | @surface0 36.1%
258 | );
259 | }
260 | #custom-mediaplayer.perc37-0 {
261 | background-image: linear-gradient(
262 | to right,
263 | @overlay0 37.0%,
264 | @surface0 37.1%
265 | );
266 | }
267 | #custom-mediaplayer.perc38-0 {
268 | background-image: linear-gradient(
269 | to right,
270 | @overlay0 38.0%,
271 | @surface0 38.1%
272 | );
273 | }
274 | #custom-mediaplayer.perc39-0 {
275 | background-image: linear-gradient(
276 | to right,
277 | @overlay0 39.0%,
278 | @surface0 39.1%
279 | );
280 | }
281 | #custom-mediaplayer.perc40-0 {
282 | background-image: linear-gradient(
283 | to right,
284 | @overlay0 40.0%,
285 | @surface0 40.1%
286 | );
287 | }
288 | #custom-mediaplayer.perc41-0 {
289 | background-image: linear-gradient(
290 | to right,
291 | @overlay0 41.0%,
292 | @surface0 41.1%
293 | );
294 | }
295 | #custom-mediaplayer.perc42-0 {
296 | background-image: linear-gradient(
297 | to right,
298 | @overlay0 42.0%,
299 | @surface0 42.1%
300 | );
301 | }
302 | #custom-mediaplayer.perc43-0 {
303 | background-image: linear-gradient(
304 | to right,
305 | @overlay0 43.0%,
306 | @surface0 43.1%
307 | );
308 | }
309 | #custom-mediaplayer.perc44-0 {
310 | background-image: linear-gradient(
311 | to right,
312 | @overlay0 44.0%,
313 | @surface0 44.1%
314 | );
315 | }
316 | #custom-mediaplayer.perc45-0 {
317 | background-image: linear-gradient(
318 | to right,
319 | @overlay0 45.0%,
320 | @surface0 45.1%
321 | );
322 | }
323 | #custom-mediaplayer.perc46-0 {
324 | background-image: linear-gradient(
325 | to right,
326 | @overlay0 46.0%,
327 | @surface0 46.1%
328 | );
329 | }
330 | #custom-mediaplayer.perc47-0 {
331 | background-image: linear-gradient(
332 | to right,
333 | @overlay0 47.0%,
334 | @surface0 47.1%
335 | );
336 | }
337 | #custom-mediaplayer.perc48-0 {
338 | background-image: linear-gradient(
339 | to right,
340 | @overlay0 48.0%,
341 | @surface0 48.1%
342 | );
343 | }
344 | #custom-mediaplayer.perc49-0 {
345 | background-image: linear-gradient(
346 | to right,
347 | @overlay0 49.0%,
348 | @surface0 49.1%
349 | );
350 | }
351 | #custom-mediaplayer.perc50-0 {
352 | background-image: linear-gradient(
353 | to right,
354 | @overlay0 50.0%,
355 | @surface0 50.1%
356 | );
357 | }
358 | #custom-mediaplayer.perc51-0 {
359 | background-image: linear-gradient(
360 | to right,
361 | @overlay0 51.0%,
362 | @surface0 51.1%
363 | );
364 | }
365 | #custom-mediaplayer.perc52-0 {
366 | background-image: linear-gradient(
367 | to right,
368 | @overlay0 52.0%,
369 | @surface0 52.1%
370 | );
371 | }
372 | #custom-mediaplayer.perc53-0 {
373 | background-image: linear-gradient(
374 | to right,
375 | @overlay0 53.0%,
376 | @surface0 53.1%
377 | );
378 | }
379 | #custom-mediaplayer.perc54-0 {
380 | background-image: linear-gradient(
381 | to right,
382 | @overlay0 54.0%,
383 | @surface0 54.1%
384 | );
385 | }
386 | #custom-mediaplayer.perc55-0 {
387 | background-image: linear-gradient(
388 | to right,
389 | @overlay0 55.0%,
390 | @surface0 55.1%
391 | );
392 | }
393 | #custom-mediaplayer.perc56-0 {
394 | background-image: linear-gradient(
395 | to right,
396 | @overlay0 56.0%,
397 | @surface0 56.1%
398 | );
399 | }
400 | #custom-mediaplayer.perc57-0 {
401 | background-image: linear-gradient(
402 | to right,
403 | @overlay0 57.0%,
404 | @surface0 57.1%
405 | );
406 | }
407 | #custom-mediaplayer.perc58-0 {
408 | background-image: linear-gradient(
409 | to right,
410 | @overlay0 58.0%,
411 | @surface0 58.1%
412 | );
413 | }
414 | #custom-mediaplayer.perc59-0 {
415 | background-image: linear-gradient(
416 | to right,
417 | @overlay0 59.0%,
418 | @surface0 59.1%
419 | );
420 | }
421 | #custom-mediaplayer.perc60-0 {
422 | background-image: linear-gradient(
423 | to right,
424 | @overlay0 60.0%,
425 | @surface0 60.1%
426 | );
427 | }
428 | #custom-mediaplayer.perc61-0 {
429 | background-image: linear-gradient(
430 | to right,
431 | @overlay0 61.0%,
432 | @surface0 61.1%
433 | );
434 | }
435 | #custom-mediaplayer.perc62-0 {
436 | background-image: linear-gradient(
437 | to right,
438 | @overlay0 62.0%,
439 | @surface0 62.1%
440 | );
441 | }
442 | #custom-mediaplayer.perc63-0 {
443 | background-image: linear-gradient(
444 | to right,
445 | @overlay0 63.0%,
446 | @surface0 63.1%
447 | );
448 | }
449 | #custom-mediaplayer.perc64-0 {
450 | background-image: linear-gradient(
451 | to right,
452 | @overlay0 64.0%,
453 | @surface0 64.1%
454 | );
455 | }
456 | #custom-mediaplayer.perc65-0 {
457 | background-image: linear-gradient(
458 | to right,
459 | @overlay0 65.0%,
460 | @surface0 65.1%
461 | );
462 | }
463 | #custom-mediaplayer.perc66-0 {
464 | background-image: linear-gradient(
465 | to right,
466 | @overlay0 66.0%,
467 | @surface0 66.1%
468 | );
469 | }
470 | #custom-mediaplayer.perc67-0 {
471 | background-image: linear-gradient(
472 | to right,
473 | @overlay0 67.0%,
474 | @surface0 67.1%
475 | );
476 | }
477 | #custom-mediaplayer.perc68-0 {
478 | background-image: linear-gradient(
479 | to right,
480 | @overlay0 68.0%,
481 | @surface0 68.1%
482 | );
483 | }
484 | #custom-mediaplayer.perc69-0 {
485 | background-image: linear-gradient(
486 | to right,
487 | @overlay0 69.0%,
488 | @surface0 69.1%
489 | );
490 | }
491 | #custom-mediaplayer.perc70-0 {
492 | background-image: linear-gradient(
493 | to right,
494 | @overlay0 70.0%,
495 | @surface0 70.1%
496 | );
497 | }
498 | #custom-mediaplayer.perc71-0 {
499 | background-image: linear-gradient(
500 | to right,
501 | @overlay0 71.0%,
502 | @surface0 71.1%
503 | );
504 | }
505 | #custom-mediaplayer.perc72-0 {
506 | background-image: linear-gradient(
507 | to right,
508 | @overlay0 72.0%,
509 | @surface0 72.1%
510 | );
511 | }
512 | #custom-mediaplayer.perc73-0 {
513 | background-image: linear-gradient(
514 | to right,
515 | @overlay0 73.0%,
516 | @surface0 73.1%
517 | );
518 | }
519 | #custom-mediaplayer.perc74-0 {
520 | background-image: linear-gradient(
521 | to right,
522 | @overlay0 74.0%,
523 | @surface0 74.1%
524 | );
525 | }
526 | #custom-mediaplayer.perc75-0 {
527 | background-image: linear-gradient(
528 | to right,
529 | @overlay0 75.0%,
530 | @surface0 75.1%
531 | );
532 | }
533 | #custom-mediaplayer.perc76-0 {
534 | background-image: linear-gradient(
535 | to right,
536 | @overlay0 76.0%,
537 | @surface0 76.1%
538 | );
539 | }
540 | #custom-mediaplayer.perc77-0 {
541 | background-image: linear-gradient(
542 | to right,
543 | @overlay0 77.0%,
544 | @surface0 77.1%
545 | );
546 | }
547 | #custom-mediaplayer.perc78-0 {
548 | background-image: linear-gradient(
549 | to right,
550 | @overlay0 78.0%,
551 | @surface0 78.1%
552 | );
553 | }
554 | #custom-mediaplayer.perc79-0 {
555 | background-image: linear-gradient(
556 | to right,
557 | @overlay0 79.0%,
558 | @surface0 79.1%
559 | );
560 | }
561 | #custom-mediaplayer.perc80-0 {
562 | background-image: linear-gradient(
563 | to right,
564 | @overlay0 80.0%,
565 | @surface0 80.1%
566 | );
567 | }
568 | #custom-mediaplayer.perc81-0 {
569 | background-image: linear-gradient(
570 | to right,
571 | @overlay0 81.0%,
572 | @surface0 81.1%
573 | );
574 | }
575 | #custom-mediaplayer.perc82-0 {
576 | background-image: linear-gradient(
577 | to right,
578 | @overlay0 82.0%,
579 | @surface0 82.1%
580 | );
581 | }
582 | #custom-mediaplayer.perc83-0 {
583 | background-image: linear-gradient(
584 | to right,
585 | @overlay0 83.0%,
586 | @surface0 83.1%
587 | );
588 | }
589 | #custom-mediaplayer.perc84-0 {
590 | background-image: linear-gradient(
591 | to right,
592 | @overlay0 84.0%,
593 | @surface0 84.1%
594 | );
595 | }
596 | #custom-mediaplayer.perc85-0 {
597 | background-image: linear-gradient(
598 | to right,
599 | @overlay0 85.0%,
600 | @surface0 85.1%
601 | );
602 | }
603 | #custom-mediaplayer.perc86-0 {
604 | background-image: linear-gradient(
605 | to right,
606 | @overlay0 86.0%,
607 | @surface0 86.1%
608 | );
609 | }
610 | #custom-mediaplayer.perc87-0 {
611 | background-image: linear-gradient(
612 | to right,
613 | @overlay0 87.0%,
614 | @surface0 87.1%
615 | );
616 | }
617 | #custom-mediaplayer.perc88-0 {
618 | background-image: linear-gradient(
619 | to right,
620 | @overlay0 88.0%,
621 | @surface0 88.1%
622 | );
623 | }
624 | #custom-mediaplayer.perc89-0 {
625 | background-image: linear-gradient(
626 | to right,
627 | @overlay0 89.0%,
628 | @surface0 89.1%
629 | );
630 | }
631 | #custom-mediaplayer.perc90-0 {
632 | background-image: linear-gradient(
633 | to right,
634 | @overlay0 90.0%,
635 | @surface0 90.1%
636 | );
637 | }
638 | #custom-mediaplayer.perc91-0 {
639 | background-image: linear-gradient(
640 | to right,
641 | @overlay0 91.0%,
642 | @surface0 91.1%
643 | );
644 | }
645 | #custom-mediaplayer.perc92-0 {
646 | background-image: linear-gradient(
647 | to right,
648 | @overlay0 92.0%,
649 | @surface0 92.1%
650 | );
651 | }
652 | #custom-mediaplayer.perc93-0 {
653 | background-image: linear-gradient(
654 | to right,
655 | @overlay0 93.0%,
656 | @surface0 93.1%
657 | );
658 | }
659 | #custom-mediaplayer.perc94-0 {
660 | background-image: linear-gradient(
661 | to right,
662 | @overlay0 94.0%,
663 | @surface0 94.1%
664 | );
665 | }
666 | #custom-mediaplayer.perc95-0 {
667 | background-image: linear-gradient(
668 | to right,
669 | @overlay0 95.0%,
670 | @surface0 95.1%
671 | );
672 | }
673 | #custom-mediaplayer.perc96-0 {
674 | background-image: linear-gradient(
675 | to right,
676 | @overlay0 96.0%,
677 | @surface0 96.1%
678 | );
679 | }
680 | #custom-mediaplayer.perc97-0 {
681 | background-image: linear-gradient(
682 | to right,
683 | @overlay0 97.0%,
684 | @surface0 97.1%
685 | );
686 | }
687 | #custom-mediaplayer.perc98-0 {
688 | background-image: linear-gradient(
689 | to right,
690 | @overlay0 98.0%,
691 | @surface0 98.1%
692 | );
693 | }
694 | #custom-mediaplayer.perc99-0 {
695 | background-image: linear-gradient(
696 | to right,
697 | @overlay0 99.0%,
698 | @surface0 99.1%
699 | );
700 | }
701 | #custom-mediaplayer.perc100-0 {
702 | background-image: linear-gradient(
703 | to right,
704 | @overlay0 100.0%,
705 | @surface0 100.1%
706 | );
707 | }
708 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | #custom-mediaplayer.perc0-0 {
2 | background-image: linear-gradient(
3 | to right,
4 | cyan 0.0%,
5 | gray 0.1%
6 | );
7 | }
8 | #custom-mediaplayer.perc1-0 {
9 | background-image: linear-gradient(
10 | to right,
11 | cyan 1.0%,
12 | gray 1.1%
13 | );
14 | }
15 | #custom-mediaplayer.perc2-0 {
16 | background-image: linear-gradient(
17 | to right,
18 | cyan 2.0%,
19 | gray 2.1%
20 | );
21 | }
22 | #custom-mediaplayer.perc3-0 {
23 | background-image: linear-gradient(
24 | to right,
25 | cyan 3.0%,
26 | gray 3.1%
27 | );
28 | }
29 | #custom-mediaplayer.perc4-0 {
30 | background-image: linear-gradient(
31 | to right,
32 | cyan 4.0%,
33 | gray 4.1%
34 | );
35 | }
36 | #custom-mediaplayer.perc5-0 {
37 | background-image: linear-gradient(
38 | to right,
39 | cyan 5.0%,
40 | gray 5.1%
41 | );
42 | }
43 | #custom-mediaplayer.perc6-0 {
44 | background-image: linear-gradient(
45 | to right,
46 | cyan 6.0%,
47 | gray 6.1%
48 | );
49 | }
50 | #custom-mediaplayer.perc7-0 {
51 | background-image: linear-gradient(
52 | to right,
53 | cyan 7.0%,
54 | gray 7.1%
55 | );
56 | }
57 | #custom-mediaplayer.perc8-0 {
58 | background-image: linear-gradient(
59 | to right,
60 | cyan 8.0%,
61 | gray 8.1%
62 | );
63 | }
64 | #custom-mediaplayer.perc9-0 {
65 | background-image: linear-gradient(
66 | to right,
67 | cyan 9.0%,
68 | gray 9.1%
69 | );
70 | }
71 | #custom-mediaplayer.perc10-0 {
72 | background-image: linear-gradient(
73 | to right,
74 | cyan 10.0%,
75 | gray 10.1%
76 | );
77 | }
78 | #custom-mediaplayer.perc11-0 {
79 | background-image: linear-gradient(
80 | to right,
81 | cyan 11.0%,
82 | gray 11.1%
83 | );
84 | }
85 | #custom-mediaplayer.perc12-0 {
86 | background-image: linear-gradient(
87 | to right,
88 | cyan 12.0%,
89 | gray 12.1%
90 | );
91 | }
92 | #custom-mediaplayer.perc13-0 {
93 | background-image: linear-gradient(
94 | to right,
95 | cyan 13.0%,
96 | gray 13.1%
97 | );
98 | }
99 | #custom-mediaplayer.perc14-0 {
100 | background-image: linear-gradient(
101 | to right,
102 | cyan 14.0%,
103 | gray 14.1%
104 | );
105 | }
106 | #custom-mediaplayer.perc15-0 {
107 | background-image: linear-gradient(
108 | to right,
109 | cyan 15.0%,
110 | gray 15.1%
111 | );
112 | }
113 | #custom-mediaplayer.perc16-0 {
114 | background-image: linear-gradient(
115 | to right,
116 | cyan 16.0%,
117 | gray 16.1%
118 | );
119 | }
120 | #custom-mediaplayer.perc17-0 {
121 | background-image: linear-gradient(
122 | to right,
123 | cyan 17.0%,
124 | gray 17.1%
125 | );
126 | }
127 | #custom-mediaplayer.perc18-0 {
128 | background-image: linear-gradient(
129 | to right,
130 | cyan 18.0%,
131 | gray 18.1%
132 | );
133 | }
134 | #custom-mediaplayer.perc19-0 {
135 | background-image: linear-gradient(
136 | to right,
137 | cyan 19.0%,
138 | gray 19.1%
139 | );
140 | }
141 | #custom-mediaplayer.perc20-0 {
142 | background-image: linear-gradient(
143 | to right,
144 | cyan 20.0%,
145 | gray 20.1%
146 | );
147 | }
148 | #custom-mediaplayer.perc21-0 {
149 | background-image: linear-gradient(
150 | to right,
151 | cyan 21.0%,
152 | gray 21.1%
153 | );
154 | }
155 | #custom-mediaplayer.perc22-0 {
156 | background-image: linear-gradient(
157 | to right,
158 | cyan 22.0%,
159 | gray 22.1%
160 | );
161 | }
162 | #custom-mediaplayer.perc23-0 {
163 | background-image: linear-gradient(
164 | to right,
165 | cyan 23.0%,
166 | gray 23.1%
167 | );
168 | }
169 | #custom-mediaplayer.perc24-0 {
170 | background-image: linear-gradient(
171 | to right,
172 | cyan 24.0%,
173 | gray 24.1%
174 | );
175 | }
176 | #custom-mediaplayer.perc25-0 {
177 | background-image: linear-gradient(
178 | to right,
179 | cyan 25.0%,
180 | gray 25.1%
181 | );
182 | }
183 | #custom-mediaplayer.perc26-0 {
184 | background-image: linear-gradient(
185 | to right,
186 | cyan 26.0%,
187 | gray 26.1%
188 | );
189 | }
190 | #custom-mediaplayer.perc27-0 {
191 | background-image: linear-gradient(
192 | to right,
193 | cyan 27.0%,
194 | gray 27.1%
195 | );
196 | }
197 | #custom-mediaplayer.perc28-0 {
198 | background-image: linear-gradient(
199 | to right,
200 | cyan 28.0%,
201 | gray 28.1%
202 | );
203 | }
204 | #custom-mediaplayer.perc29-0 {
205 | background-image: linear-gradient(
206 | to right,
207 | cyan 29.0%,
208 | gray 29.1%
209 | );
210 | }
211 | #custom-mediaplayer.perc30-0 {
212 | background-image: linear-gradient(
213 | to right,
214 | cyan 30.0%,
215 | gray 30.1%
216 | );
217 | }
218 | #custom-mediaplayer.perc31-0 {
219 | background-image: linear-gradient(
220 | to right,
221 | cyan 31.0%,
222 | gray 31.1%
223 | );
224 | }
225 | #custom-mediaplayer.perc32-0 {
226 | background-image: linear-gradient(
227 | to right,
228 | cyan 32.0%,
229 | gray 32.1%
230 | );
231 | }
232 | #custom-mediaplayer.perc33-0 {
233 | background-image: linear-gradient(
234 | to right,
235 | cyan 33.0%,
236 | gray 33.1%
237 | );
238 | }
239 | #custom-mediaplayer.perc34-0 {
240 | background-image: linear-gradient(
241 | to right,
242 | cyan 34.0%,
243 | gray 34.1%
244 | );
245 | }
246 | #custom-mediaplayer.perc35-0 {
247 | background-image: linear-gradient(
248 | to right,
249 | cyan 35.0%,
250 | gray 35.1%
251 | );
252 | }
253 | #custom-mediaplayer.perc36-0 {
254 | background-image: linear-gradient(
255 | to right,
256 | cyan 36.0%,
257 | gray 36.1%
258 | );
259 | }
260 | #custom-mediaplayer.perc37-0 {
261 | background-image: linear-gradient(
262 | to right,
263 | cyan 37.0%,
264 | gray 37.1%
265 | );
266 | }
267 | #custom-mediaplayer.perc38-0 {
268 | background-image: linear-gradient(
269 | to right,
270 | cyan 38.0%,
271 | gray 38.1%
272 | );
273 | }
274 | #custom-mediaplayer.perc39-0 {
275 | background-image: linear-gradient(
276 | to right,
277 | cyan 39.0%,
278 | gray 39.1%
279 | );
280 | }
281 | #custom-mediaplayer.perc40-0 {
282 | background-image: linear-gradient(
283 | to right,
284 | cyan 40.0%,
285 | gray 40.1%
286 | );
287 | }
288 | #custom-mediaplayer.perc41-0 {
289 | background-image: linear-gradient(
290 | to right,
291 | cyan 41.0%,
292 | gray 41.1%
293 | );
294 | }
295 | #custom-mediaplayer.perc42-0 {
296 | background-image: linear-gradient(
297 | to right,
298 | cyan 42.0%,
299 | gray 42.1%
300 | );
301 | }
302 | #custom-mediaplayer.perc43-0 {
303 | background-image: linear-gradient(
304 | to right,
305 | cyan 43.0%,
306 | gray 43.1%
307 | );
308 | }
309 | #custom-mediaplayer.perc44-0 {
310 | background-image: linear-gradient(
311 | to right,
312 | cyan 44.0%,
313 | gray 44.1%
314 | );
315 | }
316 | #custom-mediaplayer.perc45-0 {
317 | background-image: linear-gradient(
318 | to right,
319 | cyan 45.0%,
320 | gray 45.1%
321 | );
322 | }
323 | #custom-mediaplayer.perc46-0 {
324 | background-image: linear-gradient(
325 | to right,
326 | cyan 46.0%,
327 | gray 46.1%
328 | );
329 | }
330 | #custom-mediaplayer.perc47-0 {
331 | background-image: linear-gradient(
332 | to right,
333 | cyan 47.0%,
334 | gray 47.1%
335 | );
336 | }
337 | #custom-mediaplayer.perc48-0 {
338 | background-image: linear-gradient(
339 | to right,
340 | cyan 48.0%,
341 | gray 48.1%
342 | );
343 | }
344 | #custom-mediaplayer.perc49-0 {
345 | background-image: linear-gradient(
346 | to right,
347 | cyan 49.0%,
348 | gray 49.1%
349 | );
350 | }
351 | #custom-mediaplayer.perc50-0 {
352 | background-image: linear-gradient(
353 | to right,
354 | cyan 50.0%,
355 | gray 50.1%
356 | );
357 | }
358 | #custom-mediaplayer.perc51-0 {
359 | background-image: linear-gradient(
360 | to right,
361 | cyan 51.0%,
362 | gray 51.1%
363 | );
364 | }
365 | #custom-mediaplayer.perc52-0 {
366 | background-image: linear-gradient(
367 | to right,
368 | cyan 52.0%,
369 | gray 52.1%
370 | );
371 | }
372 | #custom-mediaplayer.perc53-0 {
373 | background-image: linear-gradient(
374 | to right,
375 | cyan 53.0%,
376 | gray 53.1%
377 | );
378 | }
379 | #custom-mediaplayer.perc54-0 {
380 | background-image: linear-gradient(
381 | to right,
382 | cyan 54.0%,
383 | gray 54.1%
384 | );
385 | }
386 | #custom-mediaplayer.perc55-0 {
387 | background-image: linear-gradient(
388 | to right,
389 | cyan 55.0%,
390 | gray 55.1%
391 | );
392 | }
393 | #custom-mediaplayer.perc56-0 {
394 | background-image: linear-gradient(
395 | to right,
396 | cyan 56.0%,
397 | gray 56.1%
398 | );
399 | }
400 | #custom-mediaplayer.perc57-0 {
401 | background-image: linear-gradient(
402 | to right,
403 | cyan 57.0%,
404 | gray 57.1%
405 | );
406 | }
407 | #custom-mediaplayer.perc58-0 {
408 | background-image: linear-gradient(
409 | to right,
410 | cyan 58.0%,
411 | gray 58.1%
412 | );
413 | }
414 | #custom-mediaplayer.perc59-0 {
415 | background-image: linear-gradient(
416 | to right,
417 | cyan 59.0%,
418 | gray 59.1%
419 | );
420 | }
421 | #custom-mediaplayer.perc60-0 {
422 | background-image: linear-gradient(
423 | to right,
424 | cyan 60.0%,
425 | gray 60.1%
426 | );
427 | }
428 | #custom-mediaplayer.perc61-0 {
429 | background-image: linear-gradient(
430 | to right,
431 | cyan 61.0%,
432 | gray 61.1%
433 | );
434 | }
435 | #custom-mediaplayer.perc62-0 {
436 | background-image: linear-gradient(
437 | to right,
438 | cyan 62.0%,
439 | gray 62.1%
440 | );
441 | }
442 | #custom-mediaplayer.perc63-0 {
443 | background-image: linear-gradient(
444 | to right,
445 | cyan 63.0%,
446 | gray 63.1%
447 | );
448 | }
449 | #custom-mediaplayer.perc64-0 {
450 | background-image: linear-gradient(
451 | to right,
452 | cyan 64.0%,
453 | gray 64.1%
454 | );
455 | }
456 | #custom-mediaplayer.perc65-0 {
457 | background-image: linear-gradient(
458 | to right,
459 | cyan 65.0%,
460 | gray 65.1%
461 | );
462 | }
463 | #custom-mediaplayer.perc66-0 {
464 | background-image: linear-gradient(
465 | to right,
466 | cyan 66.0%,
467 | gray 66.1%
468 | );
469 | }
470 | #custom-mediaplayer.perc67-0 {
471 | background-image: linear-gradient(
472 | to right,
473 | cyan 67.0%,
474 | gray 67.1%
475 | );
476 | }
477 | #custom-mediaplayer.perc68-0 {
478 | background-image: linear-gradient(
479 | to right,
480 | cyan 68.0%,
481 | gray 68.1%
482 | );
483 | }
484 | #custom-mediaplayer.perc69-0 {
485 | background-image: linear-gradient(
486 | to right,
487 | cyan 69.0%,
488 | gray 69.1%
489 | );
490 | }
491 | #custom-mediaplayer.perc70-0 {
492 | background-image: linear-gradient(
493 | to right,
494 | cyan 70.0%,
495 | gray 70.1%
496 | );
497 | }
498 | #custom-mediaplayer.perc71-0 {
499 | background-image: linear-gradient(
500 | to right,
501 | cyan 71.0%,
502 | gray 71.1%
503 | );
504 | }
505 | #custom-mediaplayer.perc72-0 {
506 | background-image: linear-gradient(
507 | to right,
508 | cyan 72.0%,
509 | gray 72.1%
510 | );
511 | }
512 | #custom-mediaplayer.perc73-0 {
513 | background-image: linear-gradient(
514 | to right,
515 | cyan 73.0%,
516 | gray 73.1%
517 | );
518 | }
519 | #custom-mediaplayer.perc74-0 {
520 | background-image: linear-gradient(
521 | to right,
522 | cyan 74.0%,
523 | gray 74.1%
524 | );
525 | }
526 | #custom-mediaplayer.perc75-0 {
527 | background-image: linear-gradient(
528 | to right,
529 | cyan 75.0%,
530 | gray 75.1%
531 | );
532 | }
533 | #custom-mediaplayer.perc76-0 {
534 | background-image: linear-gradient(
535 | to right,
536 | cyan 76.0%,
537 | gray 76.1%
538 | );
539 | }
540 | #custom-mediaplayer.perc77-0 {
541 | background-image: linear-gradient(
542 | to right,
543 | cyan 77.0%,
544 | gray 77.1%
545 | );
546 | }
547 | #custom-mediaplayer.perc78-0 {
548 | background-image: linear-gradient(
549 | to right,
550 | cyan 78.0%,
551 | gray 78.1%
552 | );
553 | }
554 | #custom-mediaplayer.perc79-0 {
555 | background-image: linear-gradient(
556 | to right,
557 | cyan 79.0%,
558 | gray 79.1%
559 | );
560 | }
561 | #custom-mediaplayer.perc80-0 {
562 | background-image: linear-gradient(
563 | to right,
564 | cyan 80.0%,
565 | gray 80.1%
566 | );
567 | }
568 | #custom-mediaplayer.perc81-0 {
569 | background-image: linear-gradient(
570 | to right,
571 | cyan 81.0%,
572 | gray 81.1%
573 | );
574 | }
575 | #custom-mediaplayer.perc82-0 {
576 | background-image: linear-gradient(
577 | to right,
578 | cyan 82.0%,
579 | gray 82.1%
580 | );
581 | }
582 | #custom-mediaplayer.perc83-0 {
583 | background-image: linear-gradient(
584 | to right,
585 | cyan 83.0%,
586 | gray 83.1%
587 | );
588 | }
589 | #custom-mediaplayer.perc84-0 {
590 | background-image: linear-gradient(
591 | to right,
592 | cyan 84.0%,
593 | gray 84.1%
594 | );
595 | }
596 | #custom-mediaplayer.perc85-0 {
597 | background-image: linear-gradient(
598 | to right,
599 | cyan 85.0%,
600 | gray 85.1%
601 | );
602 | }
603 | #custom-mediaplayer.perc86-0 {
604 | background-image: linear-gradient(
605 | to right,
606 | cyan 86.0%,
607 | gray 86.1%
608 | );
609 | }
610 | #custom-mediaplayer.perc87-0 {
611 | background-image: linear-gradient(
612 | to right,
613 | cyan 87.0%,
614 | gray 87.1%
615 | );
616 | }
617 | #custom-mediaplayer.perc88-0 {
618 | background-image: linear-gradient(
619 | to right,
620 | cyan 88.0%,
621 | gray 88.1%
622 | );
623 | }
624 | #custom-mediaplayer.perc89-0 {
625 | background-image: linear-gradient(
626 | to right,
627 | cyan 89.0%,
628 | gray 89.1%
629 | );
630 | }
631 | #custom-mediaplayer.perc90-0 {
632 | background-image: linear-gradient(
633 | to right,
634 | cyan 90.0%,
635 | gray 90.1%
636 | );
637 | }
638 | #custom-mediaplayer.perc91-0 {
639 | background-image: linear-gradient(
640 | to right,
641 | cyan 91.0%,
642 | gray 91.1%
643 | );
644 | }
645 | #custom-mediaplayer.perc92-0 {
646 | background-image: linear-gradient(
647 | to right,
648 | cyan 92.0%,
649 | gray 92.1%
650 | );
651 | }
652 | #custom-mediaplayer.perc93-0 {
653 | background-image: linear-gradient(
654 | to right,
655 | cyan 93.0%,
656 | gray 93.1%
657 | );
658 | }
659 | #custom-mediaplayer.perc94-0 {
660 | background-image: linear-gradient(
661 | to right,
662 | cyan 94.0%,
663 | gray 94.1%
664 | );
665 | }
666 | #custom-mediaplayer.perc95-0 {
667 | background-image: linear-gradient(
668 | to right,
669 | cyan 95.0%,
670 | gray 95.1%
671 | );
672 | }
673 | #custom-mediaplayer.perc96-0 {
674 | background-image: linear-gradient(
675 | to right,
676 | cyan 96.0%,
677 | gray 96.1%
678 | );
679 | }
680 | #custom-mediaplayer.perc97-0 {
681 | background-image: linear-gradient(
682 | to right,
683 | cyan 97.0%,
684 | gray 97.1%
685 | );
686 | }
687 | #custom-mediaplayer.perc98-0 {
688 | background-image: linear-gradient(
689 | to right,
690 | cyan 98.0%,
691 | gray 98.1%
692 | );
693 | }
694 | #custom-mediaplayer.perc99-0 {
695 | background-image: linear-gradient(
696 | to right,
697 | cyan 99.0%,
698 | gray 99.1%
699 | );
700 | }
701 | #custom-mediaplayer.perc100-0 {
702 | background-image: linear-gradient(
703 | to right,
704 | cyan 100.0%,
705 | gray 100.1%
706 | );
707 | }
708 |
--------------------------------------------------------------------------------