Fixed stop handler for vlc plugin.

For some reason, vlc event handlers are not re-entrant (https://github.com/oaubert/python-vlc/issues/44#issuecomment-378520074).

This means that the vlc API can't be used from an event handler,
and that an event handler that reacts to stop/end-of-stream by
releasing the player and the vlc instance will likely get stuck
and the app may eventually die with SIGSEGV.

Because of this design limitation on the vlc side, the plugin has
to run another thread in the main app that monitors the stop event
set by the event handler and releases the resources appropriately.
This commit is contained in:
Fabio Manganiello 2021-02-28 13:03:10 +01:00
parent d190560536
commit 833f810d4b

View file

@ -1,5 +1,7 @@
import os import os
import threading
import urllib.parse import urllib.parse
from typing import Optional
from platypush.context import get_bus from platypush.context import get_bus
from platypush.plugins.media import PlayerState, MediaPlugin from platypush.plugins.media import PlayerState, MediaPlugin
@ -47,6 +49,8 @@ class MediaVlcPlugin(MediaPlugin):
self._on_stop_callbacks = [] self._on_stop_callbacks = []
self._title = None self._title = None
self._filename = None self._filename = None
self._monitor_thread: Optional[threading.Thread] = None
self._on_stop_event = threading.Event()
@classmethod @classmethod
def _watched_event_types(cls): def _watched_event_types(cls):
@ -64,11 +68,18 @@ class MediaVlcPlugin(MediaPlugin):
def _init_vlc(self, resource): def _init_vlc(self, resource):
import vlc import vlc
if self._instance:
self.logger.info('Another instance is running, waiting for it to terminate')
self._on_stop_event.wait()
self._reset_state() self._reset_state()
for k, v in self._env.items(): for k, v in self._env.items():
os.environ[k] = v os.environ[k] = v
self._monitor_thread = threading.Thread(target=self._player_monitor)
self._monitor_thread.start()
self._instance = vlc.Instance(*self._args) self._instance = vlc.Instance(*self._args)
self._player = self._instance.media_player_new(resource) self._player = self._instance.media_player_new(resource)
@ -76,16 +87,24 @@ class MediaVlcPlugin(MediaPlugin):
self._player.event_manager().event_attach( self._player.event_manager().event_attach(
eventtype=evt, callback=self._event_callback()) eventtype=evt, callback=self._event_callback())
def _player_monitor(self):
self._on_stop_event.wait()
self.logger.info('VLC stream terminated')
self._reset_state()
def _reset_state(self): def _reset_state(self):
self._latest_seek = None self._latest_seek = None
self._title = None self._title = None
self._filename = None self._filename = None
self._on_stop_event.clear()
if self._player: if self._player:
self.logger.info('Releasing VLC player resource')
self._player.release() self._player.release()
self._player = None self._player = None
if self._instance: if self._instance:
self.logger.info('Releasing VLC instance resource')
self._instance.release() self._instance.release()
self._instance = None self._instance = None
@ -105,7 +124,7 @@ class MediaVlcPlugin(MediaPlugin):
self._post_event(MediaPauseEvent) self._post_event(MediaPauseEvent)
elif event.type == EventType.MediaPlayerStopped or \ elif event.type == EventType.MediaPlayerStopped or \
event.type == EventType.MediaPlayerEndReached: event.type == EventType.MediaPlayerEndReached:
self._reset_state() self._on_stop_event.set()
self._post_event(MediaStopEvent) self._post_event(MediaStopEvent)
for cbk in self._on_stop_callbacks: for cbk in self._on_stop_callbacks:
cbk() cbk()