Normalized all local players so that they handle events and return statuses in the same format
This commit is contained in:
parent
1964f74f19
commit
ba800ef8e2
8 changed files with 276 additions and 271 deletions
|
@ -141,8 +141,14 @@ Vue.component('media', {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
selectDevice: function(device) {
|
selectDevice: async function(device) {
|
||||||
this.selectedDevice = device;
|
this.selectedDevice = device;
|
||||||
|
let status = await this.selectedDevice.status();
|
||||||
|
|
||||||
|
this.onStatusUpdate({
|
||||||
|
device: this.selectedDevice,
|
||||||
|
status: status,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
syncPosition: function(status) {
|
syncPosition: function(status) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ MediaPlayers.browser = Vue.extend({
|
||||||
|
|
||||||
iconClass: {
|
iconClass: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'fa fa-browser',
|
default: 'fa fa-laptop',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ MediaPlayers.local = Vue.extend({
|
||||||
|
|
||||||
seek: async function(position) {
|
seek: async function(position) {
|
||||||
return await request(
|
return await request(
|
||||||
this.pluginPrefix.concat('.seek'),
|
this.pluginPrefix.concat('.set_position'),
|
||||||
{position: position},
|
{position: position},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -116,13 +116,11 @@ def get_plugin(plugin_name, reload=False):
|
||||||
|
|
||||||
return plugins[plugin_name]
|
return plugins[plugin_name]
|
||||||
|
|
||||||
|
|
||||||
def get_bus():
|
def get_bus():
|
||||||
global main_bus
|
global main_bus
|
||||||
return 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():
|
def get_or_create_event_loop():
|
||||||
try:
|
try:
|
||||||
|
@ -135,4 +133,3 @@ def get_or_create_event_loop():
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import os
|
import os
|
||||||
import select
|
import select
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -29,24 +28,6 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
_mplayer_bin_default_args = ['-slave', '-quiet', '-idle', '-input',
|
_mplayer_bin_default_args = ['-slave', '-quiet', '-idle', '-input',
|
||||||
'nodefault-bindings', '-noconfig', 'all']
|
'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,
|
def __init__(self, mplayer_bin=None,
|
||||||
mplayer_timeout=_mplayer_default_communicate_timeout,
|
mplayer_timeout=_mplayer_default_communicate_timeout,
|
||||||
args=None, *argv, **kwargs):
|
args=None, *argv, **kwargs):
|
||||||
|
@ -74,14 +55,14 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
super().__init__(*argv, **kwargs)
|
super().__init__(*argv, **kwargs)
|
||||||
|
|
||||||
self.args = args or []
|
self.args = args or []
|
||||||
self._init_mplayer_bin()
|
self._init_mplayer_bin(mplayer_bin=mplayer_bin)
|
||||||
self._build_actions()
|
self._build_actions()
|
||||||
self._player = None
|
self._player = None
|
||||||
self._mplayer_timeout = mplayer_timeout
|
self._mplayer_timeout = mplayer_timeout
|
||||||
self._mplayer_stopped_event = threading.Event()
|
self._mplayer_stopped_event = threading.Event()
|
||||||
|
self._status_lock = threading.Lock()
|
||||||
self._is_playing_torrent = False
|
self._is_playing_torrent = False
|
||||||
|
|
||||||
|
|
||||||
def _init_mplayer_bin(self, mplayer_bin=None):
|
def _init_mplayer_bin(self, mplayer_bin=None):
|
||||||
if not mplayer_bin:
|
if not mplayer_bin:
|
||||||
bin_name = 'mplayer.exe' if os.name == 'nt' else 'mplayer'
|
bin_name = 'mplayer.exe' if os.name == 'nt' else 'mplayer'
|
||||||
|
@ -105,10 +86,10 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
def _init_mplayer(self, mplayer_args=None):
|
def _init_mplayer(self, mplayer_args=None):
|
||||||
if self._player:
|
if self._player:
|
||||||
try:
|
try:
|
||||||
self._player.quit()
|
self._player.terminate()
|
||||||
except:
|
except Exception as e:
|
||||||
self.logger.debug('Failed to quit mplayer before _exec: {}'.
|
self.logger.debug('Failed to quit mplayer before _exec: {}'.
|
||||||
format(str))
|
format(str(e)))
|
||||||
|
|
||||||
mplayer_args = mplayer_args or []
|
mplayer_args = mplayer_args or []
|
||||||
args = [self.mplayer_bin] + self._mplayer_bin_default_args
|
args = [self.mplayer_bin] + self._mplayer_bin_default_args
|
||||||
|
@ -137,7 +118,7 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
def args_pprint(txt):
|
def args_pprint(txt):
|
||||||
lc = txt.lower()
|
lc = txt.lower()
|
||||||
if lc[0] == '[':
|
if lc[0] == '[':
|
||||||
return '%s=None'%lc[1:-1]
|
return '%s=None' % lc[1:-1]
|
||||||
return lc
|
return lc
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
@ -170,18 +151,19 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
|
|
||||||
self._player.stdin.write(cmd)
|
self._player.stdin.write(cmd)
|
||||||
self._player.stdin.flush()
|
self._player.stdin.flush()
|
||||||
bus = get_bus()
|
|
||||||
|
|
||||||
if cmd_name == 'loadfile' or cmd_name == 'loadlist':
|
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':
|
elif cmd_name == 'pause':
|
||||||
bus.post(MediaPauseEvent())
|
self._post_event(MediaPauseEvent)
|
||||||
elif cmd_name == 'quit' or cmd_name == 'stop':
|
elif cmd_name == 'quit' or cmd_name == 'stop':
|
||||||
if cmd_name == 'quit':
|
if cmd_name == 'quit':
|
||||||
self._player.terminate()
|
self._player.terminate()
|
||||||
self._player.wait()
|
self._player.wait()
|
||||||
try: self._player.kill()
|
try:
|
||||||
except: pass
|
self._player.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
self._player = None
|
self._player = None
|
||||||
|
|
||||||
if not wait_for_response:
|
if not wait_for_response:
|
||||||
|
@ -200,12 +182,17 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
if line.startswith('ANS_'):
|
if line.startswith('ANS_'):
|
||||||
k, v = tuple(line[4:].split('='))
|
k, v = tuple(line[4:].split('='))
|
||||||
v = v.strip()
|
v = v.strip()
|
||||||
if v == 'yes': v = True
|
if v == 'yes':
|
||||||
elif v == 'no': v = False
|
v = True
|
||||||
|
elif v == 'no':
|
||||||
|
v = False
|
||||||
|
|
||||||
try: v = eval(v)
|
try:
|
||||||
except: pass
|
v = eval(v)
|
||||||
response = { k: v }
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = {k: v}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -222,8 +209,8 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def list_actions(self):
|
def list_actions(self):
|
||||||
return [ { 'action': action, 'args': self._actions[action] }
|
return [{'action': a, 'args': self._actions[a]}
|
||||||
for action in sorted(self._actions.keys()) ]
|
for a in sorted(self._actions.keys())]
|
||||||
|
|
||||||
def _process_monitor(self):
|
def _process_monitor(self):
|
||||||
def _thread():
|
def _thread():
|
||||||
|
@ -232,15 +219,21 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
|
|
||||||
self._mplayer_stopped_event.clear()
|
self._mplayer_stopped_event.clear()
|
||||||
self._player.wait()
|
self._player.wait()
|
||||||
try: self.quit()
|
try:
|
||||||
except: pass
|
self.quit()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
get_bus().post(MediaStopEvent())
|
self._post_event(MediaStopEvent)
|
||||||
self._mplayer_stopped_event.set()
|
self._mplayer_stopped_event.set()
|
||||||
self._player = None
|
self._player = None
|
||||||
|
|
||||||
return _thread
|
return _thread
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _post_event(evt_type, **evt):
|
||||||
|
bus = get_bus()
|
||||||
|
bus.post(evt_type(player='local', plugin='media.mplayer', **evt))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def play(self, resource, subtitles=None, mplayer_args=None):
|
def play(self, resource, subtitles=None, mplayer_args=None):
|
||||||
|
@ -258,7 +251,7 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
:type mplayer_args: list[str]
|
:type mplayer_args: list[str]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
get_bus().post(MediaPlayRequestEvent(resource=resource))
|
self._post_event(MediaPlayRequestEvent, resource=resource)
|
||||||
if subtitles:
|
if subtitles:
|
||||||
mplayer_args = mplayer_args or []
|
mplayer_args = mplayer_args or []
|
||||||
mplayer_args += ['-sub', self.get_subtitles_file(subtitles)]
|
mplayer_args += ['-sub', self.get_subtitles_file(subtitles)]
|
||||||
|
@ -271,76 +264,86 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
return get_plugin('media.webtorrent').play(resource)
|
return get_plugin('media.webtorrent').play(resource)
|
||||||
|
|
||||||
self._is_playing_torrent = False
|
self._is_playing_torrent = False
|
||||||
ret = self._exec('loadfile', resource, mplayer_args=mplayer_args)
|
self._exec('loadfile', resource, mplayer_args=mplayer_args)
|
||||||
get_bus().post(MediaPlayEvent(resource=resource))
|
self._post_event(MediaPlayEvent, resource=resource)
|
||||||
return ret
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def pause(self):
|
def pause(self):
|
||||||
""" Toggle the paused state """
|
""" Toggle the paused state """
|
||||||
ret = self._exec('pause')
|
self._exec('pause')
|
||||||
get_bus().post(MediaPauseEvent())
|
self._post_event(MediaPauseEvent)
|
||||||
return ret
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Stop the playback """
|
""" Stop the playback """
|
||||||
# return self._exec('stop')
|
# return self._exec('stop')
|
||||||
return self.quit()
|
self.quit()
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def quit(self):
|
def quit(self):
|
||||||
""" Quit the player """
|
""" Quit the player """
|
||||||
self._stop_torrent()
|
self._stop_torrent()
|
||||||
self._exec('quit')
|
self._exec('quit')
|
||||||
get_bus().post(MediaStopEvent())
|
self._post_event(MediaStopEvent)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def voldown(self, step=10.0):
|
def voldown(self, step=10.0):
|
||||||
""" Volume down by (default: 10)% """
|
""" Volume down by (default: 10)% """
|
||||||
return self._exec('volume', -step*10)
|
self._exec('volume', -step * 10)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def volup(self, step=10.0):
|
def volup(self, step=10.0):
|
||||||
""" Volume up by (default: 10)% """
|
""" Volume up by (default: 10)% """
|
||||||
return self._exec('volume', step*10)
|
self._exec('volume', step * 10)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def back(self, offset=60.0):
|
def back(self, offset=60.0):
|
||||||
""" Back by (default: 60) seconds """
|
""" Back by (default: 60) seconds """
|
||||||
return self.step_property('time_pos', -offset)
|
self.step_property('time_pos', -offset)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def forward(self, offset=60.0):
|
def forward(self, offset=60.0):
|
||||||
""" Forward by (default: 60) seconds """
|
""" Forward by (default: 60) seconds """
|
||||||
return self.step_property('time_pos', offset)
|
self.step_property('time_pos', offset)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def toggle_subtitles(self):
|
def toggle_subtitles(self):
|
||||||
""" Toggle the subtitles visibility """
|
""" Toggle the subtitles visibility """
|
||||||
subs = self.get_property('sub_visibility').output.get('sub_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
|
@action
|
||||||
def set_subtitles(self, filename, **args):
|
def add_subtitles(self, filename, **args):
|
||||||
""" Sets media subtitles from filename """
|
""" Sets media subtitles from filename """
|
||||||
self._exec('sub_visibility', 1)
|
self._exec('sub_visibility', 1)
|
||||||
return self._exec('sub_load', filename)
|
self._exec('sub_load', filename)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def remove_subtitles(self, index=None):
|
def remove_subtitles(self, index=None):
|
||||||
""" Removes the subtitle specified by the index (default: all) """
|
""" Removes the subtitle specified by the index (default: all) """
|
||||||
if index is None:
|
if index is None:
|
||||||
return self._exec('sub_remove')
|
self._exec('sub_remove')
|
||||||
else:
|
else:
|
||||||
return self._exec('sub_remove', index)
|
self._exec('sub_remove', index)
|
||||||
|
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def is_playing(self):
|
def is_playing(self):
|
||||||
"""
|
"""
|
||||||
:returns: True if it's playing, False otherwise
|
: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
|
@action
|
||||||
def load(self, resource, mplayer_args=None, **kwargs):
|
def load(self, resource, mplayer_args=None, **kwargs):
|
||||||
|
@ -354,17 +357,19 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
@action
|
@action
|
||||||
def mute(self):
|
def mute(self):
|
||||||
""" Toggle mute state """
|
""" Toggle mute state """
|
||||||
return self._exec('mute')
|
self._exec('mute')
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def seek(self, position):
|
def seek(self, position):
|
||||||
"""
|
"""
|
||||||
Seek backward/forward by the specified number of seconds
|
Seek backward/forward by the specified number of seconds
|
||||||
|
|
||||||
:param relative_position: Number of seconds relative to the current cursor
|
:param position: Number of seconds relative to the current cursor
|
||||||
:type relative_position: int
|
:type position: int
|
||||||
"""
|
"""
|
||||||
return self.step_property('time_pos', position)
|
self.step_property('time_pos', position)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_position(self, position):
|
def set_position(self, position):
|
||||||
|
@ -374,7 +379,8 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
:param position: Number of seconds from the start
|
:param position: Number of seconds from the start
|
||||||
:type position: int
|
:type position: int
|
||||||
"""
|
"""
|
||||||
return self.set_property('time_pos', position)
|
self.set_property('time_pos', position)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_volume(self, volume):
|
def set_volume(self, volume):
|
||||||
|
@ -384,7 +390,8 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
:param volume: Volume value between 0 and 100
|
:param volume: Volume value between 0 and 100
|
||||||
:type volume: float
|
:type volume: float
|
||||||
"""
|
"""
|
||||||
return self._exec('volume', volume)
|
self._exec('volume', volume)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def status(self):
|
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:
|
status = {}
|
||||||
paused = self.get_property('pause').output.get('pause')
|
props = {
|
||||||
if paused is True:
|
'duration': 'length',
|
||||||
state['state'] = PlayerState.PAUSE.value
|
'filename': 'filename',
|
||||||
elif paused is False:
|
'fullscreen': 'fullscreen',
|
||||||
state['state'] = PlayerState.PLAY.value
|
'mute': 'mute',
|
||||||
except:
|
'name': 'filename',
|
||||||
pass
|
'path': 'path',
|
||||||
finally:
|
'pause': 'pause',
|
||||||
return state
|
'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
|
@action
|
||||||
def get_property(self, property, args=None):
|
def get_property(self, property, args=None):
|
||||||
|
@ -483,5 +510,4 @@ class MediaMplayerPlugin(MediaPlugin):
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -230,7 +230,7 @@ class MediaMpvPlugin(MediaPlugin):
|
||||||
pos = min(self._player.time_pos+self._player.time_remaining,
|
pos = min(self._player.time_pos+self._player.time_remaining,
|
||||||
max(0, position))
|
max(0, position))
|
||||||
self._player.time_pos = pos
|
self._player.time_pos = pos
|
||||||
return {'position': pos}
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def back(self, offset=60.0):
|
def back(self, offset=60.0):
|
||||||
|
@ -272,6 +272,16 @@ class MediaMpvPlugin(MediaPlugin):
|
||||||
""" Toggle the subtitles visibility """
|
""" Toggle the subtitles visibility """
|
||||||
return self.toggle_property('sub_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
|
@action
|
||||||
def toggle_fullscreen(self, fullscreen=None):
|
def toggle_fullscreen(self, fullscreen=None):
|
||||||
""" Toggle the fullscreen mode """
|
""" Toggle the fullscreen mode """
|
||||||
|
@ -385,91 +395,29 @@ class MediaMpvPlugin(MediaPlugin):
|
||||||
return {'state': PlayerState.STOP.value}
|
return {'state': PlayerState.STOP.value}
|
||||||
|
|
||||||
return {
|
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_channels': getattr(self._player, 'audio_channels'),
|
||||||
'audio_codec': getattr(self._player, 'audio_codec_name'),
|
'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'),
|
'delay': getattr(self._player, 'delay'),
|
||||||
'displays': getattr(self._player, 'display_names'),
|
|
||||||
'duration': getattr(self._player, 'playback_time', 0) +
|
'duration': getattr(self._player, 'playback_time', 0) +
|
||||||
getattr(self._player, 'playtime_remaining', 0)
|
getattr(self._player, 'playtime_remaining', 0)
|
||||||
if getattr(self._player, 'playtime_remaining') else None,
|
if getattr(self._player, 'playtime_remaining') else None,
|
||||||
'file_format': getattr(self._player, 'file_format'),
|
|
||||||
'filename': getattr(self._player, 'filename'),
|
'filename': getattr(self._player, 'filename'),
|
||||||
'file_size': getattr(self._player, 'file_size'),
|
'file_size': getattr(self._player, 'file_size'),
|
||||||
'font': getattr(self._player, 'font'),
|
|
||||||
'fps': getattr(self._player, 'fps'),
|
|
||||||
'fullscreen': getattr(self._player, 'fs'),
|
'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'),
|
'mute': getattr(self._player, 'mute'),
|
||||||
'name': getattr(self._player, 'name'),
|
'name': getattr(self._player, 'name'),
|
||||||
'pause': getattr(self._player, 'pause'),
|
'pause': getattr(self._player, 'pause'),
|
||||||
'percent_pos': getattr(self._player, 'percent_pos'),
|
'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'),
|
'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'),
|
'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),
|
'state': (PlayerState.PAUSE.value if self._player.pause else PlayerState.PLAY.value),
|
||||||
'stream_pos': getattr(self._player, 'stream_pos'),
|
'title': getattr(self._player, 'media_title') or getattr(self._player, 'filename'),
|
||||||
'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'),
|
|
||||||
'url': self._get_current_resource(),
|
'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_codec': getattr(self._player, 'video_codec'),
|
||||||
'video_format': getattr(self._player, 'video_format'),
|
'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': getattr(self._player, 'volume'),
|
||||||
'volume_max': getattr(self._player, 'volume_max'),
|
'volume_max': getattr(self._player, 'volume_max'),
|
||||||
'width': getattr(self._player, 'width'),
|
'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):
|
def on_stop(self, callback):
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
import enum
|
import enum
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import urllib.request
|
|
||||||
import urllib.parse
|
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.plugins.media import MediaPlugin, PlayerState
|
||||||
from platypush.message.event.video import VideoPlayEvent, VideoPauseEvent, \
|
from platypush.message.event.media import MediaPlayEvent, MediaPauseEvent, MediaStopEvent, MediaSeekEvent
|
||||||
VideoStopEvent, NewPlayingVideoEvent
|
|
||||||
|
|
||||||
from platypush.plugins import action
|
from platypush.plugins import action
|
||||||
|
|
||||||
|
@ -30,7 +24,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
* **omxplayer-wrapper** (``pip install omxplayer-wrapper``)
|
* **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
|
:param args: Arguments that will be passed to the OMXPlayer constructor
|
||||||
(e.g. subtitles, volume, start position, window size etc.) see
|
(e.g. subtitles, volume, start position, window size etc.) see
|
||||||
|
@ -41,9 +35,12 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
|
|
||||||
super().__init__(*argv, **kwargs)
|
super().__init__(*argv, **kwargs)
|
||||||
|
|
||||||
|
if args is None:
|
||||||
|
args = []
|
||||||
|
|
||||||
self.args = args
|
self.args = args
|
||||||
self._player = None
|
self._player = None
|
||||||
self._handlers = { e.value: [] for e in PlayerEvent }
|
self._handlers = {e.value: [] for e in PlayerEvent}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def play(self, resource, subtitles=None, *args, **kwargs):
|
def play(self, resource, subtitles=None, *args, **kwargs):
|
||||||
|
@ -56,6 +53,8 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
* Remote videos (format: ``https://<url>/<resource>``)
|
* Remote videos (format: ``https://<url>/<resource>``)
|
||||||
* YouTube videos (format: ``https://www.youtube.com/watch?v=<id>``)
|
* YouTube videos (format: ``https://www.youtube.com/watch?v=<id>``)
|
||||||
* Torrents (format: Magnet links, Torrent URLs or local Torrent files)
|
* Torrents (format: Magnet links, Torrent URLs or local Torrent files)
|
||||||
|
|
||||||
|
:param subtitles: Subtitles file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if subtitles:
|
if subtitles:
|
||||||
|
@ -69,7 +68,9 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception(e)
|
self.logger.exception(e)
|
||||||
self.logger.warning('Unable to stop a previously running instance ' +
|
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:
|
try:
|
||||||
from omxplayer import OMXPlayer
|
from omxplayer import OMXPlayer
|
||||||
|
@ -77,7 +78,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
self._init_player_handlers()
|
self._init_player_handlers()
|
||||||
except DBusException as e:
|
except DBusException as e:
|
||||||
self.logger.warning('DBus connection failed: you will probably not ' +
|
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)
|
self.logger.exception(e)
|
||||||
|
|
||||||
return self.status()
|
return self.status()
|
||||||
|
@ -85,15 +86,16 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
@action
|
@action
|
||||||
def pause(self):
|
def pause(self):
|
||||||
""" Pause the playback """
|
""" Pause the playback """
|
||||||
if self._player: self._player.play_pause()
|
if self._player:
|
||||||
|
self._player.play_pause()
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop(self):
|
def stop(self):
|
||||||
""" Stop the playback """
|
""" Stop the playback """
|
||||||
if self._player:
|
if self._player:
|
||||||
self._player.stop()
|
self._player.stop()
|
||||||
|
return {'status': 'stop'}
|
||||||
return {'status':'stop'}
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def quit(self):
|
def quit(self):
|
||||||
|
@ -103,8 +105,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
self._player.quit()
|
self._player.quit()
|
||||||
self._player = None
|
self._player = None
|
||||||
|
|
||||||
return {'status':'stop'}
|
return {'status': 'stop'}
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def voldown(self):
|
def voldown(self):
|
||||||
|
@ -131,7 +132,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
def forward(self, offset=60):
|
def forward(self, offset=60):
|
||||||
""" Forward by (default: 60) seconds """
|
""" Forward by (default: 60) seconds """
|
||||||
if self._player:
|
if self._player:
|
||||||
self._player.seek(+offset)
|
self._player.seek(offset)
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -140,20 +141,24 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
if self._player:
|
if self._player:
|
||||||
self._player.stop()
|
self._player.stop()
|
||||||
|
|
||||||
if self.videos_queue:
|
if self._videos_queue:
|
||||||
video = self.videos_queue.pop(0)
|
video = self._videos_queue.pop(0)
|
||||||
return self.play(video)
|
self.play(video)
|
||||||
|
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def hide_subtitles(self):
|
def hide_subtitles(self):
|
||||||
""" Hide the subtitles """
|
""" Hide the subtitles """
|
||||||
if self._player: self._player.hide_subtitles()
|
if self._player:
|
||||||
|
self._player.hide_subtitles()
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def hide_video(self):
|
def hide_video(self):
|
||||||
""" Hide the video """
|
""" Hide the video """
|
||||||
if self._player: self._player.hide_video()
|
if self._player:
|
||||||
|
self._player.hide_video()
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -162,19 +167,22 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
:returns: True if it's playing, False otherwise
|
:returns: True if it's playing, False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._player: return self._player.is_playing()
|
return self._player.is_playing()
|
||||||
else: return False
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def load(self, resource, pause=False):
|
def load(self, resource, pause=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Load a resource/video in the player.
|
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)
|
:param pause: If set, load the video in paused mode (default: False)
|
||||||
:type pause: bool
|
:type pause: bool
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._player: self._player.load(resource, )
|
if self._player:
|
||||||
|
self._player.load(resource, pause=pause)
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -187,13 +195,15 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
@action
|
@action
|
||||||
def mute(self):
|
def mute(self):
|
||||||
""" Mute the player """
|
""" Mute the player """
|
||||||
if self._player: self._player.mute()
|
if self._player:
|
||||||
|
self._player.mute()
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def unmute(self):
|
def unmute(self):
|
||||||
""" Unmute the player """
|
""" Unmute the player """
|
||||||
if self._player: self._player.unmute()
|
if self._player:
|
||||||
|
self._player.unmute()
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -205,7 +215,8 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
:type relative_position: int
|
:type relative_position: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._player: self._player.seek(relative_position)
|
if self._player:
|
||||||
|
self._player.seek(relative_position)
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -217,7 +228,8 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
:type position: int
|
:type position: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._player: self._player.set_seek(position)
|
if self._player:
|
||||||
|
self._player.set_seek(position)
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -231,7 +243,8 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
|
|
||||||
# Transform a [0,100] value to an OMXPlayer volume in [-6000,0]
|
# Transform a [0,100] value to an OMXPlayer volume in [-6000,0]
|
||||||
volume = 60.0*volume - 6000
|
volume = 60.0*volume - 6000
|
||||||
if self._player: self._player.set_volume(volume)
|
if self._player:
|
||||||
|
self._player.set_volume(volume)
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -254,41 +267,35 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
state = PlayerState.STOP.value
|
|
||||||
|
|
||||||
if self._player:
|
if self._player:
|
||||||
state = self._player.playback_status().lower()
|
state = self._player.playback_status().lower()
|
||||||
if state == 'playing': state = PlayerState.PLAY.value
|
if state == 'playing':
|
||||||
elif state == 'stopped': state = PlayerState.STOP.value
|
state = PlayerState.PLAY.value
|
||||||
elif state == 'paused': state = PlayerState.PAUSE.value
|
elif state == 'stopped':
|
||||||
|
state = PlayerState.STOP.value
|
||||||
|
elif state == 'paused':
|
||||||
|
state = PlayerState.PAUSE.value
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'source': self._player.get_source(),
|
|
||||||
'state': state,
|
|
||||||
'volume': self._player.volume(),
|
|
||||||
'elapsed': self._player.position(),
|
|
||||||
'duration': self._player.duration(),
|
'duration': self._player.duration(),
|
||||||
'width': self._player.width(),
|
'filename': urllib.parse.unquote(self._player.get_source()).split('/')[-1],
|
||||||
'height': self._player.height(),
|
'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:
|
else:
|
||||||
return {
|
return {
|
||||||
'state': PlayerState.STOP.value
|
'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):
|
def add_handler(self, event_type, callback):
|
||||||
if event_type not in self._handlers.keys():
|
if event_type not in self._handlers.keys():
|
||||||
raise AttributeError('{} is not a valid PlayerEvent type'.
|
raise AttributeError('{} is not a valid PlayerEvent type'.
|
||||||
|
@ -296,31 +303,40 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
|
|
||||||
self._handlers[event_type].append(callback)
|
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 on_play(self):
|
||||||
def _f(player):
|
def _f(player):
|
||||||
video = self._player.get_source()
|
resource = player.get_source()
|
||||||
self.send_message(VideoPlayEvent(video=video))
|
self._post_event(MediaPlayEvent, resource=resource)
|
||||||
for callback in self._handlers[PlayerEvent.PLAY.value]:
|
for callback in self._handlers[PlayerEvent.PLAY.value]:
|
||||||
callback(video)
|
callback(resource)
|
||||||
|
|
||||||
return _f
|
return _f
|
||||||
|
|
||||||
def on_pause(self):
|
def on_pause(self):
|
||||||
def _f(player):
|
def _f(player):
|
||||||
video = self._player.get_source()
|
resource = player.get_source()
|
||||||
self.send_message(VideoPauseEvent(video=video))
|
self._post_event(MediaPauseEvent, resource=resource)
|
||||||
for callback in self._handlers[PlayerEvent.PAUSE.value]:
|
for callback in self._handlers[PlayerEvent.PAUSE.value]:
|
||||||
callback(video)
|
callback(resource)
|
||||||
|
|
||||||
return _f
|
return _f
|
||||||
|
|
||||||
def on_stop(self):
|
def on_stop(self):
|
||||||
def _f(player):
|
def _f(player):
|
||||||
self.send_message(VideoStopEvent())
|
self._post_event(MediaStopEvent)
|
||||||
for callback in self._handlers[PlayerEvent.STOP.value]:
|
for callback in self._handlers[PlayerEvent.STOP.value]:
|
||||||
callback()
|
callback()
|
||||||
return _f
|
return _f
|
||||||
|
|
||||||
|
def on_seek(self):
|
||||||
|
def _f(player):
|
||||||
|
self._post_event(MediaSeekEvent, position=player.position())
|
||||||
|
return _f
|
||||||
|
|
||||||
def _init_player_handlers(self):
|
def _init_player_handlers(self):
|
||||||
if not self._player:
|
if not self._player:
|
||||||
|
@ -329,6 +345,7 @@ class MediaOmxplayerPlugin(MediaPlugin):
|
||||||
self._player.playEvent += self.on_play()
|
self._player.playEvent += self.on_play()
|
||||||
self._player.pauseEvent += self.on_pause()
|
self._player.pauseEvent += self.on_pause()
|
||||||
self._player.stopEvent += self.on_stop()
|
self._player.stopEvent += self.on_stop()
|
||||||
|
self._player.positionEvent += self.on_seek()
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
from platypush.context import get_bus, get_plugin
|
from platypush.context import get_bus, get_plugin
|
||||||
from platypush.plugins.media import PlayerState, MediaPlugin
|
from platypush.plugins.media import PlayerState, MediaPlugin
|
||||||
|
@ -45,7 +46,6 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
self._default_volume = volume
|
self._default_volume = volume
|
||||||
self._on_stop_callbacks = []
|
self._on_stop_callbacks = []
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _watched_event_types(cls):
|
def _watched_event_types(cls):
|
||||||
import vlc
|
import vlc
|
||||||
|
@ -64,7 +64,7 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
import vlc
|
import vlc
|
||||||
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._instance = vlc.Instance(*self._args)
|
self._instance = vlc.Instance(*self._args)
|
||||||
|
@ -84,44 +84,47 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
self._instance.release()
|
self._instance.release()
|
||||||
self._instance = None
|
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 _event_callback(self):
|
||||||
def callback(event):
|
def callback(event):
|
||||||
from vlc import EventType
|
from vlc import EventType
|
||||||
self.logger.debug('Received vlc event: {}'.format(event))
|
self.logger.debug('Received vlc event: {}'.format(event))
|
||||||
bus = get_bus()
|
|
||||||
|
|
||||||
if event.type == EventType.MediaPlayerPlaying:
|
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:
|
elif event.type == EventType.MediaPlayerPaused:
|
||||||
bus.post(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._reset_state()
|
||||||
bus.post(MediaStopEvent())
|
self._post_event(MediaStopEvent)
|
||||||
for callback in self._on_stop_callbacks:
|
for cbk in self._on_stop_callbacks:
|
||||||
callback()
|
cbk()
|
||||||
elif event.type == EventType.MediaPlayerTitleChanged:
|
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:
|
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:
|
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:
|
elif event.type == EventType.MediaPlayerTimeChanged:
|
||||||
pos = float(event.u.new_time/1000)
|
pos = float(event.u.new_time/1000)
|
||||||
if self._latest_seek is None or \
|
if self._latest_seek is None or \
|
||||||
abs(pos-self._latest_seek) > 5:
|
abs(pos-self._latest_seek) > 5:
|
||||||
bus.post(MediaSeekEvent(position=pos))
|
self._post_event(MediaSeekEvent, position=pos)
|
||||||
self._latest_seek = pos
|
self._latest_seek = pos
|
||||||
elif event.type == EventType.MediaPlayerAudioVolume:
|
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:
|
elif event.type == EventType.MediaPlayerMuted:
|
||||||
bus.post(MediaMuteChangedEvent(mute=True))
|
self._post_event(MediaMuteChangedEvent, mute=True)
|
||||||
elif event.type == EventType.MediaPlayerUnmuted:
|
elif event.type == EventType.MediaPlayerUnmuted:
|
||||||
bus.post(MediaMuteChangedEvent(mute=False))
|
self._post_event(MediaMuteChangedEvent, mute=False)
|
||||||
|
|
||||||
return callback
|
return callback
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def play(self, resource, subtitles=None, fullscreen=None, volume=None):
|
def play(self, resource, subtitles=None, fullscreen=None, volume=None):
|
||||||
"""
|
"""
|
||||||
|
@ -169,14 +172,13 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
|
|
||||||
return self.status()
|
return self.status()
|
||||||
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def pause(self):
|
def pause(self):
|
||||||
""" Toggle the paused state """
|
""" Toggle the paused state """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
if not self._player.can_pause():
|
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()
|
self._player.pause()
|
||||||
return self.status()
|
return self.status()
|
||||||
|
@ -186,10 +188,11 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
""" Quit the player (same as `stop`) """
|
""" Quit the player (same as `stop`) """
|
||||||
self._stop_torrent()
|
self._stop_torrent()
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
|
|
||||||
self._player.stop()
|
self._player.stop()
|
||||||
return { 'state': PlayerState.STOP.value }
|
self._player = None
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
@ -200,14 +203,14 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
def voldown(self, step=10.0):
|
def voldown(self, step=10.0):
|
||||||
""" Volume down by (default: 10)% """
|
""" Volume down by (default: 10)% """
|
||||||
if not self._player:
|
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)
|
return self.set_volume(self._player.audio_get_volume()-step)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def volup(self, step=10.0):
|
def volup(self, step=10.0):
|
||||||
""" Volume up by (default: 10)% """
|
""" Volume up by (default: 10)% """
|
||||||
if not self._player:
|
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)
|
return self.set_volume(self._player.audio_get_volume()+step)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -219,11 +222,13 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
:type volume: float
|
:type volume: float
|
||||||
"""
|
"""
|
||||||
if not self._player:
|
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)
|
self._player.audio_set_volume(volume)
|
||||||
return { 'volume': volume }
|
status = self.status().output
|
||||||
|
status['volume'] = volume
|
||||||
|
return status
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def seek(self, position):
|
def seek(self, position):
|
||||||
|
@ -234,27 +239,27 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
:type position: int
|
:type position: int
|
||||||
"""
|
"""
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
if not self._player.is_seekable():
|
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()
|
media = self._player.get_media()
|
||||||
if not media:
|
if not media:
|
||||||
return (None, 'No media loaded')
|
return None, 'No media loaded'
|
||||||
|
|
||||||
pos = min(media.get_duration()/1000, max(0, position))
|
pos = min(media.get_duration()/1000, max(0, position))
|
||||||
self._player.set_time(int(pos*1000))
|
self._player.set_time(int(pos*1000))
|
||||||
return { 'position': pos }
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def back(self, offset=60.0):
|
def back(self, offset=60.0):
|
||||||
""" Back by (default: 60) seconds """
|
""" Back by (default: 60) seconds """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
|
|
||||||
media = self._player.get_media()
|
media = self._player.get_media()
|
||||||
if not media:
|
if not media:
|
||||||
return (None, 'No media loaded')
|
return None, 'No media loaded'
|
||||||
|
|
||||||
pos = max(0, (self._player.get_time()/1000)-offset)
|
pos = max(0, (self._player.get_time()/1000)-offset)
|
||||||
return self.seek(pos)
|
return self.seek(pos)
|
||||||
|
@ -263,11 +268,11 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
def forward(self, offset=60.0):
|
def forward(self, offset=60.0):
|
||||||
""" Forward by (default: 60) seconds """
|
""" Forward by (default: 60) seconds """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
|
|
||||||
media = self._player.get_media()
|
media = self._player.get_media()
|
||||||
if not 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)
|
pos = min(media.get_duration()/1000, (self._player.get_time()/1000)+offset)
|
||||||
return self.seek(pos)
|
return self.seek(pos)
|
||||||
|
@ -276,10 +281,10 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
def toggle_subtitles(self, visibile=None):
|
def toggle_subtitles(self, visibile=None):
|
||||||
""" Toggle the subtitles visibility """
|
""" Toggle the subtitles visibility """
|
||||||
if not self._player:
|
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:
|
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 \
|
if self._player.video_get_spu() is None or \
|
||||||
self._player.video_get_spu() == -1:
|
self._player.video_get_spu() == -1:
|
||||||
|
@ -291,21 +296,21 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
def toggle_fullscreen(self):
|
def toggle_fullscreen(self):
|
||||||
""" Toggle the fullscreen mode """
|
""" Toggle the fullscreen mode """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
self._player.toggle_fullscreen()
|
self._player.toggle_fullscreen()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_fullscreen(self, fullscreen=True):
|
def set_fullscreen(self, fullscreen=True):
|
||||||
""" Set fullscreen mode """
|
""" Set fullscreen mode """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
self._player.set_fullscreen(fullscreen)
|
self._player.set_fullscreen(fullscreen)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_subtitles(self, filename, **args):
|
def set_subtitles(self, filename, **args):
|
||||||
""" Sets media subtitles from filename """
|
""" Sets media subtitles from filename """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
if filename.startswith('file://'):
|
if filename.startswith('file://'):
|
||||||
filename = filename[len('file://'):]
|
filename = filename[len('file://'):]
|
||||||
|
|
||||||
|
@ -315,7 +320,7 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
def remove_subtitles(self):
|
def remove_subtitles(self):
|
||||||
""" Removes (hides) the subtitles """
|
""" Removes (hides) the subtitles """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
self._player.video_set_spu(-1)
|
self._player.video_set_spu(-1)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -334,13 +339,14 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
"""
|
"""
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return self.play(resource, **args)
|
return self.play(resource, **args)
|
||||||
return self._player.set_media(resource)
|
self._player.set_media(resource)
|
||||||
|
return self.status()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def mute(self):
|
def mute(self):
|
||||||
""" Toggle mute state """
|
""" Toggle mute state """
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return (None, 'No vlc instance is running')
|
return None, 'No vlc instance is running'
|
||||||
self._player.audio_toggle_mute()
|
self._player.audio_toggle_mute()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -366,31 +372,36 @@ class MediaVlcPlugin(MediaPlugin):
|
||||||
"""
|
"""
|
||||||
import vlc
|
import vlc
|
||||||
if not self._player:
|
if not self._player:
|
||||||
return { 'state': PlayerState.STOP.value }
|
return {'state': PlayerState.STOP.value}
|
||||||
|
|
||||||
|
status = {}
|
||||||
vlc_state = self._player.get_state()
|
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:
|
if vlc_state == vlc.State.Playing:
|
||||||
state = PlayerState.PLAY.value
|
status['state'] = PlayerState.PLAY.value
|
||||||
if vlc_state == vlc.State.Paused:
|
elif vlc_state == vlc.State.Paused:
|
||||||
state = PlayerState.PAUSE.value
|
status['state'] = PlayerState.PAUSE.value
|
||||||
|
else:
|
||||||
|
status['state'] = PlayerState.STOP.value
|
||||||
|
|
||||||
time = float(self._player.get_time()/1000) if self._player.get_time() \
|
status['url'] = self._player.get_media().get_mrl() if self._player.get_media() else None
|
||||||
is not None 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()
|
media = self._player.get_media()
|
||||||
duration = media.get_duration()/1000 if media and media.get_duration() \
|
status['duration'] = media.get_duration()/1000 if media and media.get_duration() is not None else None
|
||||||
is not None else None
|
|
||||||
|
|
||||||
return {
|
status['seekable'] = status['duration'] is not None
|
||||||
'filename': filename,
|
status['fullscreen'] = self._player.get_fullscreen()
|
||||||
'state': state,
|
status['mute'] = self._player.audio_get_mute()
|
||||||
'time': time,
|
status['path'] = urllib.parse.unquote(status['url'])
|
||||||
'duration': duration,
|
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):
|
def on_stop(self, callback):
|
||||||
self._on_stop_callbacks.append(callback)
|
self._on_stop_callbacks.append(callback)
|
||||||
|
|
Loading…
Reference in a new issue