音声の再生¶

await vc.play(source) はボイス接続でソースを再生し、すぐに Track を返します。複数のトラックは同時に再生されてミキシングされます。Queue を使うと、トラックを 1 つずつ順番に再生します。
import bostie
from bostie import Context, voice
bot = bostie.Bot()
tracks: dict[int, voice.Track] = {}
@bot.command
async def play(ctx: Context, url: str) -> None:
"""Play a media URL."""
vc = ctx.voice.current or await ctx.voice.connect()
track = await vc.play(voice.Url(url), volume=0.8, meta=ctx.user.id)
tracks[vc.guild_id] = track
await ctx.respond(f"Playing {track.source.tags.title or url}.")
ended = await track.wait()
if ended.reason is voice.EndReason.ERROR:
await ctx.followup(f"The track failed: {ended.error}")
@bot.command
async def pause(ctx: Context) -> None:
"""Pause or resume the track."""
track = tracks.get(ctx.guild_id or 0)
if track is None or track.ended is not None:
await ctx.respond("Nothing is playing.", ephemeral=True)
return
if track.paused:
track.resume()
else:
track.pause()
await ctx.respond(f"At {track.position:.0f} s.")
@bot.command
async def seek(ctx: Context, seconds: float) -> None:
"""Jump to a point in the track."""
track = tracks.get(ctx.guild_id or 0)
if track is not None and track.source.seekable:
await track.seek(seconds)
await ctx.respond(f"Now at {seconds:.0f} s.")
@bot.command
async def bass(ctx: Context, gain: float = 6.0) -> None:
"""Boost the bass and slow down a little."""
track = tracks.get(ctx.guild_id or 0)
if track is not None:
track.filters = voice.Filters(bass=gain, speed=0.9)
await ctx.respond("Changed the filters.")
ソース¶
ソースは再生する音声です。各ソースは 1 回しか使えません。play がソースを開き、終了したソースを再び開くと StateError が送出されます。同じ音声を 2 回再生するには、source.again() で同じ音声を持つ新しいソースを取得します。
| ソース | 再生するもの | シーク |
|---|---|---|
voice.File(path) |
ローカルファイル | 可能 |
voice.Bytes(data, hint=None) |
メモリ上のファイルの内容。hint は "ogg" のような拡張子 |
可能 |
voice.Url(url, headers=None) |
ストリーミングで取得する HTTP または HTTPS のメディアの直接 URL、または HLS プレイリスト | サーバーが Range リクエストを受け付ける場合は可能。HLS は不可 |
voice.FFmpeg(args, executable="ffmpeg") |
ffmpeg コマンドの出力。bostie がデコードできない形式に使う | 不可 |
voice.Stream(items, format) |
format 形式の PCM チャンクまたは Opus パケットを返す非同期イテラブル |
不可 |
File、Bytes、Url は Rust でデコードされます。対応形式は Ogg(Opus、Vorbis)、WebM と Matroska、MP4 と M4A(AAC)、MP3、FLAC、WAV です。URL はメディアそのものを指している必要があり、メディアを埋め込んだ Web ページを指していてはいけません。サーバーとの接続が切れると、Url は 0.5 秒、1 秒、2 秒の間隔をおいて最大 3 回まで再接続し、サーバーが Range リクエストを受け付ける場合は切れた位置から続きを再生します。HLS のマスタープレイリストはその音声レンディションを再生し、ライブプレイリストは末尾の 3 セグメント手前から再生を始めます。暗号化されたプレイリストとバイト範囲指定のセグメントは SourceError で拒否されます。FFmpeg は渡された入力引数でプログラムを実行し、その出力から 48 kHz ステレオ 16 ビットの PCM を読み取ります。プログラムが PATH 上にない場合や、エラーで終了した場合は SourceError を送出します。開けない、デコードできない、シークできないソースは SourceError を送出するか、そのトラックを理由 ERROR で終了させます。
open() によって opened が設定された後、ソースは format、duration(秒。不明な場合は None)、seekable、tags を持ちます。tags は、ファイルに記録された title、artist、album を持つ voice.Tags です。
import asyncio
from collections.abc import AsyncIterator
from pathlib import Path
import bostie
from bostie import Context, voice
bot = bostie.Bot()
CHIME = Path("chime.ogg")
@bot.command
async def chime(ctx: Context) -> None:
"""Play a short sound from memory."""
vc = ctx.voice.current or await ctx.voice.connect()
data = await asyncio.to_thread(CHIME.read_bytes)
await vc.play(voice.Bytes(data, hint="ogg"))
await ctx.respond("Ding.")
@bot.command
async def radio(ctx: Context) -> None:
"""Play a station through ffmpeg."""
vc = ctx.voice.current or await ctx.voice.connect()
station = voice.FFmpeg(["-i", "https://example.com/radio.m3u8"])
await vc.play(station)
await ctx.respond("On air.")
async def silence(seconds: float) -> AsyncIterator[bytes]:
frame = bytes(3840)
for _ in range(int(seconds * 50)):
yield frame
@bot.command
async def hush(ctx: Context) -> None:
"""Play three seconds of generated silence."""
vc = ctx.voice.current or await ctx.voice.connect()
track = await vc.play(voice.Stream(silence(3), voice.PCM()))
await ctx.respond("Shh.")
await track.wait()
ほとんどの Ogg Opus ファイルや WebM ファイルのように、20 ms のパケットに分かれた 48 kHz ステレオの Opus 音声は、フィルターなし・音量 1 で単独再生している間は、デコードも再エンコードもせずにそのまま送信されます。track.passthrough で、そうなっているかどうかがわかります。
トラック¶
vc.play(source, volume=1.0, filters=Filters(), meta=None) は、開始時の音量とフィルター、そして meta を受け取ります。meta には、リクエストしたユーザーなど、トラックと一緒に保持したい任意の値を渡せます。トラックのシンクに使う buffer と stall も受け取ります。
Track |
動作 |
|---|---|
await track.wait() |
終了を待って TrackEnd を返す。待機をキャンセルしてもトラックは止まらない |
await track.started() |
再生が始まるまで待つ。先に終了した場合は False |
track.ended |
TrackEnd。再生中は None |
track.position |
これまでに再生したソースの秒数 |
track.pause()、track.resume()、track.paused |
音声の供給を止める、再開する。すでにバッファーにある音声は再生される |
await track.seek(seconds) |
指定した位置から再生を続ける。ソースがシークできない場合は SourceError |
track.volume、track.filters |
再生中に変更すると即座に反映される |
track.stop() |
トラックを終了する |
track.source、track.meta、track.sink |
再生しているソース、指定した値、書き込み先のシンク |
TrackEnd は track、reason(FINISHED、error を伴う ERROR、STOPPED、または接続が終了したときの CLEANUP)、may_start_next を持ちます。may_start_next は FINISHED または ERROR の後で真になり、独自のプレイヤーが次のトラックに進むべきかどうかを示します。vc.stop() はすべてのトラックを終了します。
音量とフィルター¶
volume はサンプルに掛ける倍率です。0.5 で半分の大きさになり、1.0 では変化しません。voice.Filters は、音声の再生中に Rust が適用するエフェクトを設定します。
Filters のフィールド |
既定値 | 範囲 |
|---|---|---|
equalizer |
{} |
最大 16 バンド。20〜20 000 Hz の中心周波数を dB 単位のゲインに対応付ける。各バンドの幅は 1 オクターブ |
bass |
0.0 | 100 Hz 未満のゲイン(dB) |
speed |
1.0 | 0.5〜2.0。音程は保たれる |
pitch |
1.0 | 0.5〜2.0。テンポは保たれる |
ゲインの範囲は -24〜24 dB で、範囲外の値は ValueError を送出します。
キュー¶
import bostie
from bostie import Context, voice
bot = bostie.Bot(voice=voice.Defaults(crossfade=3.0, history=20))
queues: dict[int, voice.Queue] = {}
async def queue_of(ctx: Context) -> voice.Queue:
vc = ctx.voice.current or await ctx.voice.connect()
queue = queues.get(vc.guild_id)
if queue is None or queue.closed:
queue = queues[vc.guild_id] = voice.Queue(vc)
return queue
@bot.command
async def add(ctx: Context, url: str, next_up: bool = False) -> None:
"""Add a URL to the queue, or play it next."""
queue = await queue_of(ctx)
queue.add(voice.Url(url), meta=ctx.user.id, index=0 if next_up else None)
await ctx.respond(f"{len(queue.upcoming)} tracks up next.")
@bot.command
async def queue(ctx: Context) -> None:
"""Show what plays now and next."""
player = await queue_of(ctx)
lines = [f"Now: {player.now.source!r}" if player.now else "Nothing playing"]
lines += [f"{i}. {track.source!r}" for i, track in enumerate(player.upcoming, 1)]
await ctx.respond("\n".join(lines))
@bot.command
async def back(ctx: Context) -> None:
"""Play the previous track again."""
player = await queue_of(ctx)
previous = player.back()
await ctx.respond("Going back." if previous else "Nothing played before.")
@bot.command
async def shuffle(ctx: Context) -> None:
"""Shuffle the upcoming tracks."""
(await queue_of(ctx)).shuffle()
await ctx.respond("Shuffled.")
voice.Queue(vc) はトラックを隙間なく続けて再生します。crossfade 秒だけ重ねて再生することもできます。volume、filters、crossfade、history(既定値は接続の設定)と、シンクの buffer と stall を受け取ります。
queue.add(source, meta=None, index=None)はソースを末尾に、または次に再生するトラックの中のindexの位置に追加し、そのTrackを返します。queue.now、queue.upcoming、queue.historyは、現在のトラック、次に再生するトラック、そして再生済みのトラック(最大history件)です。queue.skip()は現在のトラックを終了します。queue.back()は履歴の最後のトラックをもう一度再生し、その後で現在のトラックを最初から再生します。queue.move(track, index)、queue.remove(track)、queue.shuffle()、queue.clear()は、次に再生するトラックを並べ替えます。queue.loop = "track"で現在のトラックを、"queue"でキュー全体を繰り返し、Noneで繰り返しを止めます。型はvoice.Loopです。繰り返しにはsource.again()が使われます。queue.pause()、queue.resume()、queue.paused、queue.volume、queue.filtersは、再生中のトラックに作用します。queue.stop()はすべてのトラックを終了します。その後に追加したトラックは再び再生されます。await queue.join()は、キューにも再生中にも何もなくなるまで待ちます。
終了した接続のキューは closed となり、それ以上トラックを受け付けません。スニペットのように、再接続した後で新しいキューを作成してください。
サウンドボードのサウンド¶
await bot.rest.soundboard.send(vc.channel_id, sound_id=...) は、Bot が接続しているチャンネルでサウンドボードのサウンドを再生します。サウンドは Bot が送る音声とは別に、Discord が通話に直接再生します。別のサーバーのサウンドには source_guild_id を渡します。bot.rest.soundboard.list_default() は Discord 標準のサウンドを、list(guild_id) はサーバーのサウンドを一覧にし、bot.request_soundboard_sounds(guild_id) はゲートウェイにそれらを要求します。
複数のサーバーで同じ音声を流す¶
await voice.broadcast(source, connections) は 1 つのソースを複数の接続で再生し、デコードとエンコードは 1 回だけ行います。終了した接続は対象から外れ、後から参加した接続は追加されません。encoder と stall を受け取り、stall の既定値は "drop" です。これにより、停滞した接続が他の接続の足を引っ張りません。
エンコーダー¶
voice.EncoderSettings は接続の Opus エンコーダーを設定します。Defaults(encoder=...)、connect(encoder=...)、または接続中の vc.encoder = ... で指定します。
| フィールド | 既定値 | 意味 |
|---|---|---|
bitrate |
None |
ビット毎秒。500〜512 000。None はチャンネルのビットレートに従い、なければ 64 000 |
application |
"audio" |
音楽には "audio"、音声通話には "voip"、遅延を最小にするには "lowdelay" |
fec |
True |
失われたパケットを補う前方誤り訂正 |
packet_loss |
0.1 | fec が想定するパケット損失率。0〜1 |
complexity |
10 | 0〜10。低いほど CPU 使用量が減る |
bandwidth |
"auto" |
最も高い音声帯域。"narrow"、"medium"、"wide"、"superwide"、"full" のいずれか |
signal_type |
"auto" |
"voice" または "music" 向けに調整する |
vc.speaking_flags は Bot が通知する発話フラグを設定します。既定値は voice.SpeakingFlags.MICROPHONE で、共有音声には SOUNDSHARE、優先スピーカーには PRIORITY を使います。