0
# Queue System
1
2
Advanced queue management with multiple modes, history tracking, AutoPlay functionality, and thread-safe operations for robust track management. The queue system provides flexible track ordering, looping modes, and automatic track recommendations for continuous playback.
3
4
## Capabilities
5
6
### Queue Management
7
8
Thread-safe queue operations for managing track playback order with support for atomic operations and concurrent access.
9
10
```python { .api }
11
class Queue:
12
def __init__(self, history: Queue | None = None):
13
"""
14
Initialize a new queue.
15
16
Parameters:
17
- history: Optional history queue for storing played tracks
18
"""
19
20
@property
21
def mode(self) -> QueueMode:
22
"""Current queue mode (normal, loop, loop_all)."""
23
24
@mode.setter
25
def mode(self, value: QueueMode) -> None:
26
"""Set the queue mode."""
27
28
@property
29
def history(self) -> Queue | None:
30
"""History queue containing previously played tracks."""
31
32
@property
33
def count(self) -> int:
34
"""Number of tracks currently in the queue."""
35
36
@property
37
def is_empty(self) -> bool:
38
"""Whether the queue is empty."""
39
40
@property
41
def loaded(self) -> Playable | None:
42
"""Pre-loaded next track for smooth transitions."""
43
44
def put(
45
self,
46
item: list[Playable] | Playable | Playlist,
47
*,
48
atomic: bool = True
49
) -> int:
50
"""
51
Add tracks to the queue.
52
53
Parameters:
54
- item: Track(s) or playlist to add
55
- atomic: Whether to add all tracks atomically
56
57
Returns:
58
int: Number of tracks added
59
"""
60
61
async def put_wait(
62
self,
63
item: list[Playable] | Playable | Playlist,
64
*,
65
atomic: bool = True
66
) -> int:
67
"""
68
Add tracks to the queue (async version).
69
70
Parameters:
71
- item: Track(s) or playlist to add
72
- atomic: Whether to add all tracks atomically
73
74
Returns:
75
int: Number of tracks added
76
"""
77
78
def get(self) -> Playable:
79
"""
80
Get the next track from the queue.
81
82
Returns:
83
Playable: Next track to play
84
85
Raises:
86
QueueEmpty: If queue is empty
87
"""
88
89
async def get_wait(self) -> Playable:
90
"""
91
Get the next track from the queue (async, waits if empty).
92
93
Returns:
94
Playable: Next track to play
95
"""
96
97
def get_at(self, index: int) -> Playable:
98
"""
99
Get a track at a specific index without removing it.
100
101
Parameters:
102
- index: Index of the track
103
104
Returns:
105
Playable: Track at the specified index
106
107
Raises:
108
IndexError: If index is out of range
109
"""
110
111
def put_at(self, index: int, value: Playable) -> None:
112
"""
113
Insert a track at a specific index.
114
115
Parameters:
116
- index: Index to insert at
117
- value: Track to insert
118
119
Raises:
120
IndexError: If index is out of range
121
"""
122
```
123
124
### Queue Operations
125
126
Methods for manipulating queue contents including removal, reordering, and searching.
127
128
```python { .api }
129
class Queue:
130
def delete(self, index: int) -> None:
131
"""
132
Remove a track at a specific index.
133
134
Parameters:
135
- index: Index of track to remove
136
137
Raises:
138
IndexError: If index is out of range
139
"""
140
141
def peek(self, index: int = 0) -> Playable:
142
"""
143
View a track without removing it from the queue.
144
145
Parameters:
146
- index: Index of track to peek at (default: next track)
147
148
Returns:
149
Playable: Track at the specified index
150
151
Raises:
152
IndexError: If index is out of range
153
"""
154
155
def swap(self, first: int, second: int) -> None:
156
"""
157
Swap two tracks in the queue.
158
159
Parameters:
160
- first: Index of first track
161
- second: Index of second track
162
163
Raises:
164
IndexError: If either index is out of range
165
"""
166
167
def index(self, item: Playable) -> int:
168
"""
169
Find the index of a track in the queue.
170
171
Parameters:
172
- item: Track to find
173
174
Returns:
175
int: Index of the track
176
177
Raises:
178
ValueError: If track is not in queue
179
"""
180
181
def shuffle(self) -> None:
182
"""Randomly shuffle all tracks in the queue."""
183
184
def clear(self) -> None:
185
"""Remove all tracks from the queue."""
186
187
def copy(self) -> Queue:
188
"""
189
Create a copy of the queue.
190
191
Returns:
192
Queue: New queue with same tracks and settings
193
"""
194
195
def reset(self) -> None:
196
"""Reset the queue to initial state, clearing all tracks and history."""
197
198
def remove(self, item: Playable, /, count: int | None = 1) -> int:
199
"""
200
Remove specific track(s) from the queue.
201
202
Parameters:
203
- item: Track to remove
204
- count: Number of instances to remove (None for all)
205
206
Returns:
207
int: Number of tracks removed
208
"""
209
210
# Container protocol methods
211
def __len__(self) -> int:
212
"""Return the number of tracks in the queue."""
213
214
def __getitem__(self, index: int | slice) -> Playable | list[Playable]:
215
"""Get track(s) by index or slice."""
216
217
def __setitem__(self, index: int, value: Playable) -> None:
218
"""Set track at index."""
219
220
def __delitem__(self, index: int) -> None:
221
"""Delete track at index."""
222
223
def __iter__(self) -> Iterator[Playable]:
224
"""Iterate over tracks in the queue."""
225
226
def __bool__(self) -> bool:
227
"""Return True if queue has tracks."""
228
```
229
230
### Queue Modes
231
232
Enumeration of queue behavior modes for different playback patterns.
233
234
```python { .api }
235
class QueueMode(enum.Enum):
236
"""Queue behavior modes."""
237
normal = 0 # No looping, play through queue once
238
loop = 1 # Loop the current track continuously
239
loop_all = 2 # Loop through entire queue continuously
240
```
241
242
### AutoPlay System
243
244
AutoPlay functionality for continuous music playback with track recommendations.
245
246
```python { .api }
247
class AutoPlayMode(enum.Enum):
248
"""AutoPlay functionality modes."""
249
enabled = 0 # Fully autonomous with track recommendations
250
partial = 1 # Autonomous but no automatic recommendations
251
disabled = 2 # No automatic functionality
252
253
# AutoPlay is integrated into Player class
254
class Player:
255
@property
256
def autoplay(self) -> AutoPlayMode:
257
"""Current AutoPlay mode."""
258
259
@autoplay.setter
260
def autoplay(self, value: AutoPlayMode) -> None:
261
"""Set AutoPlay mode."""
262
263
auto_queue: Queue # Separate queue for AutoPlay recommendations
264
```
265
266
## Usage Examples
267
268
### Basic Queue Operations
269
270
```python
271
import wavelink
272
273
@bot.command()
274
async def queue_example(ctx):
275
"""Demonstrate basic queue operations."""
276
player = ctx.voice_client
277
if not player:
278
return await ctx.send("Not connected to a voice channel!")
279
280
# Add tracks to queue
281
tracks = await wavelink.Pool.fetch_tracks("rock music")
282
if tracks:
283
added = player.queue.put(tracks[:5]) # Add first 5 tracks
284
await ctx.send(f"Added {added} tracks to queue")
285
286
# Check queue status
287
await ctx.send(f"Queue has {player.queue.count} tracks")
288
await ctx.send(f"Queue is empty: {player.queue.is_empty}")
289
290
# Peek at next track
291
if not player.queue.is_empty:
292
next_track = player.queue.peek()
293
await ctx.send(f"Next track: {next_track.title}")
294
295
@bot.command()
296
async def show_queue(ctx, page: int = 1):
297
"""Display the current queue with pagination."""
298
player = ctx.voice_client
299
if not player:
300
return await ctx.send("Not connected to a voice channel!")
301
302
if player.queue.is_empty:
303
return await ctx.send("Queue is empty!")
304
305
# Pagination
306
per_page = 10
307
start_idx = (page - 1) * per_page
308
end_idx = start_idx + per_page
309
310
queue_tracks = list(player.queue)[start_idx:end_idx]
311
312
embed = discord.Embed(
313
title=f"Queue (Page {page})",
314
description=f"Total tracks: {player.queue.count}",
315
color=discord.Color.blue()
316
)
317
318
track_list = []
319
for i, track in enumerate(queue_tracks, start_idx + 1):
320
duration = f"{track.length // 60000}:{(track.length // 1000) % 60:02d}"
321
track_list.append(f"{i}. {track.title} - {track.author} ({duration})")
322
323
embed.add_field(
324
name="Tracks",
325
value="\n".join(track_list) if track_list else "No tracks on this page",
326
inline=False
327
)
328
329
# Add current track info
330
if player.current:
331
embed.add_field(
332
name="Now Playing",
333
value=f"{player.current.title} - {player.current.author}",
334
inline=False
335
)
336
337
await ctx.send(embed=embed)
338
```
339
340
### Queue Manipulation
341
342
```python
343
@bot.command()
344
async def remove_track(ctx, index: int):
345
"""Remove a track from the queue by index."""
346
player = ctx.voice_client
347
if not player:
348
return await ctx.send("Not connected to a voice channel!")
349
350
if player.queue.is_empty:
351
return await ctx.send("Queue is empty!")
352
353
try:
354
# Convert to 0-based index
355
track_index = index - 1
356
357
# Get track info before removing
358
track = player.queue.get_at(track_index)
359
360
# Remove the track
361
player.queue.delete(track_index)
362
363
await ctx.send(f"Removed: {track.title}")
364
365
except IndexError:
366
await ctx.send(f"Invalid index! Queue has {player.queue.count} tracks.")
367
368
@bot.command()
369
async def move_track(ctx, from_pos: int, to_pos: int):
370
"""Move a track from one position to another."""
371
player = ctx.voice_client
372
if not player:
373
return await ctx.send("Not connected to a voice channel!")
374
375
try:
376
# Convert to 0-based indices
377
from_idx = from_pos - 1
378
to_idx = to_pos - 1
379
380
# Get track info
381
track = player.queue.get_at(from_idx)
382
383
# Remove from old position
384
player.queue.delete(from_idx)
385
386
# Adjust target index if it's after the removed track
387
if to_idx > from_idx:
388
to_idx -= 1
389
390
# Insert at new position
391
player.queue.put_at(to_idx, track)
392
393
await ctx.send(f"Moved '{track.title}' from position {from_pos} to {to_pos}")
394
395
except IndexError:
396
await ctx.send("Invalid position! Check queue size.")
397
398
@bot.command()
399
async def swap_tracks(ctx, pos1: int, pos2: int):
400
"""Swap two tracks in the queue."""
401
player = ctx.voice_client
402
if not player:
403
return await ctx.send("Not connected to a voice channel!")
404
405
try:
406
# Convert to 0-based indices
407
idx1 = pos1 - 1
408
idx2 = pos2 - 1
409
410
# Get track info
411
track1 = player.queue.get_at(idx1)
412
track2 = player.queue.get_at(idx2)
413
414
# Swap tracks
415
player.queue.swap(idx1, idx2)
416
417
await ctx.send(f"Swapped '{track1.title}' and '{track2.title}'")
418
419
except IndexError:
420
await ctx.send("Invalid positions! Check queue size.")
421
422
@bot.command()
423
async def shuffle_queue(ctx):
424
"""Shuffle the queue randomly."""
425
player = ctx.voice_client
426
if not player:
427
return await ctx.send("Not connected to a voice channel!")
428
429
if player.queue.count < 2:
430
return await ctx.send("Need at least 2 tracks to shuffle!")
431
432
player.queue.shuffle()
433
await ctx.send(f"Shuffled {player.queue.count} tracks!")
434
435
@bot.command()
436
async def clear_queue(ctx):
437
"""Clear all tracks from the queue."""
438
player = ctx.voice_client
439
if not player:
440
return await ctx.send("Not connected to a voice channel!")
441
442
if player.queue.is_empty:
443
return await ctx.send("Queue is already empty!")
444
445
count = player.queue.count
446
player.queue.clear()
447
await ctx.send(f"Cleared {count} tracks from the queue!")
448
```
449
450
### Queue Modes
451
452
```python
453
@bot.command()
454
async def loop_mode(ctx, mode: str = None):
455
"""Set or check queue loop mode."""
456
player = ctx.voice_client
457
if not player:
458
return await ctx.send("Not connected to a voice channel!")
459
460
if mode is None:
461
current_mode = player.queue.mode.name
462
await ctx.send(f"Current loop mode: {current_mode}")
463
return
464
465
mode_map = {
466
'off': wavelink.QueueMode.normal,
467
'normal': wavelink.QueueMode.normal,
468
'track': wavelink.QueueMode.loop,
469
'loop': wavelink.QueueMode.loop,
470
'all': wavelink.QueueMode.loop_all,
471
'queue': wavelink.QueueMode.loop_all
472
}
473
474
if mode.lower() not in mode_map:
475
return await ctx.send("Valid modes: normal/off, track/loop, all/queue")
476
477
player.queue.mode = mode_map[mode.lower()]
478
await ctx.send(f"Loop mode set to: {player.queue.mode.name}")
479
480
@bot.command()
481
async def autoplay_mode(ctx, mode: str = None):
482
"""Configure AutoPlay mode."""
483
player = ctx.voice_client
484
if not player:
485
return await ctx.send("Not connected to a voice channel!")
486
487
if mode is None:
488
current = player.autoplay.name
489
auto_queue_count = player.auto_queue.count
490
await ctx.send(f"AutoPlay: {current} | Auto queue: {auto_queue_count} tracks")
491
return
492
493
mode_map = {
494
'on': wavelink.AutoPlayMode.enabled,
495
'enabled': wavelink.AutoPlayMode.enabled,
496
'partial': wavelink.AutoPlayMode.partial,
497
'off': wavelink.AutoPlayMode.disabled,
498
'disabled': wavelink.AutoPlayMode.disabled
499
}
500
501
if mode.lower() not in mode_map:
502
return await ctx.send("Valid modes: enabled/on, partial, disabled/off")
503
504
player.autoplay = mode_map[mode.lower()]
505
await ctx.send(f"AutoPlay mode set to: {player.autoplay.name}")
506
```
507
508
### History Management
509
510
```python
511
@bot.command()
512
async def history(ctx, count: int = 10):
513
"""Show recently played tracks."""
514
player = ctx.voice_client
515
if not player:
516
return await ctx.send("Not connected to a voice channel!")
517
518
if not player.queue.history or player.queue.history.is_empty:
519
return await ctx.send("No tracks in history!")
520
521
# Get last N tracks from history
522
history_tracks = list(player.queue.history)[-count:]
523
524
embed = discord.Embed(
525
title="Recently Played",
526
description=f"Last {len(history_tracks)} tracks",
527
color=discord.Color.purple()
528
)
529
530
track_list = []
531
for i, track in enumerate(reversed(history_tracks), 1):
532
track_list.append(f"{i}. {track.title} - {track.author}")
533
534
embed.add_field(
535
name="History",
536
value="\n".join(track_list),
537
inline=False
538
)
539
540
await ctx.send(embed=embed)
541
542
@bot.command()
543
async def replay(ctx, index: int = 1):
544
"""Replay a track from history."""
545
player = ctx.voice_client
546
if not player:
547
return await ctx.send("Not connected to a voice channel!")
548
549
if not player.queue.history or player.queue.history.is_empty:
550
return await ctx.send("No tracks in history!")
551
552
try:
553
# Get track from history (1-based index from most recent)
554
history_list = list(player.queue.history)
555
track = history_list[-(index)]
556
557
# Add to front of queue
558
player.queue.put_at(0, track)
559
await ctx.send(f"Added to front of queue: {track.title}")
560
561
except IndexError:
562
await ctx.send(f"Invalid index! History has {player.queue.history.count} tracks.")
563
```
564
565
### Advanced Queue Features
566
567
```python
568
@bot.command()
569
async def find_track(ctx, *, query: str):
570
"""Find tracks in the queue matching a query."""
571
player = ctx.voice_client
572
if not player:
573
return await ctx.send("Not connected to a voice channel!")
574
575
if player.queue.is_empty:
576
return await ctx.send("Queue is empty!")
577
578
# Search for tracks matching the query
579
matching_tracks = []
580
for i, track in enumerate(player.queue):
581
if (query.lower() in track.title.lower() or
582
query.lower() in track.author.lower()):
583
matching_tracks.append((i + 1, track))
584
585
if not matching_tracks:
586
return await ctx.send(f"No tracks found matching '{query}'")
587
588
embed = discord.Embed(
589
title="Search Results in Queue",
590
description=f"Found {len(matching_tracks)} matching tracks",
591
color=discord.Color.green()
592
)
593
594
result_list = []
595
for pos, track in matching_tracks[:10]: # Limit to 10 results
596
result_list.append(f"{pos}. {track.title} - {track.author}")
597
598
embed.add_field(
599
name="Matches",
600
value="\n".join(result_list),
601
inline=False
602
)
603
604
await ctx.send(embed=embed)
605
606
@bot.command()
607
async def queue_stats(ctx):
608
"""Show detailed queue statistics."""
609
player = ctx.voice_client
610
if not player:
611
return await ctx.send("Not connected to a voice channel!")
612
613
# Calculate total duration
614
total_duration = sum(track.length for track in player.queue)
615
hours = total_duration // 3600000
616
minutes = (total_duration % 3600000) // 60000
617
seconds = (total_duration % 60000) // 1000
618
619
# Count tracks by source
620
source_counts = {}
621
for track in player.queue:
622
source = track.source.name
623
source_counts[source] = source_counts.get(source, 0) + 1
624
625
embed = discord.Embed(
626
title="Queue Statistics",
627
color=discord.Color.blue()
628
)
629
630
embed.add_field(name="Total Tracks", value=player.queue.count, inline=True)
631
embed.add_field(name="Mode", value=player.queue.mode.name, inline=True)
632
embed.add_field(name="AutoPlay", value=player.autoplay.name, inline=True)
633
634
duration_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
635
embed.add_field(name="Total Duration", value=duration_str, inline=True)
636
637
if player.queue.history:
638
embed.add_field(name="History Count", value=player.queue.history.count, inline=True)
639
640
embed.add_field(name="Auto Queue", value=player.auto_queue.count, inline=True)
641
642
# Source breakdown
643
if source_counts:
644
source_info = "\n".join(f"{source}: {count}" for source, count in source_counts.items())
645
embed.add_field(name="Sources", value=source_info, inline=False)
646
647
await ctx.send(embed=embed)
648
```