Stop playback on ffmpeg HTTP 404
This commit is contained in:
@@ -66,6 +66,7 @@ class MumbleBot:
|
|||||||
self.read_pcm_size = 0
|
self.read_pcm_size = 0
|
||||||
self.pcm_buffer_size = 0
|
self.pcm_buffer_size = 0
|
||||||
self.last_ffmpeg_err = ""
|
self.last_ffmpeg_err = ""
|
||||||
|
self.ffmpeg_fatal_error = ""
|
||||||
|
|
||||||
# Play/pause status
|
# Play/pause status
|
||||||
self.is_pause = False
|
self.is_pause = False
|
||||||
@@ -528,13 +529,51 @@ class MumbleBot:
|
|||||||
|
|
||||||
# The ffmpeg process is a thread
|
# The ffmpeg process is a thread
|
||||||
# prepare pipe for catching stderr of ffmpeg
|
# prepare pipe for catching stderr of ffmpeg
|
||||||
if self.redirect_ffmpeg_log:
|
pipe_rd, pipe_wd = util.pipe_no_wait() # Let the pipe work in non-blocking mode
|
||||||
pipe_rd, pipe_wd = util.pipe_no_wait() # Let the pipe work in non-blocking mode
|
if pipe_rd is not None:
|
||||||
self.thread_stderr = os.fdopen(pipe_rd)
|
self.thread_stderr = os.fdopen(pipe_rd)
|
||||||
else:
|
else:
|
||||||
pipe_rd, pipe_wd = None, None
|
pipe_rd, pipe_wd = None, None
|
||||||
|
|
||||||
self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=self.pcm_buffer_size)
|
self.thread = sp.Popen(command, stdout=sp.PIPE, stderr=pipe_wd, bufsize=self.pcm_buffer_size)
|
||||||
|
if pipe_wd is not None:
|
||||||
|
os.close(pipe_wd)
|
||||||
|
|
||||||
|
def _read_ffmpeg_stderr(self):
|
||||||
|
if not self.thread_stderr:
|
||||||
|
return
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
line = self.thread_stderr.readline()
|
||||||
|
except (BlockingIOError, OSError):
|
||||||
|
return
|
||||||
|
|
||||||
|
if not line:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.last_ffmpeg_err = line
|
||||||
|
stripped_line = line.strip("\n")
|
||||||
|
if self.redirect_ffmpeg_log:
|
||||||
|
self.log.debug("ffmpeg: " + stripped_line)
|
||||||
|
if "HTTP error 404" in line or "404 Not Found" in line:
|
||||||
|
self.ffmpeg_fatal_error = stripped_line
|
||||||
|
|
||||||
|
def _fail_current_ffmpeg_item(self):
|
||||||
|
current = var.playlist.current_item()
|
||||||
|
self._cleanup_ffmpeg_process()
|
||||||
|
self.last_ffmpeg_err = ""
|
||||||
|
|
||||||
|
if not current:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log.error("bot: cannot play music %s", current.format_debug_string())
|
||||||
|
self.log.error("bot: with ffmpeg error: %s", self.ffmpeg_fatal_error)
|
||||||
|
self.ffmpeg_fatal_error = ""
|
||||||
|
|
||||||
|
self.send_channel_msg(tr('unable_play', item=current.format_title()))
|
||||||
|
var.playlist.remove_by_id(current.id)
|
||||||
|
var.cache.free_and_delete(current.id)
|
||||||
|
|
||||||
def async_download_next(self):
|
def async_download_next(self):
|
||||||
# Function start if the next music isn't ready
|
# Function start if the next music isn't ready
|
||||||
@@ -616,13 +655,11 @@ class MumbleBot:
|
|||||||
raw_music = self.thread.stdout.read(self.pcm_buffer_size)
|
raw_music = self.thread.stdout.read(self.pcm_buffer_size)
|
||||||
self.read_pcm_size += len(raw_music)
|
self.read_pcm_size += len(raw_music)
|
||||||
|
|
||||||
if self.redirect_ffmpeg_log:
|
self._read_ffmpeg_stderr()
|
||||||
try:
|
if self.ffmpeg_fatal_error:
|
||||||
self.last_ffmpeg_err = self.thread_stderr.readline()
|
self._fail_current_ffmpeg_item()
|
||||||
if self.last_ffmpeg_err:
|
raw_music = None
|
||||||
self.log.debug("ffmpeg: " + self.last_ffmpeg_err.strip("\n"))
|
continue
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if raw_music:
|
if raw_music:
|
||||||
# Adjust the volume and send it to mumble
|
# Adjust the volume and send it to mumble
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import io
|
||||||
|
import logging
|
||||||
|
import unittest
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import bragi
|
||||||
|
import constants
|
||||||
|
import variables as var
|
||||||
|
|
||||||
|
|
||||||
|
class FakeCache:
|
||||||
|
def __init__(self):
|
||||||
|
self.freed_id = None
|
||||||
|
|
||||||
|
def free_and_delete(self, item_id):
|
||||||
|
self.freed_id = item_id
|
||||||
|
|
||||||
|
|
||||||
|
class FakeItem:
|
||||||
|
id = "track-id"
|
||||||
|
|
||||||
|
def format_debug_string(self):
|
||||||
|
return "fake item"
|
||||||
|
|
||||||
|
def format_title(self):
|
||||||
|
return "Fake Item"
|
||||||
|
|
||||||
|
|
||||||
|
class FakePlaylist:
|
||||||
|
def __init__(self, item=None):
|
||||||
|
self.item = item
|
||||||
|
self.removed_id = None
|
||||||
|
|
||||||
|
def current_item(self):
|
||||||
|
return self.item
|
||||||
|
|
||||||
|
def remove_by_id(self, item_id):
|
||||||
|
self.removed_id = item_id
|
||||||
|
self.item = None
|
||||||
|
|
||||||
|
|
||||||
|
class FfmpegHttpErrorTests(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
constants.load_lang("en_US")
|
||||||
|
|
||||||
|
def make_bot(self):
|
||||||
|
bot = bragi.MumbleBot.__new__(bragi.MumbleBot)
|
||||||
|
bot.thread_stderr = None
|
||||||
|
bot.last_ffmpeg_err = ""
|
||||||
|
bot.ffmpeg_fatal_error = ""
|
||||||
|
bot.redirect_ffmpeg_log = False
|
||||||
|
bot.log = logging.getLogger("test")
|
||||||
|
return bot
|
||||||
|
|
||||||
|
def test_ffmpeg_http_404_stderr_is_fatal(self):
|
||||||
|
bot = self.make_bot()
|
||||||
|
bot.thread_stderr = io.StringIO("[http @ 0x123] HTTP error 404 Not Found\n")
|
||||||
|
|
||||||
|
bot._read_ffmpeg_stderr()
|
||||||
|
|
||||||
|
self.assertEqual(bot.ffmpeg_fatal_error, "[http @ 0x123] HTTP error 404 Not Found")
|
||||||
|
|
||||||
|
def test_ffmpeg_fatal_error_removes_current_item(self):
|
||||||
|
item = FakeItem()
|
||||||
|
var.playlist = FakePlaylist(item)
|
||||||
|
var.cache = FakeCache()
|
||||||
|
|
||||||
|
bot = self.make_bot()
|
||||||
|
bot.ffmpeg_fatal_error = "[http @ 0x123] HTTP error 404 Not Found"
|
||||||
|
bot._cleanup_ffmpeg_process = mock.Mock()
|
||||||
|
bot.send_channel_msg = mock.Mock()
|
||||||
|
|
||||||
|
bot._fail_current_ffmpeg_item()
|
||||||
|
|
||||||
|
bot._cleanup_ffmpeg_process.assert_called_once_with()
|
||||||
|
self.assertEqual(var.playlist.removed_id, item.id)
|
||||||
|
self.assertEqual(var.cache.freed_id, item.id)
|
||||||
|
bot.send_channel_msg.assert_called_once()
|
||||||
|
self.assertEqual(bot.ffmpeg_fatal_error, "")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user