2019-02-18 01:17:21 +01:00
|
|
|
import os
|
2024-01-13 22:36:42 +01:00
|
|
|
from dataclasses import asdict
|
|
|
|
from typing import Any, Dict, Optional, Type
|
|
|
|
from urllib.parse import quote
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
from platypush.plugins import action
|
2019-02-18 01:17:21 +01:00
|
|
|
from platypush.plugins.media import PlayerState, MediaPlugin
|
2022-09-30 10:41:56 +02:00
|
|
|
from platypush.message.event.media import (
|
2024-01-13 22:36:42 +01:00
|
|
|
MediaEvent,
|
2022-09-30 10:41:56 +02:00
|
|
|
MediaPlayEvent,
|
|
|
|
MediaPlayRequestEvent,
|
|
|
|
MediaPauseEvent,
|
|
|
|
MediaStopEvent,
|
|
|
|
NewPlayingMediaEvent,
|
|
|
|
MediaSeekEvent,
|
|
|
|
MediaResumeEvent,
|
|
|
|
)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
class MediaMpvPlugin(MediaPlugin):
|
|
|
|
"""
|
2023-09-24 16:54:43 +02:00
|
|
|
Plugin to control MPV instances.
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
_default_mpv_args = {
|
|
|
|
'ytdl': True,
|
|
|
|
'start_event_thread': True,
|
|
|
|
}
|
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
def __init__(
|
|
|
|
self, args: Optional[Dict[str, Any]] = None, fullscreen: bool = False, **kwargs
|
|
|
|
):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
|
|
|
Create the MPV wrapper.
|
|
|
|
|
|
|
|
:param args: Default arguments that will be passed to the mpv executable
|
2019-02-19 00:15:03 +01:00
|
|
|
as a key-value dict (names without the `--` prefix). See `man mpv`
|
|
|
|
for available options.
|
2024-01-13 22:36:42 +01:00
|
|
|
:param fullscreen: Set to True if you want media files to be opened in
|
|
|
|
fullscreen by default (can be overridden by `.play()`) (default: False)
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
super().__init__(**kwargs)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
self.args = {**self._default_mpv_args}
|
2019-02-19 00:15:03 +01:00
|
|
|
if args:
|
|
|
|
self.args.update(args)
|
2024-01-13 22:36:42 +01:00
|
|
|
if fullscreen:
|
|
|
|
self.args['fs'] = True
|
2019-02-19 00:15:03 +01:00
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
self._player = None
|
2024-01-13 22:36:42 +01:00
|
|
|
self._latest_state = PlayerState.STOP
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
def _init_mpv(self, args=None):
|
|
|
|
import mpv
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
mpv_args = self.args.copy()
|
|
|
|
if args:
|
|
|
|
mpv_args.update(args)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-07-01 19:32:22 +02:00
|
|
|
for k, v in self._env.items():
|
2019-02-19 00:58:26 +01:00
|
|
|
os.environ[k] = v
|
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
self._player = mpv.MPV(**mpv_args)
|
2020-02-21 18:29:40 +01:00
|
|
|
self._player._event_callbacks += [self._event_callback()]
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
def _post_event(self, evt_type: Type[MediaEvent], **evt):
|
|
|
|
self._bus.post(
|
|
|
|
evt_type(
|
|
|
|
player='local',
|
|
|
|
plugin='media.mpv',
|
|
|
|
resource=evt.pop('resource', self._resource),
|
|
|
|
title=self._filename,
|
|
|
|
**evt,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _cur_player(self):
|
|
|
|
if self._player and not self._player.core_shutdown:
|
|
|
|
return self._player
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _state(self):
|
|
|
|
player = self._cur_player
|
|
|
|
if not player:
|
|
|
|
return PlayerState.STOP
|
|
|
|
|
|
|
|
return PlayerState.PAUSE if player.pause else PlayerState.PLAY
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _resource(self):
|
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
cur_resource = self._cur_player.stream_path
|
|
|
|
if not cur_resource:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return quote(
|
|
|
|
('file://' if os.path.isfile(cur_resource) else '') + str(cur_resource)
|
|
|
|
)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def _filename(self):
|
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self._cur_player.filename
|
2019-06-22 00:15:32 +02:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
def _event_callback(self):
|
|
|
|
def callback(event):
|
2024-01-13 22:36:42 +01:00
|
|
|
from mpv import MpvEvent
|
2019-02-19 13:13:17 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
self.logger.info('Received mpv event: %s', event)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2022-09-30 10:41:56 +02:00
|
|
|
if isinstance(event, MpvEvent):
|
|
|
|
event = event.as_dict()
|
2024-01-13 22:36:42 +01:00
|
|
|
if not isinstance(event, dict):
|
|
|
|
return
|
2022-09-30 10:41:56 +02:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
evt_type = event.get('event', b'').decode()
|
|
|
|
if not evt_type:
|
2019-02-19 00:15:03 +01:00
|
|
|
return
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
if evt_type == 'start-file':
|
|
|
|
self._post_event(NewPlayingMediaEvent)
|
|
|
|
elif evt_type == 'playback-restart':
|
|
|
|
self._post_event(MediaPlayEvent)
|
|
|
|
elif evt_type in ('shutdown', 'idle', 'end-file'):
|
|
|
|
if self._state != PlayerState.PLAY:
|
|
|
|
self._post_event(MediaStopEvent)
|
|
|
|
|
|
|
|
if evt_type == 'shutdown' and self._player:
|
|
|
|
self._player = None
|
|
|
|
elif evt_type == 'seek' and self._cur_player:
|
2022-09-30 10:41:56 +02:00
|
|
|
self._post_event(
|
2024-01-13 22:36:42 +01:00
|
|
|
MediaSeekEvent, position=self._cur_player.playback_time
|
2022-09-30 10:41:56 +02:00
|
|
|
)
|
2019-02-19 10:56:05 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
self._latest_state = self._state
|
2019-06-21 02:13:14 +02:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
return callback
|
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
@action
|
2019-02-19 00:15:03 +01:00
|
|
|
def execute(self, cmd, **args):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
|
|
|
Execute a raw mpv command.
|
|
|
|
"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self._cur_player.command(cmd, *args)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def play(
|
|
|
|
self,
|
|
|
|
resource: str,
|
|
|
|
*_,
|
|
|
|
subtitles: Optional[str] = None,
|
|
|
|
fullscreen: Optional[bool] = None,
|
|
|
|
**args,
|
|
|
|
):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
|
|
|
Play a resource.
|
|
|
|
|
|
|
|
:param resource: Resource to play - can be a local file or a remote URL
|
|
|
|
:param subtitles: Path to optional subtitle file
|
2019-02-19 00:15:03 +01:00
|
|
|
:param args: Extra runtime arguments that will be passed to the
|
|
|
|
mpv executable as a key-value dict (keys without `--` prefix)
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
|
|
|
|
2021-10-17 16:17:20 +02:00
|
|
|
self._post_event(MediaPlayRequestEvent, resource=resource)
|
2024-01-13 22:36:42 +01:00
|
|
|
if fullscreen is not None:
|
|
|
|
args['fs'] = fullscreen
|
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
self._init_mpv(args)
|
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
resource = self._get_resource(resource)
|
|
|
|
if resource.startswith('file://'):
|
|
|
|
resource = resource[7:]
|
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
assert self._cur_player, 'The player is not ready'
|
|
|
|
self._cur_player.play(resource)
|
2020-02-21 18:40:46 +01:00
|
|
|
if self.volume:
|
|
|
|
self.set_volume(volume=self.volume)
|
2021-01-14 00:15:35 +01:00
|
|
|
if subtitles:
|
|
|
|
self.add_subtitles(subtitles)
|
2020-02-21 18:40:46 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
return self.status()
|
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def pause(self, *_, **__):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Toggle the paused state"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
2019-02-19 00:15:03 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
self._cur_player.pause = not self._cur_player.pause
|
2019-02-19 00:15:03 +01:00
|
|
|
return self.status()
|
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def quit(self, *_, **__):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Stop and quit the player"""
|
2024-01-13 22:36:42 +01:00
|
|
|
player = self._cur_player
|
|
|
|
if not player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
player.stop()
|
|
|
|
player.quit(code=0)
|
|
|
|
player.wait_for_shutdown(timeout=10)
|
|
|
|
player.terminate()
|
2019-02-19 00:15:03 +01:00
|
|
|
self._player = None
|
2024-01-13 22:36:42 +01:00
|
|
|
return self.status()
|
2019-02-19 00:15:03 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def stop(self, *_, **__):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Stop and quit the player"""
|
2019-02-19 00:15:03 +01:00
|
|
|
return self.quit()
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
def _set_vol(self, *_, step=10.0, **__):
|
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self.set_volume(float(self._cur_player.volume or 0) - step)
|
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def voldown(self, *_, step: float = 10.0, **__):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Volume down by (default: 10)%"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self.set_volume(float(self._cur_player.volume or 0) - step)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def volup(self, step: float = 10.0, **_):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Volume up by (default: 10)%"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self.set_volume(float(self._cur_player.volume or 0) + step)
|
2019-02-19 00:15:03 +01:00
|
|
|
|
|
|
|
@action
|
|
|
|
def set_volume(self, volume):
|
|
|
|
"""
|
|
|
|
Set the volume
|
|
|
|
|
|
|
|
:param volume: Volume value between 0 and 100
|
|
|
|
:type volume: float
|
|
|
|
"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
2019-02-19 00:15:03 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
max_vol = (
|
|
|
|
self._cur_player.volume_max
|
|
|
|
if self._cur_player.volume_max is not None
|
|
|
|
else 100
|
|
|
|
)
|
|
|
|
volume = max(0, min([max_vol, volume]))
|
|
|
|
self._cur_player.volume = volume
|
2019-06-22 00:15:32 +02:00
|
|
|
return self.status()
|
2019-02-19 00:15:03 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def seek(self, position: float, **_):
|
2019-02-19 00:15:03 +01:00
|
|
|
"""
|
|
|
|
Seek backward/forward by the specified number of seconds
|
|
|
|
|
2019-06-21 02:13:14 +02:00
|
|
|
:param position: Number of seconds relative to the current cursor
|
2019-02-19 00:15:03 +01:00
|
|
|
"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
assert self._cur_player.seekable, 'The resource is not seekable'
|
|
|
|
self._cur_player.time_pos = min(
|
|
|
|
float(self._cur_player.time_pos or 0)
|
|
|
|
+ float(self._cur_player.time_remaining or 0),
|
|
|
|
max(0, position),
|
|
|
|
)
|
2019-06-23 02:16:20 +02:00
|
|
|
return self.status()
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def back(self, offset=30.0, **_):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Back by (default: 30) seconds"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
assert self._cur_player.seekable, 'The resource is not seekable'
|
|
|
|
cur_pos = float(self._cur_player.time_pos or 0)
|
|
|
|
return self.seek(cur_pos - offset)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def forward(self, offset=30.0, **_):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Forward by (default: 30) seconds"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
assert self._cur_player.seekable, 'The resource is not seekable'
|
|
|
|
cur_pos = float(self._cur_player.time_pos or 0)
|
|
|
|
return self.seek(cur_pos + offset)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def next(self, **_):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Play the next item in the queue"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
self._cur_player.playlist_next()
|
|
|
|
return self.status()
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def prev(self, **_):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Play the previous item in the queue"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
self._cur_player.playlist_prev()
|
|
|
|
return self.status()
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def toggle_subtitles(self, *_, **__):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Toggle the subtitles visibility"""
|
2019-02-19 00:15:03 +01:00
|
|
|
return self.toggle_property('sub_visibility')
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-06-23 02:16:20 +02:00
|
|
|
@action
|
|
|
|
def add_subtitles(self, filename):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Add a subtitles file"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self._cur_player.sub_add(filename)
|
2019-06-23 02:16:20 +02:00
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
@action
|
2019-07-01 19:32:22 +02:00
|
|
|
def toggle_fullscreen(self):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Toggle the fullscreen mode"""
|
2019-02-19 00:15:03 +01:00
|
|
|
return self.toggle_property('fullscreen')
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2019-02-19 00:15:03 +01:00
|
|
|
def toggle_property(self, property):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
Toggle or sets the value of an mpv property (e.g. fullscreen,
|
|
|
|
sub_visibility etc.). See ``man mpv`` for a full list of properties
|
|
|
|
|
|
|
|
:param property: Property to toggle
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
if not self._player:
|
2024-01-13 22:36:42 +01:00
|
|
|
return None
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
if not hasattr(self._player, property):
|
|
|
|
self.logger.warning('No such mpv property: {}'.format(property))
|
|
|
|
|
|
|
|
value = not getattr(self._player, property)
|
|
|
|
setattr(self._player, property, value)
|
2019-07-01 19:32:22 +02:00
|
|
|
return {property: value}
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2019-02-19 00:15:03 +01:00
|
|
|
def get_property(self, property):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
Get a player property (e.g. pause, fullscreen etc.). See
|
|
|
|
``man mpv`` for a full list of the available properties
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
if not self._player:
|
2024-01-13 22:36:42 +01:00
|
|
|
return None
|
2019-02-19 00:15:03 +01:00
|
|
|
return getattr(self._player, property)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2019-02-19 00:15:03 +01:00
|
|
|
def set_property(self, **props):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
Set the value of an mpv property (e.g. fullscreen, sub_visibility
|
|
|
|
etc.). See ``man mpv`` for a full list of properties
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
:param props: Key-value args for the properties to set
|
|
|
|
:type props: dict
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
if not self._player:
|
2024-01-13 22:36:42 +01:00
|
|
|
return None
|
2019-02-19 00:15:03 +01:00
|
|
|
|
2019-12-07 17:16:48 +01:00
|
|
|
for k, v in props.items():
|
2019-02-19 00:15:03 +01:00
|
|
|
setattr(self._player, k, v)
|
|
|
|
return props
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def set_subtitles(self, filename, *_, **__):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Sets media subtitles from filename"""
|
2019-02-19 00:15:03 +01:00
|
|
|
return self.set_property(subfile=filename, sub_visibility=True)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def remove_subtitles(self, sub_id=None, **_):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Removes (hides) the subtitles"""
|
2019-02-19 00:15:03 +01:00
|
|
|
if not self._player:
|
2024-01-13 22:36:42 +01:00
|
|
|
return None
|
2022-09-30 10:41:56 +02:00
|
|
|
if sub_id:
|
|
|
|
return self._player.sub_remove(sub_id)
|
2019-02-19 00:15:03 +01:00
|
|
|
self._player.sub_visibility = False
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def is_playing(self, **_):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
:returns: True if it's playing, False otherwise
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
if not self._player:
|
|
|
|
return False
|
|
|
|
return not self._player.pause
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2019-02-19 00:15:03 +01:00
|
|
|
def load(self, resource, **args):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
Load/queue a resource/video to the player
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
if not self._player:
|
|
|
|
return self.play(resource, **args)
|
2019-12-07 17:16:48 +01:00
|
|
|
return self._player.loadfile(resource, mode='append-play')
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def mute(self, **_):
|
2022-09-30 10:41:56 +02:00
|
|
|
"""Toggle mute state"""
|
2019-02-19 00:15:03 +01:00
|
|
|
if not self._player:
|
2024-01-13 22:36:42 +01:00
|
|
|
return None
|
2019-02-19 00:15:03 +01:00
|
|
|
mute = not self._player.mute
|
|
|
|
self._player.mute = mute
|
2019-07-01 19:32:22 +02:00
|
|
|
return {'muted': mute}
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def set_position(self, position: float, **_):
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
Seek backward/forward to the specified absolute position (same as ``seek``)
|
2019-02-18 01:17:21 +01:00
|
|
|
"""
|
2019-02-19 00:15:03 +01:00
|
|
|
return self.seek(position)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
@action
|
2024-01-13 22:36:42 +01:00
|
|
|
def status(self, **_):
|
2019-02-19 00:15:03 +01:00
|
|
|
"""
|
|
|
|
Get the current player state.
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2019-02-19 00:15:03 +01:00
|
|
|
:returns: A dictionary containing the current state.
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
Example:
|
|
|
|
|
|
|
|
.. code-block:: javascript
|
|
|
|
|
|
|
|
{
|
|
|
|
"audio_channels": 2,
|
|
|
|
"audio_codec": "mp3",
|
|
|
|
"delay": 0,
|
|
|
|
"duration": 300.0,
|
|
|
|
"file_size": 123456,
|
|
|
|
"filename": "filename or stream URL",
|
|
|
|
"fullscreen": false,
|
|
|
|
"mute": false,
|
|
|
|
"name": "mpv",
|
|
|
|
"pause": false,
|
|
|
|
"percent_pos": 10.0,
|
|
|
|
"position": 30.0,
|
|
|
|
"seekable": true,
|
|
|
|
"state": "play", // or "stop" or "pause"
|
|
|
|
"title": "filename or stream URL",
|
|
|
|
"url": "file:///path/to/file.mp3",
|
|
|
|
"video_codec": "h264",
|
|
|
|
"video_format": "avc1",
|
|
|
|
"volume": 50.0,
|
|
|
|
"volume_max": 100.0,
|
|
|
|
"width": 1280
|
|
|
|
}
|
2019-02-18 01:17:21 +01:00
|
|
|
|
|
|
|
"""
|
2024-01-13 22:36:42 +01:00
|
|
|
if not self._cur_player:
|
2019-06-21 02:13:14 +02:00
|
|
|
return {'state': PlayerState.STOP.value}
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2023-11-06 22:36:25 +01:00
|
|
|
status = {
|
2022-09-30 10:41:56 +02:00
|
|
|
'audio_channels': getattr(self._player, 'audio_channels', None),
|
|
|
|
'audio_codec': getattr(self._player, 'audio_codec_name', None),
|
|
|
|
'delay': getattr(self._player, 'delay', None),
|
|
|
|
'duration': getattr(self._player, 'playback_time', 0)
|
|
|
|
+ getattr(self._player, 'playtime_remaining', 0)
|
|
|
|
if getattr(self._player, 'playtime_remaining', None)
|
|
|
|
else None,
|
|
|
|
'filename': getattr(self._player, 'filename', None),
|
|
|
|
'file_size': getattr(self._player, 'file_size', None),
|
|
|
|
'fullscreen': getattr(self._player, 'fs', None),
|
|
|
|
'mute': getattr(self._player, 'mute', None),
|
|
|
|
'name': getattr(self._player, 'name', None),
|
|
|
|
'pause': getattr(self._player, 'pause', None),
|
|
|
|
'percent_pos': getattr(self._player, 'percent_pos', None),
|
|
|
|
'position': getattr(self._player, 'playback_time', None),
|
|
|
|
'seekable': getattr(self._player, 'seekable', None),
|
2024-01-13 22:36:42 +01:00
|
|
|
'state': self._state.value,
|
2022-09-30 10:41:56 +02:00
|
|
|
'title': getattr(self._player, 'media_title', None)
|
|
|
|
or getattr(self._player, 'filename', None),
|
2024-01-13 22:36:42 +01:00
|
|
|
'url': self._resource,
|
2022-09-30 10:41:56 +02:00
|
|
|
'video_codec': getattr(self._player, 'video_codec', None),
|
|
|
|
'video_format': getattr(self._player, 'video_format', None),
|
|
|
|
'volume': getattr(self._player, 'volume', None),
|
|
|
|
'volume_max': getattr(self._player, 'volume_max', None),
|
|
|
|
'width': getattr(self._player, 'width', None),
|
2019-02-19 00:15:03 +01:00
|
|
|
}
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2023-11-06 22:36:25 +01:00
|
|
|
if self._latest_resource:
|
|
|
|
status.update(
|
|
|
|
{
|
|
|
|
k: v
|
|
|
|
for k, v in asdict(self._latest_resource).items()
|
|
|
|
if v is not None
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
if self._state != self._latest_state:
|
|
|
|
if not self._cur_player:
|
|
|
|
self._post_event(MediaStopEvent)
|
|
|
|
else:
|
|
|
|
self._post_event(
|
|
|
|
MediaPauseEvent
|
|
|
|
if self._state == PlayerState.PAUSE
|
|
|
|
else MediaResumeEvent
|
|
|
|
)
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2024-01-13 22:36:42 +01:00
|
|
|
self._latest_state = self._state
|
|
|
|
return status
|
2019-02-18 01:17:21 +01:00
|
|
|
|
2020-11-11 03:07:23 +01:00
|
|
|
def _get_resource(self, resource):
|
|
|
|
if self._is_youtube_resource(resource):
|
2022-09-30 10:41:56 +02:00
|
|
|
return resource # mpv can handle YouTube streaming natively
|
2020-11-11 03:07:23 +01:00
|
|
|
|
|
|
|
return super()._get_resource(resource)
|
|
|
|
|
|
|
|
|
2019-02-18 01:17:21 +01:00
|
|
|
# vim:sw=4:ts=4:et:
|