New media webplugin WIP

This commit is contained in:
Fabio Manganiello 2019-06-22 00:15:32 +02:00
parent 3bd9bec660
commit 482f6f0765
18 changed files with 274 additions and 127 deletions

View file

@ -34,3 +34,13 @@
} }
} }
.active-glow {
@include animation(active-glow 5s infinite);
}
@keyframes active-glow {
0% { background: $active-glow-bg-1; }
50% { background: $active-glow-bg-2; }
100% { background: $active-glow-bg-1; }
}

View file

@ -38,6 +38,8 @@ $fade-in-transition-duration: $fade-transition-duration !default;
$fade-out-transition-duration: $fade-transition-duration !default; $fade-out-transition-duration: $fade-transition-duration !default;
$roll-in-transition-duration: $roll-transition-duration !default; $roll-in-transition-duration: $roll-transition-duration !default;
$roll-out-transition-duration: $roll-transition-duration !default; $roll-out-transition-duration: $roll-transition-duration !default;
$active-glow-bg-1: #d4ffe3 !default;
$active-glow-bg-2: #9cdfb0 !default;
//// Notifications //// Notifications
$notification-bg: rgba(185, 255, 193, 0.9) !default; $notification-bg: rgba(185, 255, 193, 0.9) !default;

View file

@ -10,6 +10,11 @@
@extend .vertical-center; @extend .vertical-center;
padding-left: 1rem; padding-left: 1rem;
line-height: 2.6rem; line-height: 2.6rem;
.item-info {
font-size: 1.15em;
letter-spacing: .02em;
}
} }
button { button {
@ -26,6 +31,10 @@
justify-content: center; justify-content: center;
} }
.position {
margin-top: .75em;
}
button { button {
padding: 0 1.5rem; padding: 0 1.5rem;

View file

@ -1,8 +1,19 @@
@import 'common/animations';
.media-plugin { .media-plugin {
.results { .results {
@include calc(height, '100% - 16rem');
position: relative; // For the dropdown menu position: relative; // For the dropdown menu
overflow: auto; overflow: auto;
@include calc(height, '100%');
&.resize {
@include calc(height, '100% - 16rem');
}
.active {
@extend .active-glow;
height: 4rem;
}
.empty { .empty {
height: 100%; height: 100%;

View file

@ -1,6 +1,8 @@
@import 'common/vars'; @import 'common/vars';
@import 'common/mixins'; @import 'common/mixins';
@import 'common/layout'; @import 'common/layout';
@import 'common/animations';
@import 'webpanel/plugins/music.mpd/vars'; @import 'webpanel/plugins/music.mpd/vars';
.music-mpd-container { .music-mpd-container {
@ -155,7 +157,7 @@
&.active { &.active {
height: 4rem; height: 4rem;
@include animation(active-track 5s infinite); @extend .active-glow;
} }
&.move:hover { &.move:hover {
@ -449,21 +451,3 @@
} }
} }
@keyframes active-track {
0% { background: $active-track-bg-1; }
50% { background: $active-track-bg-2; }
100% { background: $active-track-bg-1; }
}
@-moz-keyframes active-track {
0% { background: $active-track-bg-1; }
50% { background: $active-track-bg-2; }
100% { background: $active-track-bg-1; }
}
@-webkit-keyframes active-track {
0% { background: $active-track-bg-1; }
50% { background: $active-track-bg-2; }
100% { background: $active-track-bg-1; }
}

View file

@ -16,9 +16,6 @@ $empty-playlist-shadow: 2px 1px rgb(235,235,235);
$playlist-controls-bg: rgba(247,247,247,0.95); $playlist-controls-bg: rgba(247,247,247,0.95);
$playlist-controls-border: $default-border-2; $playlist-controls-border: $default-border-2;
$active-track-bg-1: #d4ffe3;
$active-track-bg-2: #9cdfb0;
$move-mode-track-border: 3px dotted rgb(216,156,136); $move-mode-track-border: 3px dotted rgb(216,156,136);
$move-mode-track-bg: rgba(216,156,136,0.3); $move-mode-track-bg: rgba(216,156,136,0.3);

View file

@ -1,5 +1,6 @@
Vue.component('media-controls', { Vue.component('media-controls', {
template: '#tmpl-media-controls', template: '#tmpl-media-controls',
mixins: [mediaUtils],
props: { props: {
bus: { type: Object }, bus: { type: Object },
status: { status: {
@ -7,8 +8,5 @@ Vue.component('media-controls', {
default: () => {}, default: () => {},
}, },
}, },
methods: {
},
}); });

View file

@ -16,6 +16,12 @@ MediaHandlers.file = Vue.extend({
action: this.play, action: this.play,
}, },
{
text: 'Play with subtitles',
iconClass: 'fas fa-closed-captioning',
action: this.searchSubtiles,
},
{ {
text: 'Download', text: 'Download',
icon: 'download', icon: 'download',
@ -41,6 +47,9 @@ MediaHandlers.file = Vue.extend({
info: function(item) { info: function(item) {
}, },
searchSubtitles: function(item) {
},
}, },
}); });

View file

@ -1,9 +1,37 @@
// Will be filled by dynamically loading media type handler scripts // Will be filled by dynamically loading media type handler scripts
const MediaHandlers = {}; const MediaHandlers = {};
const mediaUtils = {
methods: {
convertTime: function(time) {
time = parseFloat(time); // Normalize strings
var t = {};
t.h = '' + parseInt(time/3600);
t.m = '' + parseInt(time/60 - t.h*60);
t.s = '' + parseInt(time - (t.h*3600 + t.m*60));
for (var attr of ['m','s']) {
if (parseInt(t[attr]) < 10) {
t[attr] = '0' + t[attr];
}
}
var ret = [];
if (parseInt(t.h)) {
ret.push(t.h);
}
ret.push(t.m, t.s);
return ret.join(':');
},
},
};
Vue.component('media', { Vue.component('media', {
template: '#tmpl-media', template: '#tmpl-media',
props: ['config','player'], props: ['config','player'],
mixins: [mediaUtils],
data: function() { data: function() {
return { return {
bus: new Vue({}), bus: new Vue({}),
@ -54,6 +82,38 @@ Vue.component('media', {
}); });
}, },
pause: async function() {
let status = await this.selectedDevice.pause();
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
stop: async function() {
let status = await this.selectedDevice.stop();
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
seek: async function(position) {
let status = await this.selectedDevice.seek(position);
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
setVolume: async function(volume) {
let status = await this.selectedDevice.setVolume(volume);
this.onStatusUpdate({
device: this.selectedDevice,
status: status,
});
},
info: function(item) { info: function(item) {
// TODO // TODO
console.log(item); console.log(item);
@ -78,29 +138,21 @@ Vue.component('media', {
Vue.set(this.status[dev.type], dev.name, status); Vue.set(this.status[dev.type], dev.name, status);
}, },
onNewPlayingMedia: function(event) { onMediaEvent: async function(event) {
console.log('NEW MEDIA'); let status = await request(event.plugin + '.status');
console.log(event);
},
onMediaPlay: function(event) { if (event.resource) {
console.log('PLAY'); event.url = event.resource;
console.log(event); delete event.resource;
}, }
onMediaPause: function(event) { if (event.plugin.startsWith('media.'))
console.log('PAUSE'); event.plugin = event.plugin.substr(6);
console.log(event);
},
onMediaStop: function(event) { if (this.status[event.player] && this.status[event.player][event.plugin])
console.log('STOP'); this.status[event.player][event.plugin] = status;
console.log(event); else if (this.status[event.plugin] && this.status[event.plugin][event.player])
}, this.status[event.plugin][event.player] = status;
onMediaSeek: function(event) {
console.log('SEEK');
console.log(event);
}, },
}, },
@ -112,13 +164,18 @@ Vue.component('media', {
MediaHandlers[type].bus = this.bus; MediaHandlers[type].bus = this.bus;
} }
registerEventHandler(this.onNewPlayingMedia, 'platypush.message.event.media.NewPlayingMediaEvent'); registerEventHandler(this.onMediaEvent,
registerEventHandler(this.onMediaPlay, 'platypush.message.event.media.MediaPlayEvent'); 'platypush.message.event.media.NewPlayingMediaEvent',
registerEventHandler(this.onMediaPause, 'platypush.message.event.media.MediaPauseEvent'); 'platypush.message.event.media.MediaPlayEvent',
registerEventHandler(this.onMediaStop, 'platypush.message.event.media.MediaStopEvent'); 'platypush.message.event.media.MediaPauseEvent',
registerEventHandler(this.onMediaSeek, 'platypush.message.event.media.MediaSeekEvent'); 'platypush.message.event.media.MediaStopEvent',
'platypush.message.event.media.MediaSeekEvent');
this.bus.$on('play', this.play); this.bus.$on('play', this.play);
this.bus.$on('pause', this.pause);
this.bus.$on('stop', this.stop);
this.bus.$on('seek', this.seek);
this.bus.$on('volume', this.setVolume);
this.bus.$on('info', this.info); this.bus.$on('info', this.info);
this.bus.$on('selected-device', this.selectDevice); this.bus.$on('selected-device', this.selectDevice);
this.bus.$on('results-loading', this.onResultsLoading); this.bus.$on('results-loading', this.onResultsLoading);

View file

@ -0,0 +1,80 @@
MediaPlayers.kodi = Vue.extend({
props: {
type: {
type: String,
default: 'kodi',
},
device: {
type: Object,
default: () => {
return {
url: undefined,
};
},
},
accepts: {
type: Object,
default: () => {
return {
youtube: true,
};
},
},
iconClass: {
type: String,
default: 'fa fa-film',
},
},
computed: {
host: function() {
if (!this.device.url) {
return;
}
return this.device.url.match(/^https?:\/\/([^:]+):(\d+).*$/)[1];
},
name: function() {
return this.host;
},
port: function() {
if (!this.device.url) {
return;
}
return parseInt(this.device.url.match(/^https?:\/\/([^:]+):(\d+).*$/)[2]);
},
text: function() {
return 'Kodi '.concat('[', this.host, ']');
},
},
methods: {
scan: async function() {
if (!('media.kodi' in __plugins__)) {
return [];
}
return [
{ url: __plugins__['media.kodi'].url }
];
},
status: async function() {
return {};
},
play: async function(item) {
},
stop: async function() {
},
},
});

View file

@ -67,7 +67,7 @@ MediaPlayers.local = Vue.extend({
); );
}, },
volume: async function(volume) { setVolume: async function(volume) {
return await request( return await request(
this.pluginPrefix.concat('.set_volume'), this.pluginPrefix.concat('.set_volume'),
{volume: volume} {volume: volume}

View file

@ -18,6 +18,10 @@ Vue.component('media-results', {
type: Object, type: Object,
default: () => {}, default: () => {},
}, },
resize: {
type: Boolean,
default: false,
},
}, },
data: function() { data: function() {
@ -38,6 +42,7 @@ Vue.component('media-results', {
return { return {
text: item.text, text: item.text,
icon: item.icon, icon: item.icon,
iconClass: item.iconClass,
click: function() { click: function() {
item.action(self.selectedItem); item.action(self.selectedItem);
}, },

View file

@ -5,7 +5,7 @@ var utils = {
var t = {}; var t = {};
t.h = '' + parseInt(time/3600); t.h = '' + parseInt(time/3600);
t.m = '' + parseInt(time/60 - t.h*60); t.m = '' + parseInt(time/60 - t.h*60);
t.s = '' + parseInt(time - t.m*60); t.s = '' + parseInt(time - (t.h*3600 + t.m*60));
for (var attr of ['m','s']) { for (var attr of ['m','s']) {
if (parseInt(t[attr]) < 10) { if (parseInt(t[attr]) < 10) {

View file

@ -9,22 +9,28 @@
</div> </div>
<div class="col-6 playback-controls"> <div class="col-6 playback-controls">
<div class="row"> <div class="row buttons">
<button> <button v-if="status.state === 'pause'" @click="bus.$emit('pause')">
<i class="fa fa-play"></i> <i class="fa fa-play"></i>
</button> </button>
<button> <button v-if="status.state === 'play'" @click="bus.$emit('pause')">
<i class="fa fa-pause"></i>
</button>
<button v-if="status.state === 'play' || status.state === 'pause'" @click="bus.$emit('stop')">
<i class="fa fa-stop"></i> <i class="fa fa-stop"></i>
</button> </button>
</div> </div>
<div class="row"> <div class="row position">
<span class="elapsed-time" v-text="'-:--'"></span> <span class="elapsed-time" v-text="status.position ? convertTime(status.position) : '-:--'"></span>
<input type="range" <input type="range"
v-model="status.position"
@input="bus.$emit('seek', $event.target.value)"
class="slider seek-slider" class="slider seek-slider"
:disabled="!status.seekable || !status.duration"
min="0" min="0"
max="100"> :max="status.duration || 0">
<span class="total-time" v-text="'-:--'"></span> <span class="total-time" v-text="status.duration ? convertTime(status.duration) : '-:--'"></span>
</div> </div>
</div> </div>
@ -34,9 +40,11 @@
<i class="fa fa-volume-up"></i> <i class="fa fa-volume-up"></i>
</button> </button>
<input type="range" <input type="range"
v-model="status.volume"
@input="bus.$emit('volume', $event.target.value)"
class="slider volume-slider" class="slider volume-slider"
min="0" min="0"
max="100"> :max="status.volume_max || 100">
</div> </div>
</div> </div>
</div> </div>

View file

@ -27,11 +27,13 @@
:status="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] ? status[selectedDevice.type][selectedDevice.name] : {}" :status="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] ? status[selectedDevice.type][selectedDevice.name] : {}"
:searching="loading.results" :searching="loading.results"
:loading="loading.media" :loading="loading.media"
:resize="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] && (status[selectedDevice.type][selectedDevice.name].state === 'play' || status[selectedDevice.type][selectedDevice.name].state === 'pause')"
:results="results"> :results="results">
</media-results> </media-results>
<media-controls :bus="bus" <media-controls :bus="bus"
:status="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] ? status[selectedDevice.type][selectedDevice.name] : {}"> :status="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] ? status[selectedDevice.type][selectedDevice.name] : {}"
v-if="selectedDevice && status[selectedDevice.type] && status[selectedDevice.type][selectedDevice.name] && (status[selectedDevice.type][selectedDevice.name].state === 'play' || status[selectedDevice.type][selectedDevice.name].state === 'pause')">
</media-controls> </media-controls>
</div> </div>
</script> </script>

View file

@ -1,7 +1,7 @@
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/media/results.js') }}"></script> <script type="application/javascript" src="{{ url_for('static', filename='js/plugins/media/results.js') }}"></script>
<script type="text/x-template" id="tmpl-media-results"> <script type="text/x-template" id="tmpl-media-results">
<div class="results"> <div class="results" :class="{resize: resize}">
<div class="empty" v-if="searching || loading || !results.length"> <div class="empty" v-if="searching || loading || !results.length">
<div class="searching" v-if="searching">Searching</div> <div class="searching" v-if="searching">Searching</div>
<div class="loading" v-else-if="loading">Loading</div> <div class="loading" v-else-if="loading">Loading</div>
@ -12,7 +12,7 @@
:key="item.url" :key="item.url"
:bus="bus" :bus="bus"
:selected="item.url && item.url === selectedItem.url" :selected="item.url && item.url === selectedItem.url"
:active="item.url && item.url === status.url" :active="(status.state === 'play' || status.state === 'pause') && item.url && item.url === status.url"
:item="item" :item="item"
v-else> v-else>
</media-item> </media-item>

View file

@ -4,8 +4,8 @@ from platypush.message.event import Event
class MediaEvent(Event): class MediaEvent(Event):
""" Base class for media events """ """ Base class for media events """
def __init__(self, *args, **kwargs): def __init__(self, player=None, plugin=None, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(player=player, plugin=plugin, *args, **kwargs)
class MediaPlayRequestEvent(MediaEvent): class MediaPlayRequestEvent(MediaEvent):
@ -13,13 +13,13 @@ class MediaPlayRequestEvent(MediaEvent):
Event triggered when a new media playback request is received Event triggered when a new media playback request is received
""" """
def __init__(self, resource=None, title=None, *args, **kwargs): def __init__(self, player=None, plugin=None, resource=None, title=None, *args, **kwargs):
""" """
:param resource: File name or URI of the played video :param resource: File name or URI of the played video
:type resource: str :type resource: str
""" """
super().__init__(*args, resource=resource, title=title, **kwargs) super().__init__(*args, player=player, plugin=plugin, resource=resource, title=title, **kwargs)
class MediaPlayEvent(MediaEvent): class MediaPlayEvent(MediaEvent):
@ -27,13 +27,13 @@ class MediaPlayEvent(MediaEvent):
Event triggered when a new media content is played Event triggered when a new media content is played
""" """
def __init__(self, resource=None, title=None, *args, **kwargs): def __init__(self, player=None, plugin=None, resource=None, title=None, *args, **kwargs):
""" """
:param resource: File name or URI of the played video :param resource: File name or URI of the played video
:type resource: str :type resource: str
""" """
super().__init__(*args, resource=resource, title=title, **kwargs) super().__init__(*args, player=player, plugin=plugin, resource=resource, title=title, **kwargs)
class MediaStopEvent(MediaEvent): class MediaStopEvent(MediaEvent):
@ -41,8 +41,8 @@ class MediaStopEvent(MediaEvent):
Event triggered when a media is stopped Event triggered when a media is stopped
""" """
def __init__(self, *args, **kwargs): def __init__(self, player=None, plugin=None, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, player=player, plugin=plugin, **kwargs)
class MediaPauseEvent(MediaEvent): class MediaPauseEvent(MediaEvent):
@ -50,8 +50,8 @@ class MediaPauseEvent(MediaEvent):
Event triggered when a media playback is paused Event triggered when a media playback is paused
""" """
def __init__(self, *args, **kwargs): def __init__(self, player=None, plugin=None, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, player=player, plugin=plugin, **kwargs)
class MediaSeekEvent(MediaEvent): class MediaSeekEvent(MediaEvent):
@ -59,8 +59,8 @@ class MediaSeekEvent(MediaEvent):
Event triggered when the time position in the media changes Event triggered when the time position in the media changes
""" """
def __init__(self, position, *args, **kwargs): def __init__(self, position, player=None, plugin=None, *args, **kwargs):
super().__init__(*args, position=position, **kwargs) super().__init__(*args, player=player, plugin=plugin, position=position, **kwargs)
class MediaVolumeChangedEvent(MediaEvent): class MediaVolumeChangedEvent(MediaEvent):
@ -68,8 +68,8 @@ class MediaVolumeChangedEvent(MediaEvent):
Event triggered when the media volume changes Event triggered when the media volume changes
""" """
def __init__(self, volume, *args, **kwargs): def __init__(self, volume, player=None, plugin=None, *args, **kwargs):
super().__init__(*args, volume=volume, **kwargs) super().__init__(*args, player=player, plugin=plugin, volume=volume, **kwargs)
class MediaMuteChangedEvent(MediaEvent): class MediaMuteChangedEvent(MediaEvent):
@ -77,8 +77,8 @@ class MediaMuteChangedEvent(MediaEvent):
Event triggered when the media is muted/unmuted Event triggered when the media is muted/unmuted
""" """
def __init__(self, mute, *args, **kwargs): def __init__(self, mute, player=None, plugin=None, *args, **kwargs):
super().__init__(*args, mute=mute, **kwargs) super().__init__(*args, player=player, plugin=plugin, mute=mute, **kwargs)
class NewPlayingMediaEvent(MediaEvent): class NewPlayingMediaEvent(MediaEvent):
@ -86,13 +86,13 @@ class NewPlayingMediaEvent(MediaEvent):
Event triggered when a new media source is being played Event triggered when a new media source is being played
""" """
def __init__(self, resource=None, *args, **kwargs): def __init__(self, player=None, plugin=None, resource=None, *args, **kwargs):
""" """
:param resource: File name or URI of the played resource :param resource: File name or URI of the played resource
:type resource: str :type resource: str
""" """
super().__init__(*args, resource=resource, **kwargs) super().__init__(*args, player=player, plugin=plugin, resource=resource, **kwargs)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -59,6 +59,11 @@ class MediaMpvPlugin(MediaPlugin):
self._player = mpv.MPV(**mpv_args) self._player = mpv.MPV(**mpv_args)
self._player.register_event_callback(self._event_callback()) self._player.register_event_callback(self._event_callback())
@staticmethod
def _post_event(evt_type, **evt):
bus = get_bus()
bus.post(evt_type(player='local', plugin='media.mpv', **evt))
def _event_callback(self): def _event_callback(self):
def callback(event): def callback(event):
from mpv import MpvEventID as Event from mpv import MpvEventID as Event
@ -70,17 +75,15 @@ class MediaMpvPlugin(MediaPlugin):
if not evt: if not evt:
return return
bus = get_bus()
if (evt == Event.FILE_LOADED or evt == Event.START_FILE) and self._get_current_resource(): if (evt == Event.FILE_LOADED or evt == Event.START_FILE) and self._get_current_resource():
self._playback_rebounce_event.set() self._playback_rebounce_event.set()
bus.post(NewPlayingMediaEvent(resource=self._get_current_resource(), title=self._player.filename)) self._post_event(NewPlayingMediaEvent, resource=self._get_current_resource(), title=self._player.filename)
bus.post(MediaPlayEvent(resource=self._get_current_resource(), title=self._player.filename))
elif evt == Event.PLAYBACK_RESTART: elif evt == Event.PLAYBACK_RESTART:
self._playback_rebounce_event.set() self._playback_rebounce_event.set()
elif evt == Event.PAUSE: elif evt == Event.PAUSE:
bus.post(MediaPauseEvent(resource=self._get_current_resource(), title=self._player.filename)) self._post_event(MediaPauseEvent, resource=self._get_current_resource(), title=self._player.filename)
elif evt == Event.UNPAUSE: elif evt == Event.UNPAUSE:
bus.post(MediaPlayEvent(resource=self._get_current_resource(), title=self._player.filename)) self._post_event(MediaPlayEvent, resource=self._get_current_resource(), title=self._player.filename)
elif evt == Event.SHUTDOWN or ( elif evt == Event.SHUTDOWN or (
evt == Event.END_FILE and event.get('event', {}).get('reason') evt == Event.END_FILE and event.get('event', {}).get('reason')
in [EndFile.EOF_OR_INIT_FAILURE, EndFile.ABORTED, EndFile.QUIT]): in [EndFile.EOF_OR_INIT_FAILURE, EndFile.ABORTED, EndFile.QUIT]):
@ -90,12 +93,12 @@ class MediaMpvPlugin(MediaPlugin):
return return
self._player = None self._player = None
bus.post(MediaStopEvent()) self._post_event(MediaStopEvent)
for callback in self._on_stop_callbacks: for callback in self._on_stop_callbacks:
callback() callback()
elif evt == Event.SEEK: elif evt == Event.SEEK:
bus.post(MediaSeekEvent(position=self._player.playback_time)) self._post_event(MediaSeekEvent, position=self._player.playback_time)
return callback return callback
@ -210,7 +213,7 @@ class MediaMpvPlugin(MediaPlugin):
volume = max(0, min([self._player.volume_max, volume])) volume = max(0, min([self._player.volume_max, volume]))
self._player.volume = volume self._player.volume = volume
return {'volume': volume} return self.status()
@action @action
def seek(self, position): def seek(self, position):
@ -382,31 +385,18 @@ class MediaMpvPlugin(MediaPlugin):
return {'state': PlayerState.STOP.value} return {'state': PlayerState.STOP.value}
return { return {
'alang': getattr(self._player, 'alang'),
'aspect': getattr(self._player, 'aspect'), 'aspect': getattr(self._player, 'aspect'),
'audio': getattr(self._player, 'audio'), 'audio': getattr(self._player, 'audio'),
'audio_bitrate': getattr(self._player, 'audio_bitrate'), 'audio_bitrate': getattr(self._player, 'audio_bitrate'),
'audio_buffer': getattr(self._player, 'audio_buffer'),
'audio_channels': getattr(self._player, 'audio_channels'), 'audio_channels': getattr(self._player, 'audio_channels'),
'audio_client_name': getattr(self._player, 'audio_client_name'), 'audio_codec': getattr(self._player, 'audio_codec_name'),
'audio_codec': getattr(self._player, 'audio_codec'),
'audio_codec_name': getattr(self._player, 'audio_codec_name'),
'audio_delay': getattr(self._player, 'audio_delay'), 'audio_delay': getattr(self._player, 'audio_delay'),
'audio_device': getattr(self._player, 'audio_device'), 'audio_output': getattr(self._player, 'current_ao'),
'audio_device_list': getattr(self._player, 'audio_device_list'),
'audio_exclusive': getattr(self._player, 'audio_exclusive'),
'audio_file_paths': getattr(self._player, 'audio_file_paths'), 'audio_file_paths': getattr(self._player, 'audio_file_paths'),
'audio_files': getattr(self._player, 'audio_files'), 'audio_files': getattr(self._player, 'audio_files'),
'audio_format': getattr(self._player, 'audio_format'),
'audio_out_params': getattr(self._player, 'audio_out_params'),
'audio_params': getattr(self._player, 'audio_params'), 'audio_params': getattr(self._player, 'audio_params'),
'audio_mixer_device': getattr(self._player, 'alsa_mixer_device'), 'audio_mixer': getattr(self._player, 'alsa_mixer_device'),
'audio_mixer_index': getattr(self._player, 'alsa_mixer_index'),
'audio_mixer_name': getattr(self._player, 'alsa_mixer_name'),
'autosub': getattr(self._player, 'autosub'),
'autosync': getattr(self._player, 'autosync'), 'autosync': getattr(self._player, 'autosync'),
'background': getattr(self._player, 'background'),
'border': getattr(self._player, 'border'),
'brightness': getattr(self._player, 'brightness'), 'brightness': getattr(self._player, 'brightness'),
'chapter': getattr(self._player, 'chapter'), 'chapter': getattr(self._player, 'chapter'),
'chapter_list': getattr(self._player, 'chapter_list'), 'chapter_list': getattr(self._player, 'chapter_list'),
@ -416,13 +406,11 @@ class MediaMpvPlugin(MediaPlugin):
'clock': getattr(self._player, 'clock'), 'clock': getattr(self._player, 'clock'),
'cookies': getattr(self._player, 'cookies'), 'cookies': getattr(self._player, 'cookies'),
'cookies_file': getattr(self._player, 'cookies_file'), 'cookies_file': getattr(self._player, 'cookies_file'),
'current_ao': getattr(self._player, 'current_ao'),
'current_vo': getattr(self._player, 'current_vo'),
'delay': getattr(self._player, 'delay'), 'delay': getattr(self._player, 'delay'),
'display_names': getattr(self._player, 'display_names'), 'displays': getattr(self._player, 'display_names'),
'end': getattr(self._player, 'end'), 'duration': getattr(self._player, 'playback_time', 0) +
'endpos': getattr(self._player, 'endpos'), getattr(self._player, 'playtime_remaining', 0)
'eof_reached': getattr(self._player, 'eof_reached'), if getattr(self._player, 'playtime_remaining') else None,
'file_format': getattr(self._player, 'file_format'), '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'),
@ -432,11 +420,8 @@ class MediaMpvPlugin(MediaPlugin):
'height': getattr(self._player, 'height'), 'height': getattr(self._player, 'height'),
'idle': getattr(self._player, 'idle'), 'idle': getattr(self._player, 'idle'),
'idle_active': getattr(self._player, 'idle_active'), 'idle_active': getattr(self._player, 'idle_active'),
'length': getattr(self._player, 'playback_time', 0) + getattr(self._player, 'playtime_remaining', 0)
if getattr(self._player, 'playtime_remaining') else None,
'loop': getattr(self._player, 'loop'), 'loop': getattr(self._player, 'loop'),
'media_title': getattr(self._player, 'loop'), 'media_title': getattr(self._player, 'media_title'),
'mpv_configuration': getattr(self._player, 'mpv_configuration'),
'mpv_version': getattr(self._player, 'mpv_version'), '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'),
@ -464,17 +449,8 @@ class MediaMpvPlugin(MediaPlugin):
'sub_paths': getattr(self._player, 'sub_paths'), 'sub_paths': getattr(self._player, 'sub_paths'),
'sub_text': getattr(self._player, 'sub_text'), 'sub_text': getattr(self._player, 'sub_text'),
'subdelay': getattr(self._player, 'subdelay'), 'subdelay': getattr(self._player, 'subdelay'),
'terminal': getattr(self._player, 'terminal'),
'time_start': getattr(self._player, 'time_start'), 'time_start': getattr(self._player, 'time_start'),
'title': getattr(self._player, 'filename'), 'title': getattr(self._player, 'filename'),
'tv_alsa': getattr(self._player, 'tv_alsa'),
'tv_audio': getattr(self._player, 'tv_audio'),
'tv_audiorate': getattr(self._player, 'tv_audiorate'),
'tv_channels': getattr(self._player, 'tv_channels'),
'tv_device': getattr(self._player, 'tv_device'),
'tv_height': getattr(self._player, 'tv_height'),
'tv_volume': getattr(self._player, 'tv_volume'),
'tv_width': getattr(self._player, 'tv_width'),
'url': self._get_current_resource(), 'url': self._get_current_resource(),
'user_agent': getattr(self._player, 'user_agent'), 'user_agent': getattr(self._player, 'user_agent'),
'video': getattr(self._player, 'video'), 'video': getattr(self._player, 'video'),
@ -482,19 +458,18 @@ class MediaMpvPlugin(MediaPlugin):
'video_align_y': getattr(self._player, 'video_align_y'), 'video_align_y': getattr(self._player, 'video_align_y'),
'video_aspect': getattr(self._player, 'video_aspect'), 'video_aspect': getattr(self._player, 'video_aspect'),
'video_bitrate': getattr(self._player, 'video_bitrate'), '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_params': getattr(self._player, 'video_params'),
'video_sync': getattr(self._player, 'video_sync'), 'video_sync': getattr(self._player, 'video_sync'),
'video_zoom': getattr(self._player, 'video_zoom'), 'video_zoom': getattr(self._player, 'video_zoom'),
'vlang': getattr(self._player, 'vlang'),
'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_minimized': getattr(self._player, 'window_minimized'),
'window_scale': getattr(self._player, 'window_scale'), 'window_scale': getattr(self._player, 'window_scale'),
'working_directory': getattr(self._player, 'working_directory'), 'working_directory': getattr(self._player, 'working_directory'),
'ytdl': getattr(self._player, 'ytdl'),
} }
def on_stop(self, callback): def on_stop(self, callback):