bundle.discord.cogs.music ========================= .. py:module:: bundle.discord.cogs.music .. autoapi-nested-parse:: Music playback cog -- queue-based YouTube streaming with interactive controls. Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/bundle/discord/cogs/music/controls/index /autoapi/bundle/discord/cogs/music/embed/index /autoapi/bundle/discord/cogs/music/player/index /autoapi/bundle/discord/cogs/music/queue/index /autoapi/bundle/discord/cogs/music/source/index Attributes ---------- .. autoapisummary:: bundle.discord.cogs.music.log bundle.discord.cogs.music.ALONE_TIMEOUT bundle.discord.cogs.music.PAUSE_TIMEOUT Classes ------- .. autoapisummary:: bundle.discord.cogs.music.YoutubeResolveOptions bundle.discord.cogs.music.PlayerControls bundle.discord.cogs.music.QueuePaginator bundle.discord.cogs.music.PlayerEmbed bundle.discord.cogs.music.GuildPlayer bundle.discord.cogs.music.TrackQueue bundle.discord.cogs.music.GuildSession bundle.discord.cogs.music.MusicCog Functions --------- .. autoapisummary:: bundle.discord.cogs.music.vc_status Package Contents ---------------- .. py:class:: YoutubeResolveOptions(/, **data: Any) Bases: :py:obj:`bundle.core.data.Data` Base data model class, providing utilities for serialization and deserialization from/to JSON, along with JSON Schema generation. .. attribute:: model_config Default model configuration settings. .. py:attribute:: select_video_itag :type: int | None :value: None .. py:attribute:: select_audio_itag :type: int | None :value: None .. py:attribute:: best :type: bool :value: True .. py:class:: PlayerControls(cog: bundle.discord.cogs.music.MusicCog, guild_id: int) Bases: :py:obj:`discord.ui.View` Six-button control strip attached to the now-playing embed: prev, pause, skip, stop, shuffle, queue. .. py:attribute:: cog .. py:attribute:: guild_id .. py:method:: btn_prev(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:method:: btn_pause(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:method:: btn_skip(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:method:: btn_stop(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:method:: btn_shuffle(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:method:: btn_queue(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:class:: QueuePaginator(embed_mgr: bundle.discord.cogs.music.embed.PlayerEmbed, *, timeout: float = 120) Bases: :py:obj:`discord.ui.View` Paginated queue browser with prev/next buttons. .. py:method:: btn_prev_page(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:method:: btn_next_page(interaction: discord.Interaction, button: discord.ui.Button) -> None :async: .. py:class:: PlayerEmbed(embeds: bundle.discord.embeds.EmbedFactory, queue: bundle.discord.cogs.music.queue.TrackQueue, player: bundle.discord.cogs.music.player.GuildPlayer, text_channel: discord.TextChannel) Manages the persistent now-playing message and seek-bar loop for one guild. .. py:attribute:: text_channel .. py:attribute:: msg :type: discord.Message | None :value: None .. py:attribute:: view :type: discord.ui.View | None :value: None .. py:method:: now_playing(track: bundle.youtube.track.YoutubeTrackData, status: str) -> discord.Embed .. py:method:: queue_page_count(per_page: int = 15) -> int .. py:method:: queue_embed(*, page: int = 0, per_page: int = 15) -> discord.Embed .. py:method:: refresh(*, status: str) -> None :async: Update the persistent message with current track state. .. py:method:: send_or_update(embed: discord.Embed, view: discord.ui.View | None = None) -> None :async: Send a new message or edit the existing one. .. py:method:: disable_view() -> None Disable all buttons and stop the view. .. py:method:: delete() -> None :async: Delete the persistent message and clean up the view. .. py:method:: show_error(description: str) -> None :async: Update the persistent message with an error embed. .. py:method:: start_seek_loop(guild: discord.Guild) -> None .. py:method:: cancel_seek_loop() -> None .. py:function:: vc_status(vc: discord.VoiceProtocol | None) -> str Return 'Paused' or 'Playing' based on voice client state. .. py:class:: GuildPlayer(on_track_end: collections.abc.Callable[[int, Exception | None], collections.abc.Coroutine[Any, Any, None]]) Owns voice playback and timing for a single guild. .. py:attribute:: play_started_at :type: float :value: 0.0 .. py:attribute:: pause_offset :type: float :value: 0.0 .. py:attribute:: paused_at :type: float | None :value: None .. py:method:: elapsed_secs() -> int .. py:method:: play(vc: discord.VoiceClient, track: bundle.youtube.track.YoutubeTrackData, guild_id: int) -> bool Start FFmpeg playback. Returns False if no stream URL available. .. py:method:: pause(vc: discord.VoiceClient) -> bool Pause playback. Returns True if state changed. .. py:method:: resume(vc: discord.VoiceClient) -> bool Resume playback. Returns True if state changed. .. py:method:: stop(vc: discord.VoiceClient) -> None Stop current source, suppress the after callback. .. py:class:: TrackQueue Ordered track list with a cursor. .. py:attribute:: resolving :type: bool :value: False .. py:attribute:: waiting :type: bool :value: False .. py:property:: tracks :type: list[bundle.youtube.track.YoutubeTrackData] .. py:property:: index :type: int .. py:property:: current :type: bundle.youtube.track.YoutubeTrackData | None .. py:property:: has_next :type: bool .. py:property:: has_prev :type: bool .. py:method:: enqueue(track: bundle.youtube.track.YoutubeTrackData) -> int Append a track; return its index. .. py:method:: advance(delta: int = 1) -> bool Move cursor by *delta*. Return True if the new position is valid. .. py:method:: shuffle() -> None Shuffle remaining tracks after the current cursor position. .. py:method:: pos_str() -> str .. py:data:: log .. py:data:: ALONE_TIMEOUT :value: 30 .. py:data:: PAUSE_TIMEOUT :value: 300 .. py:class:: GuildSession Bundles the per-guild components. .. py:attribute:: queue :type: queue.TrackQueue .. py:attribute:: player :type: player.GuildPlayer .. py:attribute:: embed :type: embed.PlayerEmbed .. py:attribute:: resolve_tasks :type: list[asyncio.Task] :value: [] .. py:attribute:: alone_task :type: asyncio.Task | None :value: None .. py:attribute:: pause_task :type: asyncio.Task | None :value: None .. py:class:: MusicCog(bot: bundle.discord.bot.Bot) Bases: :py:obj:`discord.ext.commands.Cog` Queue-based YouTube audio streaming. .. py:attribute:: bot .. py:method:: on_voice_state_update(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState) -> None :async: .. py:method:: play(ctx: discord.ext.commands.Context, url: str) -> None :async: Add a YouTube URL to the queue and start playing. .. py:method:: skip(ctx: discord.ext.commands.Context) -> None :async: Skip to the next track in the queue. .. py:method:: prev(ctx: discord.ext.commands.Context) -> None :async: Go back to the previous track. .. py:method:: show_queue(ctx: discord.ext.commands.Context) -> None :async: Display the current queue. .. py:method:: stop(ctx: discord.ext.commands.Context) -> None :async: Stop playback, clear the queue, and disconnect. .. py:method:: pause(ctx: discord.ext.commands.Context) -> None :async: Pause playback. .. py:method:: shuffle(ctx: discord.ext.commands.Context) -> None :async: Shuffle the remaining tracks in the queue. .. py:method:: resume(ctx: discord.ext.commands.Context) -> None :async: Resume paused playback.