├── .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 | --------------------------------------------------------------------------------