Refactored torrent module

This commit is contained in:
Fabio Manganiello 2019-02-04 01:01:39 +01:00
parent a4f80d4622
commit 286eb431f0
3 changed files with 91 additions and 130 deletions

View file

@ -46,15 +46,6 @@ class TorrentStateChangeEvent(TorrentEvent):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class TorrentStateChangeEvent(TorrentEvent):
"""
Event triggered upon torrent state change
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class TorrentDownloadCompletedEvent(TorrentEvent): class TorrentDownloadCompletedEvent(TorrentEvent):
""" """
Event triggered upon torrent state change Event triggered upon torrent state change
@ -74,4 +65,3 @@ class TorrentDownloadStopEvent(TorrentEvent):
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -155,7 +155,7 @@ class MediaMplayerPlugin(MediaPlugin):
self._init_mplayer(mplayer_args) self._init_mplayer(mplayer_args)
else: else:
if not self._mplayer: if not self._mplayer:
raise RuntimeError('MPlayer is not running') self.logger.warning('MPlayer is not running')
cmd = '{}{}{}{}\n'.format( cmd = '{}{}{}{}\n'.format(
prefix + ' ' if prefix else '', prefix + ' ' if prefix else '',
@ -249,8 +249,7 @@ class MediaMplayerPlugin(MediaPlugin):
if resource.startswith('file://'): if resource.startswith('file://'):
resource = resource[7:] resource = resource[7:]
elif resource.startswith('magnet:?'): elif resource.startswith('magnet:?'):
torrent_plugin = get_plugin('media.webtorrent') return get_plugin('media.webtorrent').play(resource)
return torrent_plugin.play(resource)
return self._exec('loadfile', resource, mplayer_args=mplayer_args) return self._exec('loadfile', resource, mplayer_args=mplayer_args)

View file

@ -1,14 +1,17 @@
import datetime
import os import os
import select import select
import subprocess import subprocess
import tempfile
import threading import threading
import time import time
from platypush.config import Config
from platypush.context import get_bus, get_plugin from platypush.context import get_bus, get_plugin
from platypush.message.response import Response from platypush.message.response import Response
from platypush.plugins.media import PlayerState, MediaPlugin from platypush.plugins.media import PlayerState, MediaPlugin
from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, \ from platypush.message.event.torrent import TorrentDownloadStartEvent, \
MediaStopEvent, NewPlayingMediaEvent TorrentDownloadCompletedEvent
from platypush.plugins import action from platypush.plugins import action
from platypush.utils import find_bins_in_path from platypush.utils import find_bins_in_path
@ -26,39 +29,19 @@ class MediaWebtorrentPlugin(MediaPlugin):
``npm install -g BlackLight/webtorrent-cli`` as my fork contains ``npm install -g BlackLight/webtorrent-cli`` as my fork contains
the ``--[player]-args`` options to pass custom arguments to your the ``--[player]-args`` options to pass custom arguments to your
installed player) installed player)
* A media plugin configured for streaming (e.g. media.mplayer
or media.omxplayer)
""" """
_supported_media_plugins = { 'media.mplayer', 'media.omxplayer' } _supported_media_plugins = { 'media.mplayer', 'media.omxplayer' }
_supported_webtorrent_players = {'airplay', 'chromecast', 'mplayer',
'mpv', 'omxplayer', 'vlc', 'xbmc'}
# Download at least 10 MBs before starting streaming
_download_size_before_streaming = 10 * 2**20
def __init__(self, player_type=None, player_args=None, webtorrent_bin=None, def __init__(self, webtorrent_bin=None, *args, **kwargs):
*args, **kwargs):
""" """
Create the webtorrent wrapper media.webtorrent will use the default media player plugin you have
configured (e.g. mplayer, omxplayer) to stream the torrent.
:param player_type: Player type to be used for streaming. Check
https://www.npmjs.com/package/webtorrent for a full list of the
supported players. If no player is specified then it will search in
your Platypush configuration for any enabled and supported media
plugin (either ``media.mplayer`` or ``media.omxplayer``) to
configure the streaming player. Currently supported options:
- airplay
- chromecast
- mplayer
- mpv
- omxlayer
- vlc
- xbmc
:type player_type: str
:param player_args: Extra arguments to pass to the player as a list.
For mplayer, vlc, omxplayer and mpv this will be a list of extra
arguments to be passed to the executable (e.g. fullscreen, volume,
audio sink etc.).
:type player_args: list[str]
:param webtorrent_bin: Path to your webtorrent executable. If not set, :param webtorrent_bin: Path to your webtorrent executable. If not set,
then Platypush will search for the right executable in your PATH then Platypush will search for the right executable in your PATH
@ -68,11 +51,12 @@ class MediaWebtorrentPlugin(MediaPlugin):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._init_webtorrent_bin(webtorrent_bin=webtorrent_bin) self._init_webtorrent_bin(webtorrent_bin=webtorrent_bin)
self._init_media_player(player_type=player_type, self._init_media_player()
player_args=player_args or [])
def _init_webtorrent_bin(self, webtorrent_bin=None): def _init_webtorrent_bin(self, webtorrent_bin=None):
self._webtorrent_process = None
if not webtorrent_bin: if not webtorrent_bin:
bin_name = 'webtorrent.exe' if os.name == 'nt' else 'webtorrent' bin_name = 'webtorrent.exe' if os.name == 'nt' else 'webtorrent'
bins = find_bins_in_path(bin_name) bins = find_bins_in_path(bin_name)
@ -94,89 +78,59 @@ class MediaWebtorrentPlugin(MediaPlugin):
self.webtorrent_bin = webtorrent_bin self.webtorrent_bin = webtorrent_bin
def _init_media_player(self, player_type, player_args): def _init_media_player(self):
if player_type is None: self._media_plugin = None
media_plugin = None plugin_name = None
plugin_name = None
for plugin_name in self._supported_media_plugins: for plugin_name in self._supported_media_plugins:
try: try:
media_plugin = get_plugin(plugin_name) if Config.get(plugin_name):
self._media_plugin = get_plugin(plugin_name)
break break
except:
pass
if not media_plugin:
raise RuntimeError(('No media player specified and no ' +
'compatible media plugin configured - ' +
'supported media plugins: {}').format(
self._supported_media_plugins))
self.player_type = plugin_name[len('media.'):]
self.player_args = media_plugin.args
else:
if player_type not in self._supported_webtorrent_players:
raise RuntimeError(('{} is not a supported player (supported ' +
'players: {})').format(
player_type,
self._supported_webtorrent_players))
self.player_type = player_type
self.player_args = player_args or []
if not self.player_args:
try:
plugin = get_plugin('media.' + player_type)
self.player_args = plugin.args
except:
pass
self._player = None
def _start_webtorrent(self, resource):
if self._player:
try:
self.quit()
except:
self.logger.debug('Failed to quit the previous instance: {}'.
format(str))
webtorrent_args = [self.webtorrent_bin, '--' + self.player_type,
resource]
if self.player_args:
webtorrent_args += ['--' + self.player_type + "-args='" + \
repr(' '.join(self.player_args)) + "'"]
popen_args = {
'stdin': subprocess.PIPE,
'stdout': subprocess.PIPE,
}
if self._env:
popen_args['env'] = self._env
else:
try:
plugin = get_plugin('media.' + player_type)
if plugin._env:
popen_args['env'] = plugin._env
except: except:
pass pass
self._player = subprocess.Popen(webtorrent_args, **popen_args) if not self._media_plugin:
raise RuntimeError(('No media player specified and no ' +
'compatible media plugin configured - ' +
'supported media plugins: {}').format(
self._supported_media_plugins))
def _process_monitor(self):
def _process_monitor(self, resource, output_file):
def _thread(): def _thread():
if not self._player: if not self._webtorrent_process:
return return
self._player.wait() bus = get_bus()
get_bus().post(MediaStopEvent()) bus.post(TorrentDownloadStartEvent(resource=resource))
self._player = None
while True:
try:
if os.path.getsize(output_file) > \
self._download_size_before_streaming:
break
except FileNotFoundError:
pass
self._media_plugin.play(output_file)
self._webtorrent_process.wait()
bus.post(TorrentDownloadCompletedEvent(resource=resource))
self._webtorrent_process = None
return _thread return _thread
def _get_torrent_download_path(self):
if self._media_plugin.download_dir:
# TODO set proper file name based on the torrent metadata
return os.path.join(self._media_plugin.download_dir,
'torrent_media_' + datetime.datetime.
today().strftime('%Y-%m-%d_%H-%M-%S-%f'))
else:
return tempfile.NamedTemporaryFile(delete=False).name
@action @action
def play(self, resource): def play(self, resource):
""" """
@ -187,10 +141,25 @@ class MediaWebtorrentPlugin(MediaPlugin):
:type resource: str :type resource: str
""" """
self._start_webtorrent(resource) if self._webtorrent_process:
bus = get_bus() try:
bus.post(NewPlayingMediaEvent(resource=resource)) self.quit()
threading.Thread(target=self._process_monitor()).start() except:
self.logger.debug('Failed to quit the previous instance: {}'.
format(str))
output_file = self._get_torrent_download_path()
webtorrent_args = [self.webtorrent_bin, '--stdout', resource]
with open(output_file, 'w') as f:
self._webtorrent_process = subprocess.Popen(webtorrent_args,
stdout=f)
threading.Thread(target=self._process_monitor(
resource=resource, output_file=output_file)).start()
return { 'resource': resource }
@action @action
def stop(self): def stop(self):
@ -200,14 +169,18 @@ class MediaWebtorrentPlugin(MediaPlugin):
@action @action
def quit(self): def quit(self):
""" Quit the player """ """ Quit the player """
if self._player and self._is_process_alive(self._player.pid): if self._media_plugin:
self._player.terminate() self._media_plugin.quit()
self._player.wait()
try: self._player.kill() if self._webtorrent_process and self._is_process_alive(
self._webtorrent_process.pid):
self._webtorrent_process.terminate()
self._webtorrent_process.wait()
try: self._webtorrent_process.kill()
except: pass except: pass
bus.post(MediaStopEvent()) bus.post(MediaStopEvent())
self._player = None self._webtorrent_process = None
@action @action
def load(self, resource): def load(self, resource):
@ -217,14 +190,14 @@ class MediaWebtorrentPlugin(MediaPlugin):
return self.play(resource) return self.play(resource)
def _is_process_alive(self): def _is_process_alive(self):
if not self._player: if not self._webtorrent_process:
return False return False
try: try:
os.kill(self._player.pid, 0) os.kill(self._webtorrent_process.pid, 0)
return True return True
except OSError: except OSError:
self._player = None self._webtorrent_process = None
return False return False
@action @action
@ -241,9 +214,8 @@ class MediaWebtorrentPlugin(MediaPlugin):
} }
""" """
return {'state': PlayerState.PLAY.value return {'state': self._media_plugin.status()
if self._player and self._is_process_alive() .get('state', PlayerState.STOP.value)}
else PlayerState.STOP.value}
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: