-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathmpris.js
123 lines (107 loc) · 5.02 KB
/
mpris.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// vim:fdm=syntax
// by tuberry
/* exported MprisPlayer */
'use strict';
const { Shell, Gio, GLib, GObject } = imports.gi;
const { loadInterfaceXML } = imports.misc.fileUtils;
const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.';
const MPRIS_PLAYER_IFACE =
`<node>
<interface name="org.mpris.MediaPlayer2.Player">
<property name="Metadata" type="a{sv}" access="read"/>
<property name="PlaybackStatus" type="s" access="read"/>
<signal name="Seeked">
<arg type="x" direction="out" name="pos"/>
</signal>
</interface>
</node>`;
Gio._promisify(Gio.File.prototype, 'load_contents_async');
Gio._promisify(Gio.DBusProxy.prototype, 'call', 'call_finish');
var MprisPlayer = class extends GObject.Object {
static {
GObject.registerClass({
Signals: {
update: { param_types: [GObject.TYPE_STRING, GObject.TYPE_JSOBJECT, GObject.TYPE_INT64] },
status: { param_types: [GObject.TYPE_STRING] },
seeked: { param_types: [GObject.TYPE_INT64] },
closed: { },
},
}, this);
}
constructor() {
super();
let DbusProxy = Gio.DBusProxy.makeProxyWrapper(loadInterfaceXML('org.freedesktop.DBus'));
this._bus_proxy = new DbusProxy(Gio.DBus.session, 'org.freedesktop.DBus', '/org/freedesktop/DBus', this._onProxyReady.bind(this));
}
_isMusicApp(bus_name) {
if(!bus_name.startsWith(MPRIS_PLAYER_PREFIX)) return false;
let name = bus_name.replace(new RegExp(`^${MPRIS_PLAYER_PREFIX}`), '');
let [app] = Shell.AppSystem.search(name).toString().split(',');
try {
let cate = Shell.AppSystem.get_default().lookup_app(app || `${name}.desktop`).get_app_info().get_string('Categories').split(';');
return cate.includes('AudioVideo') && !cate.includes('Video');
} catch(e) {
return false;
}
}
_setPlayer(bus_name) {
if(this._bus_name || !this._isMusicApp(bus_name)) return;
this._track_title = '';
this._track_length = 0;
this._track_artists = [];
this._bus_name = bus_name;
let MprisProxy = Gio.DBusProxy.makeProxyWrapper(loadInterfaceXML('org.mpris.MediaPlayer2'));
this._mpris_proxy = new MprisProxy(Gio.DBus.session, bus_name, '/org/mpris/MediaPlayer2', this._onMprisProxyReady.bind(this));
let PlayerProxy = Gio.DBusProxy.makeProxyWrapper(MPRIS_PLAYER_IFACE);
this._player_proxy = new PlayerProxy(Gio.DBus.session, bus_name, '/org/mpris/MediaPlayer2', this._onPlayerProxyReady.bind(this));
}
_onProxyReady() {
this._bus_proxy.ListNamesRemote(([names]) => names?.length && names.forEach(name => this._setPlayer(name)));
this._bus_proxy.connectSignal('NameOwnerChanged', (proxy, sender, [name, old_, new_]) => (new_ && !old_) && this._setPlayer(name));
}
async getPosition() {
// Ref: https://www.andyholmes.ca/articles/dbus-in-gjs.html
let prop = new GLib.Variant('(ss)', ['org.mpris.MediaPlayer2.Player', 'Position']);
let pos = await this._player_proxy.call('org.freedesktop.DBus.Properties.Get', prop, Gio.DBusCallFlags.NONE, -1, null);
return pos.recursiveUnpack().at(0);
}
_closePlayer() {
this._player_proxy = this._mpris_proxy = this._bus_name = null;
if(this._bus_proxy) this._onProxyReady();
this.emit('closed');
}
_onMprisProxyReady() {
this._mpris_proxy.connect('notify::g-name-owner', () => this._mpris_proxy?.g_name_owner || this._closePlayer());
if(!this._mpris_proxy?.g_name_owner) this._closePlayer();
}
_onPlayerProxyReady() {
this._player_proxy.connect('g-properties-changed', this._propsChanged.bind(this));
this._player_proxy.connectSignal('Seeked', (proxy, sender, [pos]) => this.emit('seeked', pos));
this._updateMetadata();
}
_updateMetadata() {
let metadata = {};
for(let prop in this._player_proxy?.Metadata) metadata[prop] = this._player_proxy.Metadata[prop].deepUnpack();
let title = metadata['xesam:title'];
let artists = metadata['xesam:artist'];
// Validate according to the spec; some clients send buggy metadata:
// https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata
this._track_length = metadata['mpris:length'] || 0;
this._track_title = typeof title === 'string' ? title : '';
this._track_artists = Array.isArray(artists) && artists.every(a => typeof a === 'string') ? artists : [];
if(this._track_title) this.emit('update', this._track_title, this._track_artists, this._track_length / 1000);
}
get status() {
return this._player_proxy?.PlaybackStatus ?? 'Stopped';
}
_propsChanged(proxy, changed, _invalidated) {
for(let name in changed.deepUnpack()) {
if(name === 'Metadata') this._updateMetadata();
else if(name === 'PlaybackStatus') this.emit('status', this.status);
}
}
destroy() {
this._bus_proxy = null;
this._closePlayer();
}
};