Клиент видит, что играет у тебя на компьютере: название, исполнителя, обложку, время и текст песни. Это та же музыка, которую показывает Dynamic Island, и скрипту она доступна целиком через глобал music (он же client.music).
Клиент читает системную сессию проигрывателя, поэтому источник может быть любым: Spotify, Яндекс Музыка, браузер с YouTube. Если ничего не играет, music.current() вернёт None, и проверять это надо всегда: музыку игрок может закрыть в любой момент.
@command("np")
def now_playing(ctx):
"""Что сейчас играет"""
track = music.current()
if not track:
ctx.error("Ничего не играет")
return
ctx.msg("%s : %s" % (track["artist"], track["title"]))
ctx.msg("%.0f из %d секунд" % (track["position"], track["duration"]))music.current() отдаёт обычный словарь Python со всем сразу.
| Ключ | Что внутри |
|---|---|
title / artist | Название трека и исполнитель |
owner | Программа, которая играет: по ней видно, Spotify это или браузер |
playing | True если играет, False если на паузе |
position / position_ms | Сколько уже отыграно: в секундах и в миллисекундах |
duration / duration_ms | Полная длина трека |
progress | Доля от 0 до 1: удобно для полоски |
bpm | Темп трека, если клиент успел его посчитать, иначе 0 |
color | Цвет обложки: готовый Color, им красят полоски и подписи |
artwork | Обложка как картинка: её рисуют так же, как ассеты из assets.image |
lyrics_synced | True если у песни есть текст с таймкодами |
has_lyrics | True если текст вообще нашёлся, пусть и без таймкодов |
То же самое можно спрашивать по отдельности, и это дешевле, если нужна одна цифра: music.playing(), music.position(), music.progress(), music.color(), music.artwork().
el = Hud("Сейчас играет", width=180, height=46)
@el.render
def draw(d):
d.rounded_rect(0, 0, d.w, d.h, 8, Color(0, 0, 0, 150))
track = music.current()
if not track:
d.text("тишина", 10, 18, size=8, color=Color(150, 150, 160))
return
accent = track["color"] or Color(255, 255, 255)
if track["artwork"]:
# у обложки свой метод: rounded_rect и text рисуют от угла элемента, а raw от угла экрана
d.raw().drawRoundedTexture(track["artwork"], d.x + 7, d.y + 7, 32, 32, border(6))
d.text(track["title"], 46, 11, size=8, weight="semibold")
d.text(track["artist"], 46, 23, size=7, color=Color(160, 165, 180))
d.rect(46, 35, d.w - 56, 3, Color(70, 72, 82))
d.rect(46, 35, (d.w - 56) * track["progress"], 3, accent)Клиент ищет текст с таймкодами, то есть знает, какая строка звучит в какую секунду. Строку, которая идёт прямо сейчас, отдаёт music.current_lyric(). Если текста нет или он не синхронизирован, вернётся None.
| Метод | Что возвращает |
|---|---|
music.lyrics_synced() | Есть ли у песни текст с таймкодами |
music.lyrics() | Весь текст списком строк |
music.current_lyric() | Строка, которая звучит сейчас |
music.lyric_at(time_ms) | Строка на заданное время в миллисекундах (то же самое: line_at, at) |
Строка это словарь, и у текущей строки полей больше, чем у остальных.
| Ключ | Что внутри |
|---|---|
text | Сама строка |
time_ms | Когда она начинается |
cues | Разметка по словам: time_ms и char_index, по ней подсвечивают текст по мере пения |
index | Номер строки в песне: по нему видно, что строка сменилась |
progress | Насколько строка прожита, от 0 до 1 |
singing_progress | То же, но по разметке слов: точнее для подсветки |
active | True пока строка звучит, False в паузе между строками |
Таймкоды везде в миллисекундах. Пока клиент ещё ищет текст, music.lyrics() вернёт пустой список, а если источник отдал обычный текст без таймкодов, у строк будет time_ms = -1 и music.lyric_at(...) вернёт None. Разметка cues нужна для караоке: в ней время каждого куска строки и номер символа, с которого он начинается.
Строку удобно ловить по её номеру: пока index тот же, ничего не поменялось, а как только он вырос, началась следующая строка. Вот та же строка, но не на экране, а в самом мире, через render3d.text.
mod = Module("Lyrics", "Visuals")
state = {"index": -1, "text": "", "x": 0.0, "y": 0.0, "z": 0.0}
@events.tick
def watch(event):
if not mod.isEnabled() or not world.ingame():
return
line = music.current_lyric()
if not line or int(line["index"]) == state["index"]:
return
me = world.self()
state["index"] = int(line["index"])
state["text"] = line["text"]
state["x"] = me.getX()
state["y"] = me.getY() + 2.6
state["z"] = me.getZ()
@events.render_3d
def draw(event):
if not mod.isEnabled() or not state["text"]:
return
render3d.text(event, state["text"], [state["x"], state["y"], state["z"]], 0.3,
music.color() or Color(255, 255, 255),
outline=Color(0, 0, 0, 200))Скрипт может нажимать те же кнопки, что и сам плеер. Каждый метод возвращает True, если команда ушла, и False, если играть нечем или проигрыватель её не принял.
| Метод | Что делает |
|---|---|
music.play() / music.pause() | Включить или поставить на паузу |
music.toggle() | Пауза, если играло, и наоборот |
music.next() / music.previous() | Следующий и предыдущий трек |
music.stop() | Остановить проигрывание |
mod = Module("MusicKeys", "Other")
k_toggle = Bind(mod, "Пауза").set("NUMPAD5")
k_next = Bind(mod, "Дальше").set("NUMPAD6")
@events.key
def on_key(event):
if not mod.isEnabled() or not event.isPress():
return
if k_toggle.isKey(event.getKey()):
music.toggle()
elif k_next.isKey(event.getKey()):
music.next()| Метод | Что возвращает |
|---|---|
music.active() | Играет ли вообще что-нибудь на компьютере |
music.current() | Словарь с треком целиком или None |
music.playing() | True если сейчас звучит, False на паузе |
music.position() | Отыграно секунд |
music.position_ms() | Отыграно миллисекунд |
music.duration() | Длина трека в секундах |
music.duration_ms() | Длина трека в миллисекундах |
music.progress() | Доля от 0 до 1 |
music.bpm() | Темп трека или 0 |
music.color() | Цвет обложки или None |
music.artwork() | Картинка обложки или None |
music.lyrics_synced() | Есть ли текст с таймкодами |
music.lyrics() | Весь текст песни списком |
music.current_lyric() | Текущая строка или None |
music.lyric_at(time_ms) | Строка на заданное время |
music.play() / pause() / toggle() / next() / previous() / stop() | Управление, True если команда ушла |