platypush/platypush/plugins/media/omxplayer.py

335 lines
9.3 KiB
Python

import enum
import os
import re
import subprocess
import urllib.request
import urllib.parse
from platypush.context import get_backend, get_plugin
from platypush.plugins.media import MediaPlugin, PlayerState
from platypush.message.event.video import VideoPlayEvent, VideoPauseEvent, \
VideoStopEvent, NewPlayingVideoEvent
from platypush.plugins import action
class PlayerEvent(enum.Enum):
STOP = 'stop'
PLAY = 'play'
PAUSE = 'pause'
class MediaOmxplayerPlugin(MediaPlugin):
"""
Plugin to control video and media playback using OMXPlayer.
Requires:
* **omxplayer** installed on your system (see your distro instructions)
* **omxplayer-wrapper** (``pip install omxplayer-wrapper``)
"""
def __init__(self, args=[], *argv, **kwargs):
"""
:param args: Arguments that will be passed to the OMXPlayer constructor
(e.g. subtitles, volume, start position, window size etc.) see
https://github.com/popcornmix/omxplayer#synopsis and
http://python-omxplayer-wrapper.readthedocs.io/en/latest/omxplayer/#omxplayer.player.OMXPlayer
:type args: list
"""
super().__init__(*argv, **kwargs)
self.args = args
self._player = None
self._handlers = { e.value: [] for e in PlayerEvent }
@action
def play(self, resource, subtitles=None, *args, **kwargs):
"""
Play a resource.
:param resource: Resource to play. Supported types:
* Local files (format: ``file://<path>/<file>``)
* Remote videos (format: ``https://<url>/<resource>``)
* YouTube videos (format: ``https://www.youtube.com/watch?v=<id>``)
* Torrents (format: Magnet links, Torrent URLs or local Torrent files)
"""
if subtitles:
args.append('--subtitles', subtitles)
resource = self._get_resource(resource)
if self._player:
try:
self._player.stop()
self._player = None
except Exception as e:
self.logger.exception(e)
self.logger.warning('Unable to stop a previously running instance ' +
'of OMXPlayer, trying to play anyway')
try:
from omxplayer import OMXPlayer
self._player = OMXPlayer(resource, args=self.args)
self._init_player_handlers()
except DBusException as e:
self.logger.warning('DBus connection failed: you will probably not ' +
'be able to control the media')
self.logger.exception(e)
return self.status()
@action
def pause(self):
""" Pause the playback """
if self._player: self._player.play_pause()
@action
def stop(self):
""" Stop the playback """
if self._player:
self._player.stop()
return {'status':'stop'}
@action
def quit(self):
""" Quit the player """
if self._player:
self._player.stop()
self._player.quit()
self._player = None
return {'status':'stop'}
@action
def voldown(self):
""" Volume down by 10% """
if self._player:
self._player.set_volume(max(-6000, self._player.volume()-1000))
return self.status()
@action
def volup(self):
""" Volume up by 10% """
if self._player:
self._player.set_volume(min(0, self._player.volume()+1000))
return self.status()
@action
def back(self, offset=60):
""" Back by (default: 60) seconds """
if self._player:
self._player.seek(-offset)
return self.status()
@action
def forward(self, offset=60):
""" Forward by (default: 60) seconds """
if self._player:
self._player.seek(+offset)
return self.status()
@action
def next(self):
""" Play the next track/video """
if self._player:
self._player.stop()
if self.videos_queue:
video = self.videos_queue.pop(0)
return self.play(video)
@action
def hide_subtitles(self):
""" Hide the 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()
return self.status()
@action
def is_playing(self):
"""
:returns: True if it's playing, False otherwise
"""
if self._player: return self._player.is_playing()
else: return False
@action
def load(self, resource, pause=False):
"""
Load a resource/video in the player.
:param pause: If set, load the video in paused mode (default: False)
:type pause: bool
"""
if self._player: self._player.load(resource, pause)
return self.status()
@action
def metadata(self):
""" Get the metadata of the current video """
if self._player:
return self._player.metadata()
return self.status()
@action
def mute(self):
""" Mute the player """
if self._player: self._player.mute()
return self.status()
@action
def unmute(self):
""" Unmute the player """
if self._player: self._player.unmute()
return self.status()
@action
def seek(self, relative_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
"""
if self._player: self._player.seek(relative_position)
return self.status()
@action
def set_position(self, position):
"""
Seek backward/forward to the specified absolute position
:param position: Number of seconds from the start
:type position: int
"""
if self._player: self._player.set_seek(position)
return self.status()
@action
def set_volume(self, volume):
"""
Set the volume
:param volume: Volume value between 0 and 100
:type volume: int
"""
# 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)
return self.status()
@action
def status(self):
"""
Get the current player state.
:returns: A dictionary containing the current state.
Example::
output = {
"source": "https://www.youtube.com/watch?v=7L9KkZoNZkA",
"state": "play",
"volume": 80,
"elapsed": 123,
"duration": 300,
"width": 800,
"height": 600
}
"""
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
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(),
}
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'.
format(event_type))
self._handlers[event_type].append(callback)
def on_play(self):
def _f(player):
video = self._player.get_source()
self.send_message(VideoPlayEvent(video=video))
for callback in self._handlers[PlayerEvent.PLAY.value]:
callback(video)
return _f
def on_pause(self):
def _f(player):
video = self._player.get_source()
self.send_message(VideoPauseEvent(video=video))
for callback in self._handlers[PlayerEvent.PAUSE.value]:
callback(video)
return _f
def on_stop(self):
def _f(player):
self.send_message(VideoStopEvent())
for callback in self._handlers[PlayerEvent.STOP.value]:
callback()
return _f
def _init_player_handlers(self):
if not self._player:
return
self._player.playEvent += self.on_play()
self._player.pauseEvent += self.on_pause()
self._player.stopEvent += self.on_stop()
# vim:sw=4:ts=4:et: