Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Playing audio by passingio.BytesIO doesn't work on Linux #2205

Closed
3 tasks done
davidhozic opened this issue Aug 9, 2023 · 9 comments
Closed
3 tasks done

Playing audio by passingio.BytesIO doesn't work on Linux #2205

davidhozic opened this issue Aug 9, 2023 · 9 comments
Labels
unconfirmed bug A bug report that needs triaging

Comments

@davidhozic
Copy link
Contributor

davidhozic commented Aug 9, 2023

Summary

When using io.BytesIO and pipe=True inside discord.FFmpegPCMAudio(io.BytesIO(audio_data), pipe=True), the voice won't work on Linux, however it works on Windows.

Reproduction Steps

Passing io.ByteIO to discord.FFmpegPCMAudio(io.BytesIO(audio_data), pipe=True) on Linux systems and then
trying to play audio with VoiceClient.play.

Minimal Reproducible Code

import discord
import secret
import io
import asyncio


client = discord.Client()


@client.event
async def on_ready():

    with open("test.mp3", "rb") as file:
        audio_data = file.read()

    channel: discord.VoiceChannel = client.get_channel(1132373179210399844)
    vc = await channel.connect()
    vc.play(
        discord.FFmpegPCMAudio(io.BytesIO(audio_data), pipe=True)
    )
    while vc.is_playing():
        await asyncio.sleep(1)

    await vc.disconnect()


client.run(secret.TOKEN)

Expected Results

For the audio to be played.

Actual Results

The bot connects and disconnects instantly without throwing any errors.

Intents

discord.Intents.default()

System Information

  • Python v3.8.10-final
  • py-cord vNone.1.None-final
  • aiohttp v3.8.5
  • system info: Windows 10 10.0.19045

master version of pycord, pip install --editable .

Checklist

  • I have searched the open issues for duplicates.
  • I have shown the entire traceback, if possible.
  • I have removed my token from display, if visible.

Additional Context

When using debug logging, this is displayed:

INFO:discord.voice_client:Connecting to voice...
INFO:discord.voice_client:Starting voice handshake... (connection attempt 1)
DEBUG:discord.gateway:Updating our voice state to {'op': 4, 'd': {'guild_id': 863071397207212052, 'channel_id': 1132373179210399844, 'self_mute': False, 'self_deaf': False}}.
DEBUG:discord.gateway:For Shard ID None: WebSocket Event: {'t': 'VOICE_STATE_UPDATE', 's': 10, 'op': 0, 'd': {'member': {'user': {'username': 'Aproksimacka', 'public_flags': 0, 'id': '936739431073865789', 'global_name': None, 'display_name': None, 'discriminator': '6086', 'bot': True, 'avatar_decoration': None, 'avatar': 'ad85ab7dd32c53620c31845efb12546a'}, 'roles': ['1109567210906714224'], 'premium_since': None, 'pending': False, 'nick': None, 'mute': False, 'joined_at': '2023-05-20T19:43:57.902000+00:00', 'flags': 0, 'deaf': False, 'communication_disabled_until': None, 'avatar': None}, 'user_id': '936739431073865789', 'suppress': False, 'session_id': '997050e6d39082dddedc84dde4a76884', 'self_video': False, 'self_mute': False, 'self_deaf': False, 'request_to_speak_timestamp': None, 'mute': False, 'guild_id': '863071397207212052', 'deaf': False, 'channel_id': '1132373179210399844'}}
DEBUG:discord.client:Dispatching event socket_event_type
DEBUG:discord.client:Dispatching event voice_state_update
DEBUG:discord.gateway:For Shard ID None: WebSocket Event: {'t': 'VOICE_SERVER_UPDATE', 's': 11, 'op': 0, 'd': {'token': '842a41fd7f2363f3', 'guild_id': '863071397207212052', 'endpoint': 'frankfurt8784.discord.media:443'}}
DEBUG:discord.client:Dispatching event socket_event_type
INFO:discord.voice_client:Voice handshake complete. Endpoint found frankfurt8784.discord.media
DEBUG:discord.gateway:Sending voice websocket frame: {'op': 0, 'd': {'server_id': '863071397207212052', 'user_id': '936739431073865789', 'session_id': '997050e6d39082dddedc84dde4a76884', 'token': '842a41fd7f2363f3'}}.
DEBUG:discord.gateway:Voice websocket frame received: {'op': 8, 'd': {'v': 4, 'heartbeat_interval': 13750.0}}
DEBUG:discord.gateway:Voice websocket frame received: {'op': 2, 'd': {'streams': [{'type': 'video', 'ssrc': 189060, 'rtx_ssrc': 189061, 'rid': '', 'quality': 0, 'active': False}], 'ssrc': 189059, 'port': 50027, 'modes': ['aead_aes256_gcm_rtpsize', 'aead_aes256_gcm', 'aead_xchacha20_poly1305_rtpsize', 'xsalsa20_poly1305_lite_rtpsize', 'xsalsa20_poly1305_lite', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305'], 'ip': '66.22.243.29', 'experiments': ['fixed_keyframe_interval']}}
DEBUG:discord.gateway:received packet in initial_connection: b'\x00\x02\x00F\x00\x02\xe2\x8346.248.82.243\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xcc;'
DEBUG:discord.gateway:detected ip: <my_ip> port: 52283
DEBUG:discord.gateway:received supported encryption modes: xsalsa20_poly1305_lite, xsalsa20_poly1305_suffix, xsalsa20_poly1305
DEBUG:discord.gateway:Sending voice websocket frame: {'op': 1, 'd': {'protocol': 'udp', 'data': {'address': '46.248.82.243', 'port': 52283, 'mode': 'xsalsa20_poly1305_lite'}}}.
INFO:discord.gateway:selected the voice protocol for use (xsalsa20_poly1305_lite)
DEBUG:discord.gateway:Voice websocket frame received: {'op': 18, 'd': {'user_id': '145196308985020416', 'flags': 2}}
DEBUG:discord.gateway:Voice websocket frame received: {'op': 20, 'd': {'user_id': '145196308985020416', 'platform': 0}}
DEBUG:discord.gateway:Voice websocket frame received: {'op': 4, 'd': {'video_codec': 'H264', 'secret_key': [227, 140, 28, 222, 51, 18, 234, 239, 79, 41, 209, 255, 164, 240, 165, 144, 80, 64, 28, 168, 127, 164, 191, 4, 62, 219, 27, 251, 13, 203, 158, 135], 'mode': 'xsalsa20_poly1305_lite', 'media_session_id': 'c2b49c8a0ec667a050d7660b1d0d4a56', 'audio_codec': 'opus'}}
INFO:discord.gateway:received secret key for voice connection
DEBUG:discord.gateway:Sending voice websocket frame: {'op': 5, 'd': {'speaking': 1, 'delay': 0}}.
DEBUG:discord.gateway:Sending voice websocket frame: {'op': 5, 'd': {'speaking': 0, 'delay': 0}}.
INFO:discord.player:Preparing to terminate ffmpeg process 2106.
INFO:discord.player:ffmpeg process 2106 successfully terminated with return code of -15.
DEBUG:discord.gateway:Sending voice websocket frame: {'op': 5, 'd': {'speaking': 1, 'delay': 0}}.
DEBUG:discord.gateway:Sending voice websocket frame: {'op': 5, 'd': {'speaking': 0, 'delay': 0}}.
DEBUG:discord.gateway:Sending voice websocket frame: {'op': 5, 'd': {'speaking': 0, 'delay': 0}}.
DEBUG:discord.gateway:Received WSMessage(type=<WSMsgType.CLOSED: 257>, data=None, extra=None)
INFO:discord.voice_client:Disconnecting from voice normally, close code 1000.
@davidhozic davidhozic added the unconfirmed bug A bug report that needs triaging label Aug 9, 2023
@davidhozic davidhozic changed the title Playing audio by passing io.BytesIO doesn't work on Linux Playing audio by passingio.BytesIO doesn't work on Linux Aug 9, 2023
@Ted-18
Copy link

Ted-18 commented Aug 18, 2023

Hi!
You need to download ffmpeg for linux and give the software permission to run.

ffmpeg_options_linux = {'executable': './requirements/ffmpeg'}
sound_path = "./sounds/file.wav" # mp3, wav, etc


@client.event
async def on_ready():

    # Preparing the sound with the volume
    source = discord.PCMVolumeTransformer(discord.FFmpegPCMAudio(sound_path , **FFMPEG_OPTIONS), volume=0.5)

    channel: discord.VoiceChannel = client.get_channel(1132373179210399844)
    vc = await channel.connect()
    vc.play(source)

    while vc.is_playing():
        await asyncio.sleep(1)

    await vc.disconnect()

@davidhozic
Copy link
Contributor Author

It works directly with a file, just not with io.BytesIO

@plun1331
Copy link
Member

Could you try explicitly seeking to the start of the BytesIO object and then playing it?

@davidhozic
Copy link
Contributor Author

Could you try explicitly seeking to the start of the BytesIO object and then playing it?

import discord
import secret
import io
import asyncio


client = discord.Client()


@client.event
async def on_ready():

    with open("test.mp3", "rb") as file:
        audio_data = file.read()

    channel: discord.VoiceChannel = client.get_channel(1132373179210399844)
    vc = await channel.connect()
    stream = io.BytesIO(audio_data)
    stream.seek(0)
    vc.play(
        discord.FFmpegPCMAudio(stream, pipe=True)
    )
    while vc.is_playing():
        await asyncio.sleep(1)

    await vc.disconnect()


client.run(secret.TOKEN)

Same result

@plun1331
Copy link
Member

Just thought I'd check, I've seen my fair share of issues with buffers not being read from the start

@ffrejaa
Copy link
Contributor

ffrejaa commented Aug 30, 2023

I was about to submit a bug with a similar issue i've managed to temporarily fix. How long is the audio clip you're testing on? It seems FFmpeg is terminated prematurely when playing a stream as opposed to supplying a filename, which is especially noticable when the audio clip is short. The fix I'm using is a small change in the _pipe_writer function of the FFmpegAudio class in player.py.
Here's what the full function looks like now

def _pipe_writer(self, source: io.BufferedIOBase) -> None:
    while self._process:
        # arbitrarily large read size
        data = source.read(8192)
        if not data:
            ##HOTFIX
            self._stdin.close()
            ##    
            #self._process.terminate()
            return
        try:
            self._stdin.write(data)
        except Exception:
            _log.debug(
                "Write error for %s, this is probably not a problem",
                self,
                exc_info=True,
            )
            # at this point the source data is either exhausted or the process is fubar
            self._process.terminate()
            return

Try and see if this fixes the problem

@davidhozic
Copy link
Contributor Author

davidhozic commented Aug 30, 2023

I was about to submit a bug with a similar issue i've managed to temporarily fix. [..]

The audio is about 10 seconds.
Yeah that seems to fix it.. Why would the process manually need to be terminated anyway, doesn't ffmpeg close on it's own?

@ffrejaa
Copy link
Contributor

ffrejaa commented Aug 30, 2023

Why would the process manually need to be terminated anyway, doesn't ffmpeg close on it's own?

It does when the pipe is closed and, vitally, input fully processed and written back out. I can only assume the intent was to free up resources as fast as possible but I couldn't say for sure

@Dorukyum
Copy link
Member

Appears to be fixed in #2240.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
unconfirmed bug A bug report that needs triaging
Projects
None yet
Development

No branches or pull requests

5 participants