diff --git a/platypush/backend/http/static/js/plugins/media/index.js b/platypush/backend/http/static/js/plugins/media/index.js index 97753eda..7e50da60 100644 --- a/platypush/backend/http/static/js/plugins/media/index.js +++ b/platypush/backend/http/static/js/plugins/media/index.js @@ -141,8 +141,14 @@ Vue.component('media', { }); }, - selectDevice: function(device) { + selectDevice: async function(device) { this.selectedDevice = device; + let status = await this.selectedDevice.status(); + + this.onStatusUpdate({ + device: this.selectedDevice, + status: status, + }); }, syncPosition: function(status) { diff --git a/platypush/backend/http/static/js/plugins/media/players/browser.js b/platypush/backend/http/static/js/plugins/media/players/browser.js index fd8bd3ff..0ceb18b0 100644 --- a/platypush/backend/http/static/js/plugins/media/players/browser.js +++ b/platypush/backend/http/static/js/plugins/media/players/browser.js @@ -21,7 +21,7 @@ MediaPlayers.browser = Vue.extend({ iconClass: { type: String, - default: 'fa fa-browser', + default: 'fa fa-laptop', }, }, diff --git a/platypush/backend/http/static/js/plugins/media/players/local.js b/platypush/backend/http/static/js/plugins/media/players/local.js index 354da496..fa494079 100644 --- a/platypush/backend/http/static/js/plugins/media/players/local.js +++ b/platypush/backend/http/static/js/plugins/media/players/local.js @@ -62,7 +62,7 @@ MediaPlayers.local = Vue.extend({ seek: async function(position) { return await request( - this.pluginPrefix.concat('.seek'), + this.pluginPrefix.concat('.set_position'), {position: position}, ); }, diff --git a/platypush/context/__init__.py b/platypush/context/__init__.py index 9614b5c7..49cd3532 100644 --- a/platypush/context/__init__.py +++ b/platypush/context/__init__.py @@ -116,13 +116,11 @@ def get_plugin(plugin_name, reload=False): return plugins[plugin_name] + def get_bus(): global main_bus return main_bus -def register_plugin(name, plugin, **kwargs): - """ Registers a plugin instance by name """ - global plugins def get_or_create_event_loop(): try: @@ -135,4 +133,3 @@ def get_or_create_event_loop(): # vim:sw=4:ts=4:et: - diff --git a/platypush/plugins/media/mplayer.py b/platypush/plugins/media/mplayer.py index dd0d7be6..04b1e695 100644 --- a/platypush/plugins/media/mplayer.py +++ b/platypush/plugins/media/mplayer.py @@ -1,7 +1,6 @@ import os import select import subprocess -import tempfile import threading import time @@ -29,24 +28,6 @@ class MediaMplayerPlugin(MediaPlugin): _mplayer_bin_default_args = ['-slave', '-quiet', '-idle', '-input', 'nodefault-bindings', '-noconfig', 'all'] - _mplayer_properties = [ - 'osdlevel', 'speed', 'loop', 'pause', 'filename', 'path', 'demuxer', - 'stream_pos', 'stream_start', 'stream_end', 'stream_length', - 'stream_time_pos', 'titles', 'chapter', 'chapters', 'angle', 'length', - 'percent_pos', 'time_pos', 'metadata', 'metadata', 'volume', 'balance', - 'mute', 'audio_delay', 'audio_format', 'audio_codec', 'audio_bitrate', - 'samplerate', 'channels', 'switch_audio', 'switch_angle', - 'switch_title', 'capturing', 'fullscreen', 'deinterlace', 'ontop', - 'rootwin', 'border', 'framedropping', 'gamma', 'brightness', 'contrast', - 'saturation', 'hue', 'panscan', 'vsync', 'video_format', 'video_codec', - 'video_bitrate', 'width', 'height', 'fps', 'aspect', 'switch_video', - 'switch_program', 'sub', 'sub_source', 'sub_file', 'sub_vob', - 'sub_demux', 'sub_delay', 'sub_pos', 'sub_alignment', 'sub_visibility', - 'sub_forced_only', 'sub_scale', 'tv_brightness', 'tv_contrast', - 'tv_saturation', 'tv_hue', 'teletext_page', 'teletext_subpage', - 'teletext_mode', 'teletext_format', - ] - def __init__(self, mplayer_bin=None, mplayer_timeout=_mplayer_default_communicate_timeout, args=None, *argv, **kwargs): @@ -74,14 +55,14 @@ class MediaMplayerPlugin(MediaPlugin): super().__init__(*argv, **kwargs) self.args = args or [] - self._init_mplayer_bin() + self._init_mplayer_bin(mplayer_bin=mplayer_bin) self._build_actions() self._player = None self._mplayer_timeout = mplayer_timeout self._mplayer_stopped_event = threading.Event() + self._status_lock = threading.Lock() self._is_playing_torrent = False - def _init_mplayer_bin(self, mplayer_bin=None): if not mplayer_bin: bin_name = 'mplayer.exe' if os.name == 'nt' else 'mplayer' @@ -105,10 +86,10 @@ class MediaMplayerPlugin(MediaPlugin): def _init_mplayer(self, mplayer_args=None): if self._player: try: - self._player.quit() - except: + self._player.terminate() + except Exception as e: self.logger.debug('Failed to quit mplayer before _exec: {}'. - format(str)) + format(str(e))) mplayer_args = mplayer_args or [] args = [self.mplayer_bin] + self._mplayer_bin_default_args @@ -137,7 +118,7 @@ class MediaMplayerPlugin(MediaPlugin): def args_pprint(txt): lc = txt.lower() if lc[0] == '[': - return '%s=None'%lc[1:-1] + return '%s=None' % lc[1:-1] return lc while True: @@ -170,18 +151,19 @@ class MediaMplayerPlugin(MediaPlugin): self._player.stdin.write(cmd) self._player.stdin.flush() - bus = get_bus() if cmd_name == 'loadfile' or cmd_name == 'loadlist': - bus.post(NewPlayingMediaEvent(resource=args[0])) + self._post_event(NewPlayingMediaEvent, resource=args[0]) elif cmd_name == 'pause': - bus.post(MediaPauseEvent()) + self._post_event(MediaPauseEvent) elif cmd_name == 'quit' or cmd_name == 'stop': if cmd_name == 'quit': self._player.terminate() self._player.wait() - try: self._player.kill() - except: pass + try: + self._player.kill() + except Exception: + pass self._player = None if not wait_for_response: @@ -200,12 +182,17 @@ class MediaMplayerPlugin(MediaPlugin): if line.startswith('ANS_'): k, v = tuple(line[4:].split('=')) v = v.strip() - if v == 'yes': v = True - elif v == 'no': v = False + if v == 'yes': + v = True + elif v == 'no': + v = False - try: v = eval(v) - except: pass - response = { k: v } + try: + v = eval(v) + except Exception: + pass + + response = {k: v} return response @@ -222,8 +209,8 @@ class MediaMplayerPlugin(MediaPlugin): @action def list_actions(self): - return [ { 'action': action, 'args': self._actions[action] } - for action in sorted(self._actions.keys()) ] + return [{'action': a, 'args': self._actions[a]} + for a in sorted(self._actions.keys())] def _process_monitor(self): def _thread(): @@ -232,15 +219,21 @@ class MediaMplayerPlugin(MediaPlugin): self._mplayer_stopped_event.clear() self._player.wait() - try: self.quit() - except: pass + try: + self.quit() + except Exception: + pass - get_bus().post(MediaStopEvent()) + self._post_event(MediaStopEvent) self._mplayer_stopped_event.set() self._player = None return _thread + @staticmethod + def _post_event(evt_type, **evt): + bus = get_bus() + bus.post(evt_type(player='local', plugin='media.mplayer', **evt)) @action def play(self, resource, subtitles=None, mplayer_args=None): @@ -258,7 +251,7 @@ class MediaMplayerPlugin(MediaPlugin): :type mplayer_args: list[str] """ - get_bus().post(MediaPlayRequestEvent(resource=resource)) + self._post_event(MediaPlayRequestEvent, resource=resource) if subtitles: mplayer_args = mplayer_args or [] mplayer_args += ['-sub', self.get_subtitles_file(subtitles)] @@ -271,76 +264,86 @@ class MediaMplayerPlugin(MediaPlugin): return get_plugin('media.webtorrent').play(resource) self._is_playing_torrent = False - ret = self._exec('loadfile', resource, mplayer_args=mplayer_args) - get_bus().post(MediaPlayEvent(resource=resource)) - return ret + self._exec('loadfile', resource, mplayer_args=mplayer_args) + self._post_event(MediaPlayEvent, resource=resource) + return self.status() @action def pause(self): """ Toggle the paused state """ - ret = self._exec('pause') - get_bus().post(MediaPauseEvent()) - return ret + self._exec('pause') + self._post_event(MediaPauseEvent) + return self.status() @action def stop(self): """ Stop the playback """ # return self._exec('stop') - return self.quit() + self.quit() + return self.status() @action def quit(self): """ Quit the player """ self._stop_torrent() self._exec('quit') - get_bus().post(MediaStopEvent()) + self._post_event(MediaStopEvent) + return self.status() @action def voldown(self, step=10.0): """ Volume down by (default: 10)% """ - return self._exec('volume', -step*10) + self._exec('volume', -step * 10) + return self.status() @action def volup(self, step=10.0): """ Volume up by (default: 10)% """ - return self._exec('volume', step*10) + self._exec('volume', step * 10) + return self.status() @action def back(self, offset=60.0): """ Back by (default: 60) seconds """ - return self.step_property('time_pos', -offset) + self.step_property('time_pos', -offset) + return self.status() @action def forward(self, offset=60.0): """ Forward by (default: 60) seconds """ - return self.step_property('time_pos', offset) + self.step_property('time_pos', offset) + return self.status() @action def toggle_subtitles(self): """ Toggle the subtitles visibility """ subs = self.get_property('sub_visibility').output.get('sub_visibility') - return self._exec('sub_visibility', int(not subs)) + self._exec('sub_visibility', int(not subs)) + return self.status() @action - def set_subtitles(self, filename, **args): + def add_subtitles(self, filename, **args): """ Sets media subtitles from filename """ self._exec('sub_visibility', 1) - return self._exec('sub_load', filename) + self._exec('sub_load', filename) + return self.status() @action def remove_subtitles(self, index=None): """ Removes the subtitle specified by the index (default: all) """ if index is None: - return self._exec('sub_remove') + self._exec('sub_remove') else: - return self._exec('sub_remove', index) + self._exec('sub_remove', index) + + return self.status() @action def is_playing(self): """ :returns: True if it's playing, False otherwise """ - return self.get_property('pause').output.get('pause') == False + return self.get_property('pause').output.get('pause') is False @action def load(self, resource, mplayer_args=None, **kwargs): @@ -354,17 +357,19 @@ class MediaMplayerPlugin(MediaPlugin): @action def mute(self): """ Toggle mute state """ - return self._exec('mute') + self._exec('mute') + return self.status() @action def seek(self, position): """ Seek backward/forward by the specified number of seconds - :param relative_position: Number of seconds relative to the current cursor - :type relative_position: int + :param position: Number of seconds relative to the current cursor + :type position: int """ - return self.step_property('time_pos', position) + self.step_property('time_pos', position) + return self.status() @action def set_position(self, position): @@ -374,7 +379,8 @@ class MediaMplayerPlugin(MediaPlugin): :param position: Number of seconds from the start :type position: int """ - return self.set_property('time_pos', position) + self.set_property('time_pos', position) + return self.status() @action def set_volume(self, volume): @@ -384,7 +390,8 @@ class MediaMplayerPlugin(MediaPlugin): :param volume: Volume value between 0 and 100 :type volume: float """ - return self._exec('volume', volume) + self._exec('volume', volume) + return self.status() @action def status(self): @@ -400,18 +407,38 @@ class MediaMplayerPlugin(MediaPlugin): } """ - state = { 'state': PlayerState.STOP.value } + if not self._player: + return {'state': PlayerState.STOP.value} - try: - paused = self.get_property('pause').output.get('pause') - if paused is True: - state['state'] = PlayerState.PAUSE.value - elif paused is False: - state['state'] = PlayerState.PLAY.value - except: - pass - finally: - return state + status = {} + props = { + 'duration': 'length', + 'filename': 'filename', + 'fullscreen': 'fullscreen', + 'mute': 'mute', + 'name': 'filename', + 'path': 'path', + 'pause': 'pause', + 'percent_pos': 'percent_pos', + 'position': 'time_pos', + 'title': 'filename', + 'volume': 'volume', + } + + with self._status_lock: + for prop, player_prop in props.items(): + value = self.get_property(player_prop).output + if value is not None: + status[prop] = value.get(player_prop) + + status['seekable'] = True if status['duration'] is not None else False + status['state'] = PlayerState.PAUSE.value if status['pause'] else PlayerState.PLAY.value + + if status['path']: + status['url'] = ('file://' if os.path.isfile(status['path']) else '') + status['path'] + + status['volume_max'] = 100 + return status @action def get_property(self, property, args=None): @@ -483,5 +510,4 @@ class MediaMplayerPlugin(MediaPlugin): return response - # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/media/mpv.py b/platypush/plugins/media/mpv.py index 44588dc0..23f4b4d0 100644 --- a/platypush/plugins/media/mpv.py +++ b/platypush/plugins/media/mpv.py @@ -230,7 +230,7 @@ class MediaMpvPlugin(MediaPlugin): pos = min(self._player.time_pos+self._player.time_remaining, max(0, position)) self._player.time_pos = pos - return {'position': pos} + return self.status() @action def back(self, offset=60.0): @@ -272,6 +272,16 @@ class MediaMpvPlugin(MediaPlugin): """ Toggle the subtitles visibility """ return self.toggle_property('sub_visibility') + @action + def add_subtitles(self, filename): + """ Add a subtitles file """ + return self._player.sub_add(filename) + + @action + def remove_subtitles(self, sub_id): + """ Remove a subtitles track by id """ + return self._player.sub_remove(sub_id) + @action def toggle_fullscreen(self, fullscreen=None): """ Toggle the fullscreen mode """ @@ -385,91 +395,29 @@ class MediaMpvPlugin(MediaPlugin): return {'state': PlayerState.STOP.value} return { - 'aspect': getattr(self._player, 'aspect'), - 'audio': getattr(self._player, 'audio'), - 'audio_bitrate': getattr(self._player, 'audio_bitrate'), 'audio_channels': getattr(self._player, 'audio_channels'), 'audio_codec': getattr(self._player, 'audio_codec_name'), - 'audio_delay': getattr(self._player, 'audio_delay'), - 'audio_output': getattr(self._player, 'current_ao'), - 'audio_file_paths': getattr(self._player, 'audio_file_paths'), - 'audio_files': getattr(self._player, 'audio_files'), - 'audio_params': getattr(self._player, 'audio_params'), - 'audio_mixer': getattr(self._player, 'alsa_mixer_device'), - 'autosync': getattr(self._player, 'autosync'), - 'brightness': getattr(self._player, 'brightness'), - 'chapter': getattr(self._player, 'chapter'), - 'chapter_list': getattr(self._player, 'chapter_list'), - 'chapter_metadata': getattr(self._player, 'chapter_metadata'), - 'chapters': getattr(self._player, 'chapters'), - 'chapters_file': getattr(self._player, 'chapters_file'), - 'clock': getattr(self._player, 'clock'), - 'cookies': getattr(self._player, 'cookies'), - 'cookies_file': getattr(self._player, 'cookies_file'), 'delay': getattr(self._player, 'delay'), - 'displays': getattr(self._player, 'display_names'), 'duration': getattr(self._player, 'playback_time', 0) + getattr(self._player, 'playtime_remaining', 0) if getattr(self._player, 'playtime_remaining') else None, - 'file_format': getattr(self._player, 'file_format'), 'filename': getattr(self._player, 'filename'), 'file_size': getattr(self._player, 'file_size'), - 'font': getattr(self._player, 'font'), - 'fps': getattr(self._player, 'fps'), 'fullscreen': getattr(self._player, 'fs'), - 'height': getattr(self._player, 'height'), - 'idle': getattr(self._player, 'idle'), - 'idle_active': getattr(self._player, 'idle_active'), - 'loop': getattr(self._player, 'loop'), - 'media_title': getattr(self._player, 'media_title'), - 'mpv_version': getattr(self._player, 'mpv_version'), 'mute': getattr(self._player, 'mute'), 'name': getattr(self._player, 'name'), 'pause': getattr(self._player, 'pause'), 'percent_pos': getattr(self._player, 'percent_pos'), - 'playlist': getattr(self._player, 'playlist'), - 'playlist_pos': getattr(self._player, 'playlist_pos'), 'position': getattr(self._player, 'playback_time'), - 'quiet': getattr(self._player, 'quiet'), - 'really_quiet': getattr(self._player, 'really_quiet'), - 'saturation': getattr(self._player, 'saturation'), - 'screen': getattr(self._player, 'screen'), - 'screenshot_directory': getattr(self._player, 'screenshot_directory'), - 'screenshot_format': getattr(self._player, 'screenshot_format'), - 'screenshot_template': getattr(self._player, 'screenshot_template'), 'seekable': getattr(self._player, 'seekable'), - 'seeking': getattr(self._player, 'seeking'), - 'shuffle': getattr(self._player, 'shuffle'), - 'speed': getattr(self._player, 'speed'), 'state': (PlayerState.PAUSE.value if self._player.pause else PlayerState.PLAY.value), - 'stream_pos': getattr(self._player, 'stream_pos'), - 'sub': getattr(self._player, 'sub'), - 'sub_file_paths': getattr(self._player, 'sub_file_paths'), - 'sub_files': getattr(self._player, 'sub_files'), - 'sub_paths': getattr(self._player, 'sub_paths'), - 'sub_text': getattr(self._player, 'sub_text'), - 'subdelay': getattr(self._player, 'subdelay'), - 'time_start': getattr(self._player, 'time_start'), - 'title': getattr(self._player, 'filename'), + 'title': getattr(self._player, 'media_title') or getattr(self._player, 'filename'), 'url': self._get_current_resource(), - 'user_agent': getattr(self._player, 'user_agent'), - 'video': getattr(self._player, 'video'), - 'video_align_x': getattr(self._player, 'video_align_x'), - 'video_align_y': getattr(self._player, 'video_align_y'), - 'video_aspect': getattr(self._player, 'video_aspect'), - 'video_bitrate': getattr(self._player, 'video_bitrate'), - 'video_output': getattr(self._player, 'current_vo'), 'video_codec': getattr(self._player, 'video_codec'), 'video_format': getattr(self._player, 'video_format'), - 'video_params': getattr(self._player, 'video_params'), - 'video_sync': getattr(self._player, 'video_sync'), - 'video_zoom': getattr(self._player, 'video_zoom'), 'volume': getattr(self._player, 'volume'), 'volume_max': getattr(self._player, 'volume_max'), 'width': getattr(self._player, 'width'), - 'window_minimized': getattr(self._player, 'window_minimized'), - 'window_scale': getattr(self._player, 'window_scale'), - 'working_directory': getattr(self._player, 'working_directory'), } def on_stop(self, callback): diff --git a/platypush/plugins/media/omxplayer.py b/platypush/plugins/media/omxplayer.py index 881d3148..d78a7f85 100644 --- a/platypush/plugins/media/omxplayer.py +++ b/platypush/plugins/media/omxplayer.py @@ -1,15 +1,9 @@ import enum -import os -import re -import subprocess - -import urllib.request import urllib.parse -from platypush.context import get_backend, get_plugin +from platypush.context import get_bus from platypush.plugins.media import MediaPlugin, PlayerState -from platypush.message.event.video import VideoPlayEvent, VideoPauseEvent, \ - VideoStopEvent, NewPlayingVideoEvent +from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, MediaStopEvent, MediaSeekEvent from platypush.plugins import action @@ -30,7 +24,7 @@ class MediaOmxplayerPlugin(MediaPlugin): * **omxplayer-wrapper** (``pip install omxplayer-wrapper``) """ - def __init__(self, args=[], *argv, **kwargs): + def __init__(self, args=None, *argv, **kwargs): """ :param args: Arguments that will be passed to the OMXPlayer constructor (e.g. subtitles, volume, start position, window size etc.) see @@ -41,9 +35,12 @@ class MediaOmxplayerPlugin(MediaPlugin): super().__init__(*argv, **kwargs) + if args is None: + args = [] + self.args = args self._player = None - self._handlers = { e.value: [] for e in PlayerEvent } + self._handlers = {e.value: [] for e in PlayerEvent} @action def play(self, resource, subtitles=None, *args, **kwargs): @@ -56,6 +53,8 @@ class MediaOmxplayerPlugin(MediaPlugin): * Remote videos (format: ``https:///``) * YouTube videos (format: ``https://www.youtube.com/watch?v=``) * Torrents (format: Magnet links, Torrent URLs or local Torrent files) + + :param subtitles: Subtitles file """ if subtitles: @@ -69,7 +68,9 @@ class MediaOmxplayerPlugin(MediaPlugin): except Exception as e: self.logger.exception(e) self.logger.warning('Unable to stop a previously running instance ' + - 'of OMXPlayer, trying to play anyway') + 'of OMXPlayer, trying to play anyway') + + from dbus import DBusException try: from omxplayer import OMXPlayer @@ -77,7 +78,7 @@ class MediaOmxplayerPlugin(MediaPlugin): self._init_player_handlers() except DBusException as e: self.logger.warning('DBus connection failed: you will probably not ' + - 'be able to control the media') + 'be able to control the media') self.logger.exception(e) return self.status() @@ -85,15 +86,16 @@ class MediaOmxplayerPlugin(MediaPlugin): @action def pause(self): """ Pause the playback """ - if self._player: self._player.play_pause() + if self._player: + self._player.play_pause() + return self.status() @action def stop(self): """ Stop the playback """ if self._player: self._player.stop() - - return {'status':'stop'} + return {'status': 'stop'} @action def quit(self): @@ -103,8 +105,7 @@ class MediaOmxplayerPlugin(MediaPlugin): self._player.quit() self._player = None - return {'status':'stop'} - + return {'status': 'stop'} @action def voldown(self): @@ -131,7 +132,7 @@ class MediaOmxplayerPlugin(MediaPlugin): def forward(self, offset=60): """ Forward by (default: 60) seconds """ if self._player: - self._player.seek(+offset) + self._player.seek(offset) return self.status() @action @@ -140,20 +141,24 @@ class MediaOmxplayerPlugin(MediaPlugin): if self._player: self._player.stop() - if self.videos_queue: - video = self.videos_queue.pop(0) - return self.play(video) + if self._videos_queue: + video = self._videos_queue.pop(0) + self.play(video) + + return self.status() @action def hide_subtitles(self): """ Hide the subtitles """ - if self._player: self._player.hide_subtitles() + if self._player: + self._player.hide_subtitles() return self.status() @action def hide_video(self): """ Hide the video """ - if self._player: self._player.hide_video() + if self._player: + self._player.hide_video() return self.status() @action @@ -162,19 +167,22 @@ class MediaOmxplayerPlugin(MediaPlugin): :returns: True if it's playing, False otherwise """ - if self._player: return self._player.is_playing() - else: return False + return self._player.is_playing() @action - def load(self, resource, pause=False): + def load(self, resource, pause=False, **kwargs): """ Load a resource/video in the player. + :param resource: URL or filename to load + :type resource: str + :param pause: If set, load the video in paused mode (default: False) :type pause: bool """ - if self._player: self._player.load(resource, ) + if self._player: + self._player.load(resource, pause=pause) return self.status() @action @@ -187,13 +195,15 @@ class MediaOmxplayerPlugin(MediaPlugin): @action def mute(self): """ Mute the player """ - if self._player: self._player.mute() + if self._player: + self._player.mute() return self.status() @action def unmute(self): """ Unmute the player """ - if self._player: self._player.unmute() + if self._player: + self._player.unmute() return self.status() @action @@ -205,7 +215,8 @@ class MediaOmxplayerPlugin(MediaPlugin): :type relative_position: int """ - if self._player: self._player.seek(relative_position) + if self._player: + self._player.seek(relative_position) return self.status() @action @@ -217,7 +228,8 @@ class MediaOmxplayerPlugin(MediaPlugin): :type position: int """ - if self._player: self._player.set_seek(position) + if self._player: + self._player.set_seek(position) return self.status() @action @@ -231,7 +243,8 @@ class MediaOmxplayerPlugin(MediaPlugin): # Transform a [0,100] value to an OMXPlayer volume in [-6000,0] volume = 60.0*volume - 6000 - if self._player: self._player.set_volume(volume) + if self._player: + self._player.set_volume(volume) return self.status() @action @@ -254,41 +267,35 @@ class MediaOmxplayerPlugin(MediaPlugin): } """ - state = PlayerState.STOP.value - if self._player: state = self._player.playback_status().lower() - if state == 'playing': state = PlayerState.PLAY.value - elif state == 'stopped': state = PlayerState.STOP.value - elif state == 'paused': state = PlayerState.PAUSE.value + if state == 'playing': + state = PlayerState.PLAY.value + elif state == 'stopped': + state = PlayerState.STOP.value + elif state == 'paused': + state = PlayerState.PAUSE.value return { - 'source': self._player.get_source(), - 'state': state, - 'volume': self._player.volume(), - 'elapsed': self._player.position(), 'duration': self._player.duration(), - 'width': self._player.width(), - 'height': self._player.height(), + 'filename': urllib.parse.unquote(self._player.get_source()).split('/')[-1], + 'fullscreen': self._player.fullscreen(), + 'mute': self._player._is_muted, + 'path': self._player.get_source(), + 'pause': state == PlayerState.PAUSE.value, + 'position': self._player.position(), + 'seekable': self._player.can_seek(), + 'state': state, + 'title': urllib.parse.unquote(self._player.get_source()).split('/')[-1], + 'url': self._player.get_source(), + 'volume': self._player.volume(), + 'volume_max': 100, } else: return { 'state': PlayerState.STOP.value } - @action - def send_message(self, msg): - try: - redis = get_backend('redis') - if not redis: - raise KeyError() - except KeyError: - self.logger.warning("Backend {} does not implement send_message " + - "and the fallback Redis backend isn't configured") - return - - redis.send_message(msg) - def add_handler(self, event_type, callback): if event_type not in self._handlers.keys(): raise AttributeError('{} is not a valid PlayerEvent type'. @@ -296,31 +303,40 @@ class MediaOmxplayerPlugin(MediaPlugin): self._handlers[event_type].append(callback) + @staticmethod + def _post_event(evt_type, **evt): + bus = get_bus() + bus.post(evt_type(player='local', plugin='media.omxplayer', **evt)) + def on_play(self): def _f(player): - video = self._player.get_source() - self.send_message(VideoPlayEvent(video=video)) + resource = player.get_source() + self._post_event(MediaPlayEvent, resource=resource) for callback in self._handlers[PlayerEvent.PLAY.value]: - callback(video) + callback(resource) return _f def on_pause(self): def _f(player): - video = self._player.get_source() - self.send_message(VideoPauseEvent(video=video)) + resource = player.get_source() + self._post_event(MediaPauseEvent, resource=resource) for callback in self._handlers[PlayerEvent.PAUSE.value]: - callback(video) + callback(resource) return _f def on_stop(self): def _f(player): - self.send_message(VideoStopEvent()) + self._post_event(MediaStopEvent) for callback in self._handlers[PlayerEvent.STOP.value]: callback() return _f + def on_seek(self): + def _f(player): + self._post_event(MediaSeekEvent, position=player.position()) + return _f def _init_player_handlers(self): if not self._player: @@ -329,6 +345,7 @@ class MediaOmxplayerPlugin(MediaPlugin): self._player.playEvent += self.on_play() self._player.pauseEvent += self.on_pause() self._player.stopEvent += self.on_stop() + self._player.positionEvent += self.on_seek() # vim:sw=4:ts=4:et: diff --git a/platypush/plugins/media/vlc.py b/platypush/plugins/media/vlc.py index 55f1a068..0372ba9c 100644 --- a/platypush/plugins/media/vlc.py +++ b/platypush/plugins/media/vlc.py @@ -1,4 +1,5 @@ import os +import urllib.parse from platypush.context import get_bus, get_plugin from platypush.plugins.media import PlayerState, MediaPlugin @@ -45,7 +46,6 @@ class MediaVlcPlugin(MediaPlugin): self._default_volume = volume self._on_stop_callbacks = [] - @classmethod def _watched_event_types(cls): import vlc @@ -64,7 +64,7 @@ class MediaVlcPlugin(MediaPlugin): import vlc self._reset_state() - for k,v in self._env.items(): + for k, v in self._env.items(): os.environ[k] = v self._instance = vlc.Instance(*self._args) @@ -84,44 +84,47 @@ class MediaVlcPlugin(MediaPlugin): self._instance.release() self._instance = None + @staticmethod + def _post_event(evt_type, **evt): + bus = get_bus() + bus.post(evt_type(player='local', plugin='media.vlc', **evt)) + def _event_callback(self): def callback(event): from vlc import EventType self.logger.debug('Received vlc event: {}'.format(event)) - bus = get_bus() if event.type == EventType.MediaPlayerPlaying: - bus.post(MediaPlayEvent(resource=self._get_current_resource())) + self._post_event(MediaPlayEvent, resource=self._get_current_resource()) elif event.type == EventType.MediaPlayerPaused: - bus.post(MediaPauseEvent()) + self._post_event(MediaPauseEvent) elif event.type == EventType.MediaPlayerStopped or \ - event.type == EventType.MediaPlayerEndReached: + event.type == EventType.MediaPlayerEndReached: self._reset_state() - bus.post(MediaStopEvent()) - for callback in self._on_stop_callbacks: - callback() + self._post_event(MediaStopEvent) + for cbk in self._on_stop_callbacks: + cbk() elif event.type == EventType.MediaPlayerTitleChanged: - bus.post(NewPlayingMediaEvent(resource=event.u.new_title)) + self._post_event(NewPlayingMediaEvent, resource=event.u.new_title) elif event.type == EventType.MediaPlayerMediaChanged: - bus.post(NewPlayingMediaEvent(resource=event.u.filename)) + self._post_event(NewPlayingMediaEvent, resource=event.u.filename) elif event.type == EventType.MediaPlayerLengthChanged: - bus.post(NewPlayingMediaEvent(resource=self._get_current_resource())) + self._post_event(NewPlayingMediaEvent, resource=self._get_current_resource()) elif event.type == EventType.MediaPlayerTimeChanged: pos = float(event.u.new_time/1000) if self._latest_seek is None or \ abs(pos-self._latest_seek) > 5: - bus.post(MediaSeekEvent(position=pos)) + self._post_event(MediaSeekEvent, position=pos) self._latest_seek = pos elif event.type == EventType.MediaPlayerAudioVolume: - bus.post(MediaVolumeChangedEvent(volume=self._player.audio_get_volume())) + self._post_event(MediaVolumeChangedEvent, volume=self._player.audio_get_volume()) elif event.type == EventType.MediaPlayerMuted: - bus.post(MediaMuteChangedEvent(mute=True)) + self._post_event(MediaMuteChangedEvent, mute=True) elif event.type == EventType.MediaPlayerUnmuted: - bus.post(MediaMuteChangedEvent(mute=False)) + self._post_event(MediaMuteChangedEvent, mute=False) return callback - @action def play(self, resource, subtitles=None, fullscreen=None, volume=None): """ @@ -169,14 +172,13 @@ class MediaVlcPlugin(MediaPlugin): return self.status() - @action def pause(self): """ Toggle the paused state """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' if not self._player.can_pause(): - return (None, 'The specified media type cannot be paused') + return None, 'The specified media type cannot be paused' self._player.pause() return self.status() @@ -186,10 +188,11 @@ class MediaVlcPlugin(MediaPlugin): """ Quit the player (same as `stop`) """ self._stop_torrent() if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' self._player.stop() - return { 'state': PlayerState.STOP.value } + self._player = None + return self.status() @action def stop(self): @@ -200,14 +203,14 @@ class MediaVlcPlugin(MediaPlugin): def voldown(self, step=10.0): """ Volume down by (default: 10)% """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' return self.set_volume(self._player.audio_get_volume()-step) @action def volup(self, step=10.0): """ Volume up by (default: 10)% """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' return self.set_volume(self._player.audio_get_volume()+step) @action @@ -219,11 +222,13 @@ class MediaVlcPlugin(MediaPlugin): :type volume: float """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' - volume = max(0, min(100, volume)) + volume = max(0, min([100, volume])) self._player.audio_set_volume(volume) - return { 'volume': volume } + status = self.status().output + status['volume'] = volume + return status @action def seek(self, position): @@ -234,27 +239,27 @@ class MediaVlcPlugin(MediaPlugin): :type position: int """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' if not self._player.is_seekable(): - return (None, 'The resource is not seekable') + return None, 'The resource is not seekable' media = self._player.get_media() if not media: - return (None, 'No media loaded') + return None, 'No media loaded' pos = min(media.get_duration()/1000, max(0, position)) self._player.set_time(int(pos*1000)) - return { 'position': pos } + return self.status() @action def back(self, offset=60.0): """ Back by (default: 60) seconds """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' media = self._player.get_media() if not media: - return (None, 'No media loaded') + return None, 'No media loaded' pos = max(0, (self._player.get_time()/1000)-offset) return self.seek(pos) @@ -263,11 +268,11 @@ class MediaVlcPlugin(MediaPlugin): def forward(self, offset=60.0): """ Forward by (default: 60) seconds """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' media = self._player.get_media() if not media: - return (None, 'No media loaded') + return None, 'No media loaded' pos = min(media.get_duration()/1000, (self._player.get_time()/1000)+offset) return self.seek(pos) @@ -276,10 +281,10 @@ class MediaVlcPlugin(MediaPlugin): def toggle_subtitles(self, visibile=None): """ Toggle the subtitles visibility """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' if self._player.video_get_spu_count() == 0: - return (None, 'The media file has no subtitles set') + return None, 'The media file has no subtitles set' if self._player.video_get_spu() is None or \ self._player.video_get_spu() == -1: @@ -291,21 +296,21 @@ class MediaVlcPlugin(MediaPlugin): def toggle_fullscreen(self): """ Toggle the fullscreen mode """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' self._player.toggle_fullscreen() @action def set_fullscreen(self, fullscreen=True): """ Set fullscreen mode """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' self._player.set_fullscreen(fullscreen) @action def set_subtitles(self, filename, **args): """ Sets media subtitles from filename """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' if filename.startswith('file://'): filename = filename[len('file://'):] @@ -315,7 +320,7 @@ class MediaVlcPlugin(MediaPlugin): def remove_subtitles(self): """ Removes (hides) the subtitles """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' self._player.video_set_spu(-1) @action @@ -334,13 +339,14 @@ class MediaVlcPlugin(MediaPlugin): """ if not self._player: return self.play(resource, **args) - return self._player.set_media(resource) + self._player.set_media(resource) + return self.status() @action def mute(self): """ Toggle mute state """ if not self._player: - return (None, 'No vlc instance is running') + return None, 'No vlc instance is running' self._player.audio_toggle_mute() @action @@ -366,31 +372,36 @@ class MediaVlcPlugin(MediaPlugin): """ import vlc if not self._player: - return { 'state': PlayerState.STOP.value } + return {'state': PlayerState.STOP.value} + status = {} vlc_state = self._player.get_state() - state = PlayerState.STOP.value - filename = self._player.get_media().get_mrl() \ - if self._player.get_media() else None if vlc_state == vlc.State.Playing: - state = PlayerState.PLAY.value - if vlc_state == vlc.State.Paused: - state = PlayerState.PAUSE.value + status['state'] = PlayerState.PLAY.value + elif vlc_state == vlc.State.Paused: + status['state'] = PlayerState.PAUSE.value + else: + status['state'] = PlayerState.STOP.value - time = float(self._player.get_time()/1000) if self._player.get_time() \ - is not None else None + status['url'] = self._player.get_media().get_mrl() if self._player.get_media() else None + status['position'] = float(self._player.get_time()/1000) if self._player.get_time() is not None else None media = self._player.get_media() - duration = media.get_duration()/1000 if media and media.get_duration() \ - is not None else None + status['duration'] = media.get_duration()/1000 if media and media.get_duration() is not None else None - return { - 'filename': filename, - 'state': state, - 'time': time, - 'duration': duration, - } + status['seekable'] = status['duration'] is not None + status['fullscreen'] = self._player.get_fullscreen() + status['mute'] = self._player.audio_get_mute() + status['path'] = urllib.parse.unquote(status['url']) + status['pause'] = status['state'] == PlayerState.PAUSE.value + status['percent_pos'] = self._player.get_position()*100 + status['filename'] = urllib.parse.unquote(status['url']).split('/')[-1] + status['title'] = status['filename'] + status['volume'] = self._player.audio_get_volume() + status['volume_max'] = 100 + + return status def on_stop(self, callback): self._on_stop_callbacks.append(callback)