├── .editorconfig
├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
├── cmake
└── gdbus_codegen.cmake
├── include
├── main.h
├── message.h
├── mpris2.h
├── proxy.h
└── util.h
├── me.f1u77y.web_media_controller.chromium.json
├── me.f1u77y.web_media_controller.firefox.json
├── meson.build
├── src
├── main.c
├── message.c
├── mpris2.c
├── proxy.c
└── util.c
└── third-party
└── mpris-spec
├── org.mpris.MediaPlayer2.Player.xml
└── org.mpris.MediaPlayer2.xml
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 |
9 | [*.{c,h}]
10 | indent_size = 4
11 | indent_style = space
12 |
13 | [meson.build]
14 | indent_size = 2
15 | indent_style = space
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | compile_commands.json
3 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.0)
2 | project(web-media-controller C)
3 |
4 | option (ENABLE_FIREFOX "Install manifest for Firefox" ON)
5 | option (ENABLE_CHROME "Install manifest for Google Chrome" ON)
6 | option (ENABLE_CHROMIUM "Install manifest for Chromium" ON)
7 |
8 | option (INSTALL_FOR_CURRENT_USER "Install for current user only" OFF)
9 | set (CHROMIUM_MANIFEST_DESTINATION "/etc/chromium/native-messaging-hosts" CACHE STRING "Choromium's manifest installation location")
10 | set (CHROME_MANIFEST_DESTINATION "/etc/opt/chrome/native-messaging-hosts" CACHE STRING "Chrome's manifest installation location")
11 | set (FIREFOX_MANIFEST_DESTINATION "/usr/lib/mozilla/native-messaging-hosts" CACHE STRING "Firefox's manifest installation location")
12 | set (CMAKE_INSTALL_PREFIX "/usr/local" CACHE STRING "Install prefix")
13 |
14 | if (INSTALL_FOR_CURRENT_USER)
15 | set (CHROMIUM_MANIFEST_DESTINATION "$ENV{HOME}/.config/chromium/NativeMessagingHosts")
16 | set (CHROME_MANIFEST_DESTINATION "$ENV{HOME}/.config/google-chrome/NativeMessagingHosts")
17 | set (FIREFOX_MANIFEST_DESTINATION "$ENV{HOME}/.mozilla/native-messaging-hosts")
18 | endif ()
19 |
20 |
21 | set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
22 | include (gdbus_codegen)
23 |
24 | set (MPRIS_GENERATED_DIR "${PROJECT_BINARY_DIR}/mpris-generated")
25 | if (NOT EXISTS "${MPRIS_GENERATED_DIR}")
26 | file (MAKE_DIRECTORY "${MPRIS_GENERATED_DIR}")
27 | endif ()
28 |
29 | set (MPRIS_SPEC_DIR "${PROJECT_SOURCE_DIR}/third-party/mpris-spec")
30 |
31 | generate_gdbus_code (
32 | OUTPUT "${MPRIS_GENERATED_DIR}/mpris-core"
33 | INTERFACE "org.mpris"
34 | INPUT "${MPRIS_SPEC_DIR}/org.mpris.MediaPlayer2.xml"
35 | )
36 |
37 | generate_gdbus_code (
38 | OUTPUT "${MPRIS_GENERATED_DIR}/mpris-player"
39 | INTERFACE "org.mpris"
40 | INPUT "${MPRIS_SPEC_DIR}/org.mpris.MediaPlayer2.Player.xml"
41 | )
42 |
43 | add_executable (${PROJECT_NAME}
44 | "${MPRIS_GENERATED_DIR}/mpris-core.c"
45 | "${MPRIS_GENERATED_DIR}/mpris-player.c"
46 | src/util.c
47 | src/message.c
48 | src/proxy.c
49 | src/mpris2.c
50 | src/main.c
51 | )
52 | set_property (TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99)
53 |
54 | if (INSTALL_FOR_CURRENT_USER)
55 | set (CMAKE_INSTALL_PREFIX "$ENV{HOME}/.local")
56 | endif ()
57 |
58 | set (DEST_BINARY_DIR bin)
59 | set (EXECUTABLE_PATH "${CMAKE_INSTALL_PREFIX}/${DEST_BINARY_DIR}/${PROJECT_NAME}")
60 |
61 | if (ENABLE_CHROMIUM)
62 | configure_file (
63 | me.f1u77y.web_media_controller.chromium.json
64 | manifest/chromium/me.f1u77y.web_media_controller.json
65 | ESCAPE_QUOTES
66 | )
67 | install (
68 | FILES "${CMAKE_BINARY_DIR}/manifest/chromium/me.f1u77y.web_media_controller.json"
69 | DESTINATION "${CHROMIUM_MANIFEST_DESTINATION}"
70 | )
71 | endif ()
72 |
73 | if (ENABLE_CHROME)
74 | configure_file (
75 | me.f1u77y.web_media_controller.chromium.json
76 | manifest/chrome/me.f1u77y.web_media_controller.json
77 | ESCAPE_QUOTES
78 | )
79 | install (
80 | FILES "${CMAKE_BINARY_DIR}/manifest/chrome/me.f1u77y.web_media_controller.json"
81 | DESTINATION "${CHROME_MANIFEST_DESTINATION}"
82 | )
83 | endif ()
84 |
85 | if (ENABLE_FIREFOX)
86 | configure_file (
87 | me.f1u77y.web_media_controller.firefox.json
88 | manifest/firefox/me.f1u77y.web_media_controller.json
89 | ESCAPE_QUOTES
90 | )
91 | install (
92 | FILES "${CMAKE_BINARY_DIR}/manifest/firefox/me.f1u77y.web_media_controller.json"
93 | DESTINATION "${FIREFOX_MANIFEST_DESTINATION}"
94 | )
95 | endif ()
96 |
97 | install(
98 | TARGETS ${PROJECT_NAME}
99 | DESTINATION "${DEST_BINARY_DIR}"
100 | )
101 |
102 | find_package (PkgConfig)
103 | pkg_check_modules (GLIB2 REQUIRED glib-2.0)
104 | pkg_check_modules (GIO_UNIX REQUIRED gio-unix-2.0)
105 | pkg_check_modules (JSON_GLIB REQUIRED json-glib-1.0>=0.16)
106 |
107 | target_include_directories (${PROJECT_NAME} PUBLIC
108 | include
109 | ${MPRIS_GENERATED_DIR}
110 | ${GLIB2_INCLUDE_DIRS}
111 | ${GIO_UNIX_INCLUDE_DIRS}
112 | ${JSON_GLIB_INCLUDE_DIRS}
113 | )
114 |
115 | target_link_libraries (${PROJECT_NAME}
116 | ${GLIB2_LIBRARIES}
117 | ${GIO_UNIX_LIBRARIES}
118 | ${JSON_GLIB_LIBRARIES}
119 | m
120 | )
121 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This project is now abandoned due to its redundancy
2 |
3 | See https://github.com/f1u77y/web-media-controller#this-project-is-now-abandoned-due-to-its-redundancy
4 |
5 | # MPRIS proxy for Web Media Controller
6 |
7 | ## Installation
8 | ### Dependencies
9 | - `glib2`
10 | - `json-glib`
11 | - Corresponding header packages for building from source if your distro uses them (eg. Debian/Ubuntu)
12 | ### Building from source
13 | ```
14 | $ git clone https://github.com/f1u77y/wmc-mpris.git
15 | $ cd wmc-mpris
16 | $ mkdir build
17 | $ cd build
18 | $ cmake -DCMAKE_BUILD_TYPE=Release ..
19 | $ make
20 | ```
21 | Now you can install this with `sudo make install` and uninstall with
22 | `cat install_manifest.txt | sudo xargs rm` (both commands should be run from `build/`).
23 | No other actions required.
24 |
25 | If you don't have `sudo` privilidges, replace the last 2 commands with the following:
26 |
27 | ```
28 | cmake -DCMAKE_BUILD_TYPE=Release -DINSTALL_FOR_CURRENT_USER=On ..
29 | make
30 | ```
31 |
32 | And then `make install` won't need `sudo`.
33 |
34 | Alternatively, you could build with `meson` (will be the only option in near future):
35 | ```
36 | $ meson build --buildtype release
37 | $ ninja -C build
38 | ```
39 |
40 | And then install with `sudo ninja -C build install` and uninstall with
41 | `sudo ninja -C build uninstall`.
42 |
43 | ### Packages
44 | - [AUR](https://aur.archlinux.org/packages/web-media-controller-mpris-git/)
45 | - [Nix Packages Collection](https://github.com/NixOS/nixpkgs/tree/master/pkgs/applications/misc/web-media-controller)
46 | - Feel free to create packages for other distros and add them to this list
47 |
48 | ## Usage
49 |
50 | In addition to installing/building the web-media-controller, you will need to install [this browser extension](https://github.com/f1u77y/web-media-controller/releases). When both the extension and this program installed, the extension will interact with it in the background and you should be able to go to one of the [supported websites](https://github.com/f1u77y/web-media-controller#supported-websites) and be able to control it with every MPRIS client. Here are a few examples:
51 |
52 | - [playerctl](https://github.com/acrisci/playerctl)
53 | - Default Plasma player widget
54 | - [playbar2](https://github.com/audoban/PlayBar2)
55 | - [GNOME Shell extension](https://extensions.gnome.org/extension/1379/mpris-indicator-button/)
56 | - Many other tools (just search for " mpris widget")
57 |
58 | Some of these tools supports controlling the player via media keys, which is the initial goal of the project.
59 |
--------------------------------------------------------------------------------
/cmake/gdbus_codegen.cmake:
--------------------------------------------------------------------------------
1 | find_program (GDBUS_CODEGEN gdbus-codegen)
2 | if (NOT GDBUS_CODEGEN)
3 | message (SEND_ERROR "Could not find gdbus-codegen program")
4 | endif ()
5 |
6 | function (generate_gdbus_code)
7 | set (OPTIONS GENERATE_OBJECT_MANAGER)
8 | set (ONE_VALUE_ARGS OUTPUT INTERFACE NAMESPACE GENERATE_AUTOCLEANUP INPUT)
9 | set (MUTLI_VALUE_ARGS)
10 | cmake_parse_arguments (ARG "${OPTIONS}"
11 | "${ONE_VALUE_ARGS}"
12 | "${MUTLI_VALUE_ARGS}"
13 | ${ARGN}
14 | )
15 | set (GDBUS_OPTIONS )
16 | if ("${ARG_GENERATE_OBJECT_MANAGER}")
17 | set (GDBUS_OPTIONS ${GDBUS_OPTIONS} --c-generate-object-manager)
18 | endif ()
19 | # TODO depend on version
20 | #if ("${ARG_GENERATE_AUTOCLEANUP}")
21 | #set (GDBUS_OPTIONS ${GDBUS_OPTION} --c-generate-autocleanup ${ARG_GENERATE_AUTOCLEANUP})
22 | #else ()
23 | #set (GDBUS_OPTIONS ${GDBUS_OPTIONS} --c-generate-autocleanup none)
24 | #endif ()
25 | if ("${ARG_NAMESPACE}")
26 | set (GDBUS_OPTIONS ${GDBUS_OPTIONS} --c-namespace "${ARG_NAMESPACE}")
27 | endif ()
28 | add_custom_command (
29 | OUTPUT "${ARG_OUTPUT}.c" "${ARG_OUTPUT}.h"
30 | COMMAND ${GDBUS_CODEGEN} --generate-c-code "${ARG_OUTPUT}"
31 | --interface-prefix "${ARG_INTERFACE}"
32 | ${GDBUS_OPTIONS}
33 | "${ARG_INPUT}"
34 | MAIN_DEPENDENCY "${ARG_INPUT}"
35 | )
36 | endfunction ()
37 |
--------------------------------------------------------------------------------
/include/main.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | extern GMainLoop *loop;
6 |
--------------------------------------------------------------------------------
/include/message.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | void
7 | messages_init();
8 |
9 | JsonParser *
10 | message_read();
11 |
12 | gboolean
13 | message_write(JsonNode *node);
14 |
--------------------------------------------------------------------------------
/include/mpris2.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | #define SERVICE_NAME "org.mpris.MediaPlayer2.web-media-controller"
7 | #define OBJECT_NAME "/org/mpris/MediaPlayer2"
8 |
9 | gboolean
10 | mpris2_init();
11 |
12 | void
13 | mpris2_update_playback_status(JsonNode *argument);
14 |
15 | void
16 | mpris2_update_position(JsonNode *argument);
17 |
18 | void
19 | mpris2_update_volume(JsonNode *argument);
20 | void
21 | mpris2_update_metadata(JsonNode *argument);
22 |
23 | void
24 | mpris2_update_controls_info(JsonNode *argument);
25 |
26 | void
27 | mpris2_update_name(JsonNode *argument);
28 |
--------------------------------------------------------------------------------
/include/proxy.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | gboolean
7 | proxy_listen_commands();
8 |
9 | gboolean
10 | proxy_send_command(JsonNode *command);
11 |
--------------------------------------------------------------------------------
/include/util.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | gchar *
7 | camelcase_to_dashes(const gchar *s);
8 |
9 | gchar *
10 | capitalize(const gchar *s);
11 |
12 | gchar *
13 | json_to_string(JsonNode *root, gboolean is_pretty);
14 |
--------------------------------------------------------------------------------
/me.f1u77y.web_media_controller.chromium.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "me.f1u77y.web_media_controller",
3 | "description": "Allows controlling VK player via MPRIS",
4 | "path": "@EXECUTABLE_PATH@",
5 | "type": "stdio",
6 | "allowed_origins": [
7 | "chrome-extension://hhnmgpbnninfopkhcfigndicniegfocb/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/me.f1u77y.web_media_controller.firefox.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "me.f1u77y.web_media_controller",
3 | "description": "Allows controlling VK player via MPRIS",
4 | "path": "@EXECUTABLE_PATH@",
5 | "type": "stdio",
6 | "allowed_extensions": [
7 | "web-media-controller@f1u77y.me"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/meson.build:
--------------------------------------------------------------------------------
1 | project('web-media-controller-mpris', 'c',
2 | default_options: ['c_std=c99'],
3 | version: '0.1.0',
4 | license: 'Unlicense')
5 |
6 | gnome = import('gnome')
7 | cc = meson.get_compiler('c')
8 |
9 | m_dep = cc.find_library('m', required: false)
10 | glib_dep = dependency('glib-2.0', required: true)
11 | gio_unix_dep = dependency('gio-unix-2.0', required: true)
12 | json_glib_dep = dependency('json-glib-1.0', required: true)
13 |
14 | wmc_deps = [m_dep, glib_dep, gio_unix_dep, json_glib_dep]
15 |
16 | mpris2_core_src = gnome.gdbus_codegen('mpris-core',
17 | sources: 'third-party/mpris-spec/org.mpris.MediaPlayer2.xml',
18 | interface_prefix: 'org.mpris.',
19 | )
20 |
21 | mpris2_player_src = gnome.gdbus_codegen('mpris-player',
22 | sources: 'third-party/mpris-spec/org.mpris.MediaPlayer2.Player.xml',
23 | interface_prefix: 'org.mpris.',
24 | )
25 |
26 | wmc_exe_name = 'web-media-controller-mpris'
27 | wmc_exe = executable(wmc_exe_name,
28 | mpris2_core_src,
29 | mpris2_player_src,
30 | 'src/main.c',
31 | 'src/message.c',
32 | 'src/mpris2.c',
33 | 'src/proxy.c',
34 | 'src/util.c',
35 |
36 | include_directories: include_directories('include'),
37 | dependencies: wmc_deps,
38 | install: true,
39 | )
40 |
41 | config_data = configuration_data({
42 | 'EXECUTABLE_PATH': get_option('prefix') / get_option('bindir') / wmc_exe_name
43 | })
44 |
45 | configure_file(input: 'me.f1u77y.web_media_controller.firefox.json',
46 | output: '@PLAINNAME@',
47 | configuration: config_data)
48 | install_data(meson.build_root() / 'me.f1u77y.web_media_controller.firefox.json',
49 | install_dir: '/usr/lib/mozilla/native-messaging-hosts',
50 | rename: 'me.f1u77y.web_media_controller.json')
51 |
52 | configure_file(input: 'me.f1u77y.web_media_controller.chromium.json',
53 | output: '@PLAINNAME@',
54 | configuration: config_data)
55 | install_data(meson.build_root() / 'me.f1u77y.web_media_controller.chromium.json',
56 | install_dir: '/etc/opt/chrome/native-messaging-hosts',
57 | rename: 'me.f1u77y.web_media_controller.json')
58 | install_data(meson.build_root() / 'me.f1u77y.web_media_controller.chromium.json',
59 | install_dir: '/etc/chromium/native-messaging-hosts',
60 | rename: 'me.f1u77y.web_media_controller.json')
61 |
--------------------------------------------------------------------------------
/src/main.c:
--------------------------------------------------------------------------------
1 | #include "main.h"
2 |
3 | #include "proxy.h"
4 | #include "message.h"
5 | #include "mpris2.h"
6 |
7 | #include
8 |
9 | GMainLoop *loop;
10 |
11 | int main()
12 | {
13 | if (!mpris2_init()) {
14 | g_error("Error at mpris2 initialization, aborting");
15 | }
16 | messages_init();
17 | proxy_listen_commands();
18 |
19 | loop = g_main_loop_new(NULL, FALSE);
20 | g_main_loop_run(loop);
21 |
22 | return 0;
23 | }
24 |
--------------------------------------------------------------------------------
/src/message.c:
--------------------------------------------------------------------------------
1 | #include "message.h"
2 | #include "util.h"
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include
9 | #include
10 |
11 | void
12 | messages_init()
13 | {
14 | freopen(NULL, "rb", stdin);
15 | freopen(NULL, "wb", stdout);
16 | }
17 |
18 | static GBytes *
19 | raw_message_read()
20 | {
21 | guchar *buf = NULL;
22 | GBytes *result = NULL;
23 |
24 | unsigned char size_bytes[4];
25 | if (!fread(size_bytes, 4, 1, stdin)) {
26 | goto out;
27 | }
28 | guint32 size = *(guint32 *)size_bytes;
29 |
30 | buf = g_new(guchar, size + 1);
31 | buf[size] = 0;
32 | if (!fread(buf, size, 1, stdin)) {
33 | goto out;
34 | }
35 | result = g_bytes_new(buf, size);
36 |
37 | out:
38 | g_free(buf);
39 | return result;
40 | }
41 |
42 | static gboolean
43 | raw_message_write(GBytes *message)
44 | {
45 | gboolean result = TRUE;
46 |
47 | gsize size = 0;
48 | gconstpointer data = g_bytes_get_data(message, &size);
49 | unsigned char size_bytes[4];
50 | *(guint32 *)size_bytes = size;
51 |
52 | if (!fwrite(size_bytes, 4, 1, stdout)) {
53 | result = FALSE;
54 | goto out;
55 | }
56 | if (!fwrite(data, size, 1, stdout)) {
57 | result = FALSE;
58 | goto out;
59 | }
60 | fflush(stdout);
61 |
62 | out:
63 | return result;
64 | }
65 |
66 | JsonParser *
67 | message_read()
68 | {
69 | GBytes *raw_message = raw_message_read();
70 | if (!raw_message) {
71 | return NULL;
72 | }
73 | JsonParser *parser = json_parser_new();
74 | gsize size = 0;
75 | gconstpointer data = g_bytes_get_data(raw_message, &size);
76 | if (!json_parser_load_from_data(parser, data, size, NULL)) {
77 | g_object_unref(parser);
78 | return NULL;
79 | }
80 | return parser;
81 | }
82 |
83 | gboolean
84 | message_write(JsonNode *node)
85 | {
86 | gchar *data = json_to_string(node, FALSE);
87 | GBytes *raw_message = g_bytes_new(data, strlen(data));
88 | gboolean result = raw_message_write(raw_message);
89 | g_bytes_unref(raw_message);
90 | g_free(data);
91 | return result;
92 | }
93 |
--------------------------------------------------------------------------------
/src/mpris2.c:
--------------------------------------------------------------------------------
1 | #include "mpris2.h"
2 |
3 | #include "main.h"
4 | #include "proxy.h"
5 | #include "util.h"
6 |
7 | #include "mpris-core.h"
8 | #include "mpris-player.h"
9 |
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 |
16 | static MediaPlayer2 *core = NULL;
17 | static MediaPlayer2Player *player = NULL;
18 |
19 | #define BEGIN_COMMAND(COMMAND) \
20 | JsonBuilder *builder = make_command(COMMAND); \
21 |
22 | #define END_COMMAND() \
23 | json_builder_end_object(builder); \
24 | proxy_send_command(json_builder_get_root(builder)); \
25 | json_builder_reset(builder);
26 |
27 |
28 | static JsonBuilder *
29 | make_command(const gchar *command)
30 | {
31 | JsonBuilder *builder = json_builder_new();
32 | json_builder_begin_object(builder);
33 | json_builder_set_member_name(builder, "command");
34 | json_builder_add_string_value(builder, command);
35 | json_builder_set_member_name(builder, "argument");
36 | return builder;
37 | }
38 |
39 |
40 | #define DEFINE_PLAYER_COMMAND_CALLBACK(NAME, COMMAND) \
41 | static gboolean on_##NAME(MediaPlayer2Player *player, \
42 | GDBusMethodInvocation *call, \
43 | gpointer G_GNUC_UNUSED user_data) \
44 | { \
45 | BEGIN_COMMAND(COMMAND); \
46 | json_builder_add_null_value(builder); \
47 | END_COMMAND(); \
48 | media_player2_player_complete_##NAME(player, call); \
49 | return TRUE; \
50 | } \
51 |
52 | DEFINE_PLAYER_COMMAND_CALLBACK(play, "play")
53 | DEFINE_PLAYER_COMMAND_CALLBACK(pause, "pause")
54 | DEFINE_PLAYER_COMMAND_CALLBACK(previous, "previous")
55 | DEFINE_PLAYER_COMMAND_CALLBACK(next, "next")
56 | DEFINE_PLAYER_COMMAND_CALLBACK(stop, "stop")
57 | DEFINE_PLAYER_COMMAND_CALLBACK(play_pause, "playPause")
58 |
59 | #undef DEFINE_PLAYER_COMMAND_CALLBACK
60 |
61 | static gboolean
62 | on_seek(MediaPlayer2Player *player,
63 | GDBusMethodInvocation *call,
64 | gint64 offset_us,
65 | gpointer G_GNUC_UNUSED user_data)
66 | {
67 | BEGIN_COMMAND("seek");
68 | json_builder_add_double_value(builder, offset_us / 1000.0);
69 | END_COMMAND();
70 | media_player2_player_complete_seek(player, call);
71 | return TRUE;
72 | }
73 |
74 |
75 | static gboolean
76 | on_set_position(MediaPlayer2Player *player,
77 | GDBusMethodInvocation *call,
78 | const gchar *track_id,
79 | gint64 position_us,
80 | gpointer G_GNUC_UNUSED user_data)
81 | {
82 | BEGIN_COMMAND("setPosition");
83 | json_builder_begin_object(builder);
84 | json_builder_set_member_name(builder, "position");
85 | json_builder_add_double_value(builder, position_us / 1000.0);
86 | json_builder_set_member_name(builder, "trackId");
87 | json_builder_add_string_value(builder, track_id);
88 | json_builder_end_object(builder);
89 | END_COMMAND();
90 | media_player2_player_complete_set_position(player, call);
91 | return TRUE;
92 | }
93 |
94 | static gboolean
95 | on_volume_changed(GObject *object) {
96 | gdouble volume = 0;
97 | g_object_get(object, "volume", &volume, NULL);
98 | BEGIN_COMMAND("volume");
99 | json_builder_add_double_value(builder, volume);
100 | END_COMMAND();
101 | return TRUE;
102 | }
103 |
104 | static gboolean
105 | on_quit(MediaPlayer2 *core,
106 | GDBusMethodInvocation *call,
107 | gpointer G_GNUC_UNUSED user_data)
108 | {
109 | g_main_loop_quit(loop);
110 | media_player2_complete_quit(core, call);
111 | return TRUE;
112 | }
113 |
114 | static void
115 | mpris2_core_init()
116 | {
117 | core = media_player2_skeleton_new();
118 |
119 | media_player2_set_can_quit(core, TRUE);
120 | media_player2_set_can_raise(core, FALSE);
121 | media_player2_set_identity(core, "Web Media Controller");
122 |
123 | g_signal_connect (core, "handle-quit", G_CALLBACK(on_quit), NULL);
124 | }
125 |
126 | static void
127 | mpris2_player_init()
128 | {
129 | player = media_player2_player_skeleton_new();
130 |
131 | media_player2_player_set_minimum_rate(player, 1.0);
132 | media_player2_player_set_maximum_rate(player, 1.0);
133 | media_player2_player_set_rate(player, 1.0);
134 | media_player2_player_set_can_control(player, TRUE);
135 |
136 | g_signal_connect(player, "handle-play", G_CALLBACK(on_play), NULL);
137 | g_signal_connect(player, "handle-pause", G_CALLBACK(on_pause), NULL);
138 | g_signal_connect(player, "handle-stop", G_CALLBACK(on_stop), NULL);
139 | g_signal_connect(player, "handle-play-pause", G_CALLBACK(on_play_pause), NULL);
140 | g_signal_connect(player, "handle-previous", G_CALLBACK(on_previous), NULL);
141 | g_signal_connect(player, "handle-next", G_CALLBACK(on_next), NULL);
142 | g_signal_connect(player, "handle-seek", G_CALLBACK(on_seek), NULL);
143 | g_signal_connect(player, "handle-set-position",
144 | G_CALLBACK(on_set_position), NULL);
145 | g_signal_connect(player, "notify::volume", G_CALLBACK(on_volume_changed), NULL);
146 | }
147 |
148 |
149 | gboolean
150 | mpris2_init()
151 | {
152 | GError *error = NULL;
153 | GDBusConnection *bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
154 |
155 | if (!bus) {
156 | g_critical("%s", error->message);
157 | g_error_free(error);
158 | return FALSE;
159 | }
160 |
161 | gchar* service_name = g_strdup_printf("%s.pid%d", SERVICE_NAME, (int)getpid());
162 | guint owner_id = g_bus_own_name_on_connection(bus, service_name,
163 | G_BUS_NAME_OWNER_FLAGS_NONE,
164 | NULL, NULL, NULL, NULL);
165 | g_free(service_name);
166 |
167 | mpris2_core_init();
168 | mpris2_player_init();
169 |
170 | if (!g_dbus_interface_skeleton_export((GDBusInterfaceSkeleton *)core,
171 | bus,
172 | OBJECT_NAME,
173 | &error)
174 | ||
175 | !g_dbus_interface_skeleton_export((GDBusInterfaceSkeleton *)player,
176 | bus,
177 | OBJECT_NAME,
178 | &error))
179 | {
180 | g_bus_unown_name(owner_id);
181 | g_critical("%s", error->message);
182 | g_error_free(error);
183 | return FALSE;
184 | }
185 |
186 | return TRUE;
187 | }
188 |
189 | void
190 | mpris2_update_position(JsonNode *argument)
191 | {
192 | gdouble position = json_node_get_double(argument);
193 | gint64 position_us = round(position * 1000);
194 | media_player2_player_set_position(player, position_us);
195 | }
196 |
197 | void
198 | mpris2_update_volume(JsonNode *argument)
199 | {
200 | gdouble volume = json_node_get_double(argument);
201 | g_signal_handlers_block_by_func(player, G_CALLBACK(on_volume_changed), NULL);
202 | media_player2_player_set_volume(player, volume);
203 | g_signal_handlers_unblock_by_func(player, G_CALLBACK(on_volume_changed), NULL);
204 | }
205 |
206 | static void
207 | add_string_to_builder(JsonArray G_GNUC_UNUSED *array,
208 | guint G_GNUC_UNUSED index,
209 | JsonNode *element_node,
210 | gpointer user_data)
211 | {
212 | GVariantBuilder *builder = (GVariantBuilder *)user_data;
213 | const gchar *value = json_node_get_string(element_node);
214 | g_variant_builder_add(builder, "s", value);
215 | }
216 |
217 | static void
218 | add_value_to_metadata_builder(JsonObject *serialized_metadata,
219 | const gchar *key,
220 | JsonNode *value_node,
221 | gpointer user_data)
222 | {
223 | GVariantBuilder *builder = (GVariantBuilder *)user_data;
224 | if (!g_strcmp0(key, "artist")) {
225 | GVariantBuilder artist;
226 | g_variant_builder_init(&artist, G_VARIANT_TYPE("as"));
227 |
228 | if (JSON_NODE_HOLDS_VALUE(value_node)) {
229 | const gchar *value = json_node_get_string(value_node);
230 | g_variant_builder_add(&artist, "s", value);
231 | } else if (JSON_NODE_HOLDS_ARRAY(value_node)) {
232 | JsonArray *array = json_node_get_array(value_node);
233 | json_array_foreach_element(array,
234 | add_string_to_builder,
235 | &artist);
236 | }
237 | g_variant_builder_add(builder, "{sv}",
238 | "xesam:artist",
239 | g_variant_builder_end(&artist));
240 | } else if (!g_strcmp0(key, "title")) {
241 | const gchar *value = json_node_get_string(value_node);
242 | g_variant_builder_add(builder, "{sv}",
243 | "xesam:title",
244 | g_variant_new_string(value));
245 | } else if (!g_strcmp0(key, "album")) {
246 | const gchar *value = json_node_get_string(value_node);
247 | g_variant_builder_add(builder, "{sv}",
248 | "xesam:album",
249 | g_variant_new_string(value));
250 | } else if (!g_strcmp0(key, "url")) {
251 | const gchar *value = json_node_get_string(value_node);
252 | g_variant_builder_add(builder, "{sv}",
253 | "xesam:url",
254 | g_variant_new_string(value));
255 | } else if (!g_strcmp0(key, "length")) {
256 | gint64 value = json_node_get_int(value_node);
257 | g_variant_builder_add(builder, "{sv}",
258 | "mpris:length",
259 | g_variant_new_int64(value * 1000));
260 | } else if (!g_strcmp0(key, "artUrl")) {
261 | const gchar *value = json_node_get_string(value_node);
262 | g_variant_builder_add(builder, "{sv}",
263 | "mpris:artUrl",
264 | g_variant_new_string(value));
265 | } else if (!g_strcmp0(key, "trackId")) {
266 | const gchar *value = json_node_get_string(value_node);
267 | g_variant_builder_add(builder, "{sv}",
268 | "mpris:trackid",
269 | g_variant_new_object_path(value));
270 | }
271 | }
272 |
273 | void
274 | mpris2_update_metadata(JsonNode *argument)
275 | {
276 | JsonObject *serialized_metadata = json_node_get_object(argument);
277 |
278 | GVariantBuilder builder;
279 | g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
280 |
281 | json_object_foreach_member(serialized_metadata,
282 | add_value_to_metadata_builder,
283 | (void *)(&builder));
284 |
285 | GVariant *metadata = g_variant_builder_end(&builder);
286 | media_player2_player_set_metadata(player, metadata);
287 | }
288 |
289 | void
290 | mpris2_update_playback_status(JsonNode *arg_node)
291 | {
292 | const gchar *value = json_node_get_string(arg_node);
293 | gchar *cap = capitalize(value);
294 | media_player2_player_set_playback_status(player, cap);
295 | g_free(cap);
296 | }
297 |
298 | static void
299 | update_single_controls_property(JsonObject *root,
300 | const gchar* key,
301 | JsonNode *value_node,
302 | gpointer G_GNUC_UNUSED user_data)
303 | {
304 | gchar *name = camelcase_to_dashes(key);
305 | g_object_set(player,
306 | name, json_node_get_boolean(value_node),
307 | NULL);
308 | g_free(name);
309 | }
310 |
311 | void
312 | mpris2_update_controls_info(JsonNode *arg_node)
313 | {
314 | JsonObject *root = json_node_get_object(arg_node);
315 | json_object_foreach_member(root, update_single_controls_property, NULL);
316 | }
317 |
318 | void
319 | mpris2_update_name(JsonNode *arg_node)
320 | {
321 | const gchar *value = json_node_get_string(arg_node);
322 | media_player2_set_identity(core, value);
323 | }
324 |
--------------------------------------------------------------------------------
/src/proxy.c:
--------------------------------------------------------------------------------
1 | #include "proxy.h"
2 |
3 | #include "main.h"
4 | #include "mpris2.h"
5 | #include "message.h"
6 |
7 | #include
8 | #include
9 |
10 | static gpointer
11 | listen_stdio(gpointer G_GNUC_UNUSED user_data)
12 | {
13 | JsonParser *parser = NULL;
14 | do {
15 | parser = message_read();
16 | if (!parser) goto next_iteration;
17 | JsonNode *root_node = json_parser_get_root(parser);
18 | if (!JSON_NODE_HOLDS_OBJECT(root_node)) {
19 | goto next_iteration;
20 | }
21 | JsonObject *root = json_node_get_object(root_node);
22 | JsonNode *cmd_node = json_object_get_member(root, "name");
23 | JsonNode *arg_node = json_object_get_member(root, "value");
24 | if (!cmd_node || !arg_node) goto next_iteration;
25 | if (!JSON_NODE_HOLDS_VALUE(cmd_node)) goto next_iteration;
26 | const gchar *cmd = json_node_get_string(cmd_node);
27 | if (!cmd) {
28 | goto next_iteration;
29 | } else if (!g_strcmp0(cmd, "playbackStatus")) {
30 | mpris2_update_playback_status(arg_node);
31 | } else if (!g_strcmp0(cmd, "currentTime")) {
32 | mpris2_update_position(arg_node);
33 | } else if (!g_strcmp0(cmd, "trackInfo")) {
34 | mpris2_update_metadata(arg_node);
35 | } else if (!g_strcmp0(cmd, "volume")) {
36 | mpris2_update_volume(arg_node);
37 | } else if (!g_strcmp0(cmd, "controlsInfo")) {
38 | mpris2_update_controls_info(arg_node);
39 | } else if (!g_strcmp0(cmd, "name")) {
40 | mpris2_update_name(arg_node);
41 | } else if (!g_strcmp0(cmd, "ping")) {
42 | JsonNode* response = json_node_alloc();
43 | response = json_node_init_string(response, "pong");
44 | message_write(response);
45 | json_node_free(response);
46 | }
47 | next_iteration:
48 | if (parser) {
49 | g_object_unref(parser);
50 | }
51 | } while (parser);
52 | g_main_loop_quit(loop);
53 | return NULL;
54 | }
55 |
56 | gboolean
57 | proxy_listen_commands()
58 | {
59 | GThread *listen_thread = g_thread_new("listen-stdio", listen_stdio, NULL);
60 | g_thread_unref(listen_thread);
61 | return TRUE;
62 | }
63 |
64 | gboolean
65 | proxy_send_command(JsonNode *node)
66 | {
67 | return message_write(node);
68 | }
69 |
--------------------------------------------------------------------------------
/src/util.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 |
6 | gchar *
7 | camelcase_to_dashes(const gchar *s)
8 | {
9 | gchar *result = g_new(gchar, strlen(s) * 2 + 1);
10 | gchar *cur = result;
11 | for (; *s != 0; ++s) {
12 | if (isupper(*s)) {
13 | *(cur++) = '-';
14 | }
15 | *(cur++) = tolower(*s);
16 | }
17 | *cur = 0;
18 | return result;
19 | }
20 |
21 | gchar *
22 | capitalize(const gchar *s)
23 | {
24 | gchar *result = g_strdup(s);
25 | if (strlen(result) > 0) {
26 | result[0] = toupper(result[0]);
27 | }
28 | return result;
29 | }
30 |
31 | gchar *
32 | json_to_string(JsonNode *root, gboolean is_pretty)
33 | {
34 | JsonGenerator *gen = json_generator_new();
35 | json_generator_set_root(gen, root);
36 | json_generator_set_pretty(gen, is_pretty);
37 | gchar *result = json_generator_to_data(gen, NULL);
38 | return result;
39 | }
40 |
--------------------------------------------------------------------------------
/third-party/mpris-spec/org.mpris.MediaPlayer2.Player.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | This interface implements the methods for querying and providing basic
8 | control over what is currently playing.
9 |
10 |
11 |
12 |
13 |
14 |
15 | A track is currently playing.
16 |
17 |
18 |
19 |
20 | A track is currently paused.
21 |
22 |
23 |
24 |
25 | There is no track currently playing.
26 |
27 |
28 |
29 | A playback state.
30 |
31 |
32 |
33 |
34 |
35 |
36 | The playback will stop when there are no more tracks to play
37 |
38 |
39 |
40 |
41 | The current track will start again from the begining once it has finished playing
42 |
43 |
44 |
45 |
46 | The playback loops through a list of tracks
47 |
48 |
49 |
50 | A repeat / loop status
51 |
52 |
53 |
54 |
55 |
56 | Unique track identifier.
57 |
58 | If the media player implements the TrackList interface and allows
59 | the same track to appear multiple times in the tracklist,
60 | this must be unique within the scope of the tracklist.
61 |
62 |
63 | Note that this should be a valid D-Bus object id, although clients
64 | should not assume that any object is actually exported with any
65 | interfaces at that path.
66 |
67 |
68 | Media players may not use any paths starting with
69 | /org/mpris unless explicitly allowed by this specification.
70 | Such paths are intended to have special meaning, such as
71 | /org/mpris/MediaPlayer2/TrackList/NoTrack
72 | to indicate "no track".
73 |
74 |
75 |
76 | This is a D-Bus object id as that is the definitive way to have
77 | unique identifiers on D-Bus. It also allows for future optional
78 | expansions to the specification where tracks are exported to D-Bus
79 | with an interface similar to org.gnome.UPnP.MediaItem2.
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | A playback rate
88 |
89 | This is a multiplier, so a value of 0.5 indicates that playback is
90 | happening at half speed, while 1.5 means that 1.5 seconds of "track time"
91 | is consumed every second.
92 |
93 |
94 |
95 |
96 |
97 |
98 | Audio volume level
99 |
100 | - 0.0 means mute.
101 | - 1.0 is a sensible maximum volume level (ex: 0dB).
102 |
103 |
104 | Note that the volume may be higher than 1.0, although generally
105 | clients should not attempt to set it above 1.0.
106 |
107 |
108 |
109 |
110 |
111 |
112 | Time in microseconds.
113 |
114 |
115 |
116 |
117 |
118 | Skips to the next track in the tracklist.
119 |
120 | If there is no next track (and endless playback and track
121 | repeat are both off), stop playback.
122 |
123 | If playback is paused or stopped, it remains that way.
124 |
125 | If CanGoNext is
126 | false, attempting to call this method should have
127 | no effect.
128 |
129 |
130 |
131 |
132 |
133 |
134 | Skips to the previous track in the tracklist.
135 |
136 | If there is no previous track (and endless playback and track
137 | repeat are both off), stop playback.
138 |
139 | If playback is paused or stopped, it remains that way.
140 |
141 | If CanGoPrevious is
142 | false, attempting to call this method should have
143 | no effect.
144 |
145 |
146 |
147 |
148 |
149 |
150 | Pauses playback.
151 | If playback is already paused, this has no effect.
152 |
153 | Calling Play after this should cause playback to start again
154 | from the same position.
155 |
156 |
157 | If CanPause is
158 | false, attempting to call this method should have
159 | no effect.
160 |
161 |
162 |
163 |
164 |
165 |
166 | Pauses playback.
167 | If playback is already paused, resumes playback.
168 | If playback is stopped, starts playback.
169 |
170 | If CanPause is
171 | false, attempting to call this method should have
172 | no effect and raise an error.
173 |
174 |
175 |
176 |
177 |
178 |
179 | Stops playback.
180 | If playback is already stopped, this has no effect.
181 |
182 | Calling Play after this should cause playback to
183 | start again from the beginning of the track.
184 |
185 |
186 | If CanControl is
187 | false, attempting to call this method should have
188 | no effect and raise an error.
189 |
190 |
191 |
192 |
193 |
194 |
195 | Starts or resumes playback.
196 | If already playing, this has no effect.
197 | If paused, playback resumes from the current position.
198 | If there is no track to play, this has no effect.
199 |
200 | If CanPlay is
201 | false, attempting to call this method should have
202 | no effect.
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 | The number of microseconds to seek forward.
211 |
212 |
213 |
214 |
215 | Seeks forward in the current track by the specified number
216 | of microseconds.
217 |
218 |
219 | A negative value seeks back. If this would mean seeking
220 | back further than the start of the track, the position
221 | is set to 0.
222 |
223 |
224 | If the value passed in would mean seeking beyond the end
225 | of the track, acts like a call to Next.
226 |
227 |
228 | If the CanSeek property is false,
229 | this has no effect.
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 | The currently playing track's identifier.
238 |
239 | If this does not match the id of the currently-playing track,
240 | the call is ignored as "stale".
241 |
242 |
243 | /org/mpris/MediaPlayer2/TrackList/NoTrack
244 | is not a valid value for this argument.
245 |
246 |
247 |
248 |
249 |
250 | Track position in microseconds.
251 | This must be between 0 and <track_length>.
252 |
253 |
254 |
255 | Sets the current track position in microseconds.
256 | If the Position argument is less than 0, do nothing.
257 |
258 | If the Position argument is greater than the track length,
259 | do nothing.
260 |
261 |
262 | If the CanSeek property is false,
263 | this has no effect.
264 |
265 |
266 |
267 | The reason for having this method, rather than making
268 | Position writable, is to include
269 | the TrackId argument to avoid race conditions where a client tries
270 | to seek to a position when the track has already changed.
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 | Uri of the track to load. Its uri scheme should be an element of the
281 | org.mpris.MediaPlayer2.SupportedUriSchemes
282 | property and the mime-type should match one of the elements of the
283 | org.mpris.MediaPlayer2.SupportedMimeTypes.
284 |
285 |
286 |
287 |
288 | Opens the Uri given as an argument
289 | If the playback is stopped, starts playing
290 |
291 | If the uri scheme or the mime-type of the uri to open is not supported,
292 | this method does nothing and may raise an error. In particular, if the
293 | list of available uri schemes is empty, this method may not be
294 | implemented.
295 |
296 | Clients should not assume that the Uri has been opened as soon as this
297 | method returns. They should wait until the mpris:trackid field in the
298 | Metadata property changes.
299 |
300 |
301 | If the media player implements the TrackList interface, then the
302 | opened track should be made part of the tracklist, the
303 | org.mpris.MediaPlayer2.TrackList.TrackAdded or
304 | org.mpris.MediaPlayer2.TrackList.TrackListReplaced
305 | signal should be fired, as well as the
306 | org.freedesktop.DBus.Properties.PropertiesChanged
307 | signal on the tracklist interface.
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 | The current playback status.
316 |
317 | May be "Playing", "Paused" or "Stopped".
318 |
319 |
320 |
321 |
322 |
324 |
325 |
326 |
327 | The current loop / repeat status
328 | May be:
329 |
330 | - "None" if the playback will stop when there are no more tracks to play
331 | - "Track" if the current track will start again from the begining once it has finished playing
332 | - "Playlist" if the playback loops through a list of tracks
333 |
334 |
335 |
336 | If CanControl is
337 | false, attempting to set this property should have
338 | no effect and raise an error.
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 | The current playback rate.
347 |
348 | The value must fall in the range described by
349 | MinimumRate and
350 | MaximumRate, and must not be 0.0. If
351 | playback is paused, the PlaybackStatus
352 | property should be used to indicate this. A value of 0.0 should not
353 | be set by the client. If it is, the media player should act as
354 | though Pause was called.
355 |
356 |
357 | If the media player has no ability to play at speeds other than the
358 | normal playback rate, this must still be implemented, and must
359 | return 1.0. The MinimumRate and
360 | MaximumRate properties must also be
361 | set to 1.0.
362 |
363 |
364 | Not all values may be accepted by the media player. It is left to
365 | media player implementations to decide how to deal with values they
366 | cannot use; they may either ignore them or pick a "best fit" value.
367 | Clients are recommended to only use sensible fractions or multiples
368 | of 1 (eg: 0.5, 0.25, 1.5, 2.0, etc).
369 |
370 |
371 |
372 | This allows clients to display (reasonably) accurate progress bars
373 | without having to regularly query the media player for the current
374 | position.
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 | A value of false indicates that playback is
386 | progressing linearly through a playlist, while true
387 | means playback is progressing through a playlist in some other order.
388 |
389 |
390 | If CanControl is
391 | false, attempting to set this property should have
392 | no effect and raise an error.
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 | The metadata of the current element.
401 |
402 | If there is a current track, this must have a "mpris:trackid" entry
403 | (of D-Bus type "o") at the very least, which contains a D-Bus path that
404 | uniquely identifies this track.
405 |
406 |
407 | See the type documentation for more details.
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 | The volume level.
416 |
417 | When setting, if a negative value is passed, the volume
418 | should be set to 0.0.
419 |
420 |
421 | If CanControl is
422 | false, attempting to set this property should have
423 | no effect and raise an error.
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 | The current track position in microseconds, between 0 and
433 | the 'mpris:length' metadata entry (see Metadata).
434 |
435 |
436 | Note: If the media player allows it, the current playback position
437 | can be changed either the SetPosition method or the Seek method on
438 | this interface. If this is not the case, the
439 | CanSeek property is false, and
440 | setting this property has no effect and can raise an error.
441 |
442 |
443 | If the playback progresses in a way that is inconstistant with the
444 | Rate property, the
445 | Seeked signal is emited.
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 | The minimum value which the Rate
455 | property can take.
456 | Clients should not attempt to set the
457 | Rate property below this value.
458 |
459 |
460 | Note that even if this value is 0.0 or negative, clients should
461 | not attempt to set the Rate property
462 | to 0.0.
463 |
464 | This value should always be 1.0 or less.
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 | The maximum value which the Rate
473 | property can take.
474 | Clients should not attempt to set the
475 | Rate property above this value.
476 |
477 |
478 | This value should always be 1.0 or greater.
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 | Whether the client can call the Next
488 | method on this interface and expect the current track to change.
489 |
490 |
491 | If it is unknown whether a call to Next will
492 | be successful (for example, when streaming tracks), this property should
493 | be set to true.
494 |
495 |
496 | If CanControl is
497 | false, this property should also be
498 | false.
499 |
500 |
501 |
502 | Even when playback can generally be controlled, there may not
503 | always be a next track to move to.
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 | Whether the client can call the
514 | Previous method on this interface and
515 | expect the current track to change.
516 |
517 |
518 | If it is unknown whether a call to Previous
519 | will be successful (for example, when streaming tracks), this property
520 | should be set to true.
521 |
522 |
523 | If CanControl is
524 | false, this property should also be
525 | false.
526 |
527 |
528 |
529 | Even when playback can generally be controlled, there may not
530 | always be a next previous to move to.
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 | Whether playback can be started using
541 | Play or
542 | PlayPause.
543 |
544 |
545 | Note that this is related to whether there is a "current track": the
546 | value should not depend on whether the track is currently paused or
547 | playing. In fact, if a track is currently playing (and
548 | CanControl is true),
549 | this should be true.
550 |
551 |
552 | If CanControl is
553 | false, this property should also be
554 | false.
555 |
556 |
557 |
558 | Even when playback can generally be controlled, it may not be
559 | possible to enter a "playing" state, for example if there is no
560 | "current track".
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 | Whether playback can be paused using
570 | Pause or
571 | PlayPause.
572 |
573 |
574 | Note that this is an intrinsic property of the current track: its
575 | value should not depend on whether the track is currently paused or
576 | playing. In fact, if playback is currently paused (and
577 | CanControl is true),
578 | this should be true.
579 |
580 |
581 | If CanControl is
582 | false, this property should also be
583 | false.
584 |
585 |
586 |
587 | Not all media is pausable: it may not be possible to pause some
588 | streamed media, for example.
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 | Whether the client can control the playback position using
599 | Seek and
600 | SetPosition. This may be different for
601 | different tracks.
602 |
603 |
604 | If CanControl is
605 | false, this property should also be
606 | false.
607 |
608 |
609 |
610 | Not all media is seekable: it may not be possible to seek when
611 | playing some streamed media, for example.
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 | Whether the media player may be controlled over this interface.
621 |
622 | This property is not expected to change, as it describes an intrinsic
623 | capability of the implementation.
624 |
625 |
626 | If this is false, clients should assume that all
627 | properties on this interface are read-only (and will raise errors
628 | if writing to them is attempted), no methods are implemented
629 | and all other properties starting with "Can" are also
630 | false.
631 |
632 |
633 |
634 | This allows clients to determine whether to present and enable
635 | controls to the user in advance of attempting to call methods
636 | and write to properties.
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 | The new position, in microseconds.
646 |
647 |
648 |
649 |
650 | Indicates that the track position has changed in a way that is
651 | inconsistant with the current playing state.
652 |
653 | When this signal is not received, clients should assume that:
654 |
655 | -
656 | When playing, the position progresses according to the rate property.
657 |
658 | - When paused, it remains constant.
659 |
660 |
661 | This signal does not need to be emitted when playback starts
662 | or when the track changes, unless the track is starting at an
663 | unexpected position. An expected position would be the last
664 | known one when going from Paused to Playing, and 0 when going from
665 | Stopped to Playing.
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
--------------------------------------------------------------------------------
/third-party/mpris-spec/org.mpris.MediaPlayer2.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Brings the media player's user interface to the front using any
10 | appropriate mechanism available.
11 |
12 |
13 | The media player may be unable to control how its user interface
14 | is displayed, or it may not have a graphical user interface at all.
15 | In this case, the CanRaise property is
16 | false and this method does nothing.
17 |
18 |
19 |
20 |
21 |
22 |
23 | Causes the media player to stop running.
24 |
25 | The media player may refuse to allow clients to shut it down.
26 | In this case, the CanQuit property is
27 | false and this method does nothing.
28 |
29 |
30 | Note: Media players which can be D-Bus activated, or for which there is
31 | no sensibly easy way to terminate a running instance (via the main
32 | interface or a notification area icon for example) should allow clients
33 | to use this method. Otherwise, it should not be needed.
34 |
35 | If the media player does not have a UI, this should be implemented.
36 |
37 |
38 |
39 |
40 |
41 |
42 | If false, calling
43 | Quit will have no effect, and may
44 | raise a NotSupported error. If true, calling
45 | Quit will cause the media application
46 | to attempt to quit (although it may still be prevented from quitting
47 | by the user, for example).
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Whether the media player is occupying the fullscreen.
57 |
58 | This is typically used for videos. A value of true
59 | indicates that the media player is taking up the full screen.
60 |
61 |
62 | Media centre software may well have this value fixed to true
63 |
64 |
65 | If CanSetFullscreen is true,
66 | clients may set this property to true to tell the media player
67 | to enter fullscreen mode, or to false to return to windowed
68 | mode.
69 |
70 |
71 | If CanSetFullscreen is false,
72 | then attempting to set this property should have no effect, and may raise
73 | an error. However, even if it is true, the media player
74 | may still be unable to fulfil the request, in which case attempting to set
75 | this property will have no effect (but should not raise an error).
76 |
77 |
78 |
79 | This allows remote control interfaces, such as LIRC or mobile devices like
80 | phones, to control whether a video is shown in fullscreen.
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | If false, attempting to set
92 | Fullscreen will have no effect, and may
93 | raise an error. If true, attempting to set
94 | Fullscreen will not raise an error, and (if it
95 | is different from the current value) will cause the media player to attempt to
96 | enter or exit fullscreen mode.
97 |
98 |
99 | Note that the media player may be unable to fulfil the request.
100 | In this case, the value will not change. If the media player knows in
101 | advance that it will not be able to fulfil the request, however, this
102 | property should be false.
103 |
104 |
105 |
106 | This allows clients to choose whether to display controls for entering
107 | or exiting fullscreen mode.
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | If false, calling
117 | Raise will have no effect, and may
118 | raise a NotSupported error. If true, calling
119 | Raise will cause the media application
120 | to attempt to bring its user interface to the front, although it may
121 | be prevented from doing so (by the window manager, for example).
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | Indicates whether the /org/mpris/MediaPlayer2
130 | object implements the org.mpris.MediaPlayer2.TrackList
131 | interface.
132 |
133 |
134 |
135 |
136 |
137 |
138 | A friendly name to identify the media player to users.
139 | This should usually match the name found in .desktop files
140 | (eg: "VLC media player").
141 |
142 |
143 |
144 |
145 |
146 |
147 | The basename of an installed .desktop file which complies with the Desktop entry specification,
148 | with the ".desktop" extension stripped.
149 |
150 | Example: The desktop entry file is "/usr/share/applications/vlc.desktop",
151 | and this property contains "vlc"
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | The URI schemes supported by the media player.
160 |
161 |
162 | This can be viewed as protocols supported by the player in almost
163 | all cases. Almost every media player will include support for the
164 | "file" scheme. Other common schemes are "http" and "rtsp".
165 |
166 |
167 | Note that URI schemes should be lower-case.
168 |
169 |
170 |
171 | This is important for clients to know when using the editing
172 | capabilities of the Playlist interface, for example.
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 | The mime-types supported by the media player.
182 |
183 |
184 | Mime-types should be in the standard format (eg: audio/mpeg or
185 | application/ogg).
186 |
187 |
188 |
189 | This is important for clients to know when using the editing
190 | capabilities of the Playlist interface, for example.
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
--------------------------------------------------------------------------------