Implemented support for casting local media through the localstream script

This commit is contained in:
Fabio Manganiello 2019-02-06 11:51:44 +01:00
parent d15b21ddfa
commit 6713ce0f03
3 changed files with 88 additions and 11 deletions

View File

@ -2,6 +2,7 @@ import enum
import os
import re
import subprocess
import threading
import urllib.request
import urllib.parse
@ -9,6 +10,7 @@ import urllib.parse
from platypush.config import Config
from platypush.context import get_plugin
from platypush.plugins import Plugin, action
from platypush.utils import get_ip_or_hostname
class PlayerState(enum.Enum):
STOP = 'stop'
@ -25,12 +27,24 @@ class MediaPlugin(Plugin):
* A media player installed (supported so far: mplayer, omxplayer, chromecast)
* **python-libtorrent** (``pip install python-libtorrent``), optional for Torrent support
* **youtube-dl** installed on your system (see your distro instructions), optional for YouTube support
To start the local media stream service over HTTP:
* **nodejs** installed on your system
* **express** module (``npm install express``)
* **mime-types** module (``npm install mime-types``)
"""
# A media plugin can either be local or remote (e.g. control media on
# another device)
_is_local = True
# Default port for the local resources HTTP streaming service
_default_streaming_port = 8989
_local_stream_bin = os.path.join(os.path.dirname(__file__), 'bin',
'localstream')
_NOT_IMPLEMENTED_ERR = NotImplementedError(
'This method must be implemented in a derived class')
@ -57,7 +71,7 @@ class MediaPlugin(Plugin):
'media.chromecast'}
def __init__(self, media_dirs=[], download_dir=None, env=None,
*args, **kwargs):
streaming_port=_default_streaming_port, *args, **kwargs):
"""
:param media_dirs: Directories that will be scanned for media files when
a search is performed (default: none)
@ -70,6 +84,10 @@ class MediaPlugin(Plugin):
:param env: Environment variables key-values to pass to the
player executable (e.g. DISPLAY, XDG_VTNR, PULSE_SINK etc.)
:type env: dict
:param streaming_port: Port to be used for streaming local resources
over HTTP (default: 8989)
:type streaming_port: int
"""
super().__init__(*args, **kwargs)
@ -121,6 +139,8 @@ class MediaPlugin(Plugin):
self.media_dirs.add(self.download_dir)
self._videos_queue = []
self._streaming_port = streaming_port
self._streaming_proc = None
def _get_resource(self, resource):
"""
@ -331,6 +351,50 @@ class MediaPlugin(Plugin):
return self._youtube_search_html_parse(query=query)
@action
def start_streaming(self, media):
if self._streaming_proc:
self.logger.info('A streaming process is already running, ' +
'terminating it first')
self.stop_streaming()
self._streaming_proc = subprocess.Popen(
[self._local_stream_bin, media, str(self._streaming_port)],
stdin=subprocess.PIPE, stdout=subprocess.PIPE
)
threading.Thread(target=self._streaming_process_monitor(media)).start()
url = 'http://{}:{}/video'.format(get_ip_or_hostname(),
self._streaming_port)
self.logger.info('Starting streaming {} on {}'.format(media, url))
return { 'url': url }
@action
def stop_streaming(self):
if not self._streaming_proc:
self.logger.info('No streaming process found')
return
self._streaming_proc.terminate()
self._streaming_proc.wait()
try: self._streaming_proc.kill()
except: pass
self._streaming_proc = None
def _streaming_process_monitor(self, media):
def _thread():
if not self._streaming_proc:
return
self._streaming_proc.wait()
try: self.stop_streaming()
except: pass
return _thread
def _youtube_search_api(self, query):
return [
{
@ -376,6 +440,7 @@ class MediaPlugin(Plugin):
return proc.stdout.read().decode("utf-8", "strict")[:-1]
def is_local(self):
return self._is_local

View File

@ -7,6 +7,7 @@ from pychromecast.controllers.youtube import YouTubeController
from platypush.context import get_plugin
from platypush.plugins import Plugin, action
from platypush.plugins.media import MediaPlugin
from platypush.utils import get_mime_type
class MediaChromecastPlugin(MediaPlugin):
@ -172,29 +173,26 @@ class MediaChromecastPlugin(MediaPlugin):
return hndl.play_video(yt)
resource = self._get_resource(resource)
if resource.startswith('magnet:?'):
player_args = { 'chromecast': cast }
return get_plugin('media.webtorrent').play(resource,
player='chromecast',
**player_args)
# Best effort from the extension
if not content_type:
for ext in self.video_extensions:
if ('.' + ext).lower() in resource.lower():
content_type = 'video/' + ext
break
if resource.startswith('file://'):
resource = resource[len('file://'):]
if not content_type:
for ext in self.audio_extensions:
if ('.' + ext).lower() in resource.lower():
content_type = 'audio/' + ext
break
content_type = get_mime_type(resource)
if not content_type:
raise RuntimeError('content_type required to process media {}'.
format(resource))
if os.path.isfile(resource):
resource = self.start_streaming(resource).output['url']
self.logger.info('Playing {} on {}'.format(resource, chromecast))
mc.play_media(resource, content_type, title=title, thumb=image_url,

View File

@ -4,10 +4,12 @@ import hashlib
import importlib
import inspect
import logging
import magic
import os
import signal
import socket
import ssl
import urllib.request
logger = logging.getLogger(__name__)
@ -216,4 +218,16 @@ def get_ip_or_hostname():
return socket.getfqdn() if ip.startswith('127.') else ip
def get_mime_type(resource):
if resource.startswith('file://'):
resource = resource[len('file://'):]
if resource.startswith('http://') or resource.startswith('https://'):
with urllib.request.urlopen(resource) as response:
return response.info().get_content_type()
else:
mime = magic.Magic(mime=True)
return mime.from_file(resource)
# vim:sw=4:ts=4:et: