diff --git a/v6d3music/app.py b/v6d3music/app.py index 13af0d7..a76dcb4 100644 --- a/v6d3music/app.py +++ b/v6d3music/app.py @@ -223,7 +223,8 @@ class MusicAppFactory(AppFactory): @routes.get('/main.css') async def maincss(_request: web.Request) -> web.Response: return web.Response( - text=await self.file('main.css') + text=await self.file('main.css'), + content_type='text/css' ) @routes.post('/api/') diff --git a/v6d3music/commands.py b/v6d3music/commands.py index 5924956..63e9ad9 100644 --- a/v6d3music/commands.py +++ b/v6d3music/commands.py @@ -144,6 +144,53 @@ async def default(ctx: Context, args: list[str]) -> None: await ctx.reply(f'effects set to `{effects}`') +@at('commands', 'repeat') +async def repeat(ctx: Context, args: list[str]): + match args: + case []: + n = 1 + case [n_] if n_.isdecimal(): + n = int(n_) + case _: + raise Explicit('misformatted') + assert_admin(ctx.member) + queue = await queue_for(ctx, create=False, force_play=False) + if not queue.queue: + raise Explicit('empty queue') + if n > 99: + raise Explicit('too long') + audio = queue.queue[0] + for _ in range(n): + queue.queue.insert(1, audio.copy()) + + +@at('commands', 'branch') +async def branch(ctx: Context, args: list[str]): + match args: + case ['-', effects]: + pass + case ['+', preset]: + effects = effects_for_preset(preset) + case ['none']: + effects = '' + case []: + effects = None + case _: + raise Explicit('misformatted') + assert_admin(ctx.member) + queue = await queue_for(ctx, create=False, force_play=False) + if not queue.queue: + raise Explicit('empty queue') + audio = queue.queue[0].branch() + if effects is not None: + seconds = audio.source_seconds() + audio.options = options_for_effects(effects or None) + audio.set_seconds(seconds) + else: + audio.set_source() + queue.queue.insert(1, audio) + + @at('commands', '//') @at('commands', 'queue') async def queue_(ctx: Context, args: list[str]) -> None: diff --git a/v6d3music/core/queueaudio.py b/v6d3music/core/queueaudio.py index cd4113a..e2e3b54 100644 --- a/v6d3music/core/queueaudio.py +++ b/v6d3music/core/queueaudio.py @@ -45,7 +45,7 @@ class QueueAudio(discord.AudioSource): @classmethod async def create(cls, guild: discord.Guild): - return cls(guild, await QueueAudio.respawned(guild)) + return cls(guild, await cls.respawned(guild)) async def save(self, delay: bool): hybernated = [] diff --git a/v6d3music/core/ytaudio.py b/v6d3music/core/ytaudio.py index c86173b..0514139 100644 --- a/v6d3music/core/ytaudio.py +++ b/v6d3music/core/ytaudio.py @@ -1,9 +1,10 @@ import asyncio +import functools import random from typing import Optional import discord -from v6d2ctx.context import Benchmark +from v6d2ctx.context import Explicit from v6d3music.core.ffmpegnormalaudio import FFmpegNormalAudio from v6d3music.core.real_url import real_url @@ -12,6 +13,9 @@ from v6d3music.utils.sparq import sparq from v6d3music.utils.tor_prefix import tor_prefix +__all__ = ('YTAudio',) + + class YTAudio(discord.AudioSource): source: FFmpegNormalAudio @@ -24,6 +28,9 @@ class YTAudio(discord.AudioSource): rby: discord.Member, already_read: int, tor: bool, + /, + *, + stop_at: int | None = None ): self.url = url self.origin = origin @@ -36,6 +43,7 @@ class YTAudio(discord.AudioSource): # self.set_source() self._durations: dict[str, str] = {} self.loop = asyncio.get_running_loop() + self.stop_at: int | None = stop_at def set_source_if_necessary(self): if not hasattr(self, 'source'): @@ -117,6 +125,8 @@ class YTAudio(discord.AudioSource): def read(self) -> bytes: if self.regenerating: return FILL + if self.stop_at is not None and self.already_read >= self.stop_at - 1: + return b'' self.already_read += 1 ret: bytes = self.source.read() if not ret and not self.source.droppable(): @@ -156,6 +166,7 @@ class YTAudio(discord.AudioSource): 'rby': self.rby.id, 'already_read': self.already_read, 'tor': self.tor, + 'stop_at': self.stop_at, } @classmethod @@ -167,7 +178,8 @@ class YTAudio(discord.AudioSource): respawn['options'], guild.get_member(respawn['rby']) or await guild.fetch_member(respawn['rby']), respawn['already_read'], - respawn.get('tor', False) + respawn.get('tor', False), + stop_at=respawn.get('stop_at', None), ) async def regenerate(self): @@ -189,3 +201,29 @@ class YTAudio(discord.AudioSource): 'description': self.description, 'canbeskipped': self.can_be_skipped_by(member), } + + def copy(self) -> 'YTAudio': + return YTAudio( + self.url, + self.origin, + self.description, + self.options, + self.rby, + 0, + self.tor, + ) + + def branch(self) -> 'YTAudio': + if self.stop_at is not None: + raise Explicit('already branched') + self.stop_at = stop_at = self.already_read + 50 + audio = YTAudio( + self.url, + self.origin, + self.description, + self.options, + self.rby, + stop_at, + self.tor, + ) + return audio diff --git a/v6d3music/html/home.html b/v6d3music/html/home.html index 5b19734..d45a117 100644 --- a/v6d3music/html/home.html +++ b/v6d3music/html/home.html @@ -1,8 +1,13 @@ - -
- - + + + + + + + + + diff --git a/v6d3music/html/main.css b/v6d3music/html/main.css index 4b88557..a96907e 100644 --- a/v6d3music/html/main.css +++ b/v6d3music/html/main.css @@ -1,5 +1,23 @@ -html, body { - color: white; - background: black; - margin: 0; +html, +body, +input { + color: white; + background: black; + margin: 0; +} + +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: #111; +} + +::-webkit-scrollbar-thumb { + background: #444; +} + +::-webkit-scrollbar-thumb:hover { + background: #555; } diff --git a/v6d3music/html/main.js b/v6d3music/html/main.js index 88d8e7c..ce837b3 100644 --- a/v6d3music/html/main.js +++ b/v6d3music/html/main.js @@ -147,38 +147,40 @@ const aQueue = async () => { const sleep = (s) => { return new Promise((resolve) => setTimeout(resolve, 1000 * s)); }; -const aUpdateAudioSchedule = async (timecode, audio, i, current_i) => { - while (i == current_i()) { - timecode.innerText = audio.ts(); - await sleep(0.5); - } -}; -const audioWidget = (audio, i, current_i) => { +const audioWidget = (audio) => { const description = baseEl("span", audio.description); const timecode = baseEl("span", audio.timecode); const duration = baseEl("span", audio.duration); - aUpdateAudioSchedule(timecode, audio, i, current_i); + audio.tce = timecode; return baseEl("div", "audio", " ", timecode, "/", duration, " ", description); }; -const aUpdateQueueOnce = async (queue, el, i, current_i) => { - console.log(queue); - console.log(JSON.stringify(queue)); +const aUpdateQueueOnce = async (queue, el) => { el.innerHTML = ""; if (queue !== null) { for (const audio of queue.queuejson) { - el.append(audioWidget(audio, i, current_i)); + el.append(audioWidget(audio)); } } }; const aUpdateQueueSetup = async (el) => { - let i = 0; - await aUpdateQueueOnce(await aQueue(), el, i, () => i); + let queue = await aQueue(); + await aUpdateQueueOnce(queue, el); (async () => { while (true) { - const queue = await aQueue(); - i += 1; - await aUpdateQueueOnce(queue, el, i, () => i); await sleep(2); + const newQueue = await aQueue(); + await aUpdateQueueOnce(newQueue, el); + queue = newQueue; + } + })(); + (async () => { + while (true) { + await sleep(.25); + if (queue !== null) { + for (const audio of queue.queuejson) { + audio.tce.innerText = audio.ts(); + } + } } })(); };