Skip to content

Commit

Permalink
Fixed stop handler for vlc plugin.
Browse files Browse the repository at this point in the history
For some reason, vlc event handlers are not re-entrant (oaubert/python-vlc#44 (comment)).

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.
  • Loading branch information
blacklight committed Feb 28, 2021
1 parent d190560 commit 833f810
Showing 1 changed file with 20 additions and 1 deletion.
21 changes: 20 additions & 1 deletion platypush/plugins/media/vlc.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import threading
import urllib.parse
from typing import Optional

from platypush.context import get_bus
from platypush.plugins.media import PlayerState, MediaPlugin
Expand Down Expand Up @@ -47,6 +49,8 @@ def __init__(self, args=None, fullscreen=False, volume=100, *argv, **kwargs):
self._on_stop_callbacks = []
self._title = None
self._filename = None
self._monitor_thread: Optional[threading.Thread] = None
self._on_stop_event = threading.Event()

@classmethod
def _watched_event_types(cls):
Expand All @@ -64,28 +68,43 @@ def _watched_event_types(cls):

def _init_vlc(self, resource):
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()

for k, v in self._env.items():
os.environ[k] = v

self._monitor_thread = threading.Thread(target=self._player_monitor)
self._monitor_thread.start()
self._instance = vlc.Instance(*self._args)
self._player = self._instance.media_player_new(resource)

for evt in self._watched_event_types():
self._player.event_manager().event_attach(
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):
self._latest_seek = None
self._title = None
self._filename = None
self._on_stop_event.clear()

if self._player:
self.logger.info('Releasing VLC player resource')
self._player.release()
self._player = None

if self._instance:
self.logger.info('Releasing VLC instance resource')
self._instance.release()
self._instance = None

Expand All @@ -105,7 +124,7 @@ def callback(event):
self._post_event(MediaPauseEvent)
elif event.type == EventType.MediaPlayerStopped or \
event.type == EventType.MediaPlayerEndReached:
self._reset_state()
self._on_stop_event.set()
self._post_event(MediaStopEvent)
for cbk in self._on_stop_callbacks:
cbk()
Expand Down

0 comments on commit 833f810

Please sign in to comment.