#define _POSIX_C_SOURCE 200809L #include "mpv.h" #include <lektor/common.h> #include <lektor/database.h> #include <strings.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <sqlite3.h> enum { STATE_STOP = 0, STATE_PLAY = 1, STATE_PAUSE = 2, }; void lmpv_free(mpv_handle **ctx) { RETURN_UNLESS(ctx && *ctx, "Missing mpv ctx", NOTHING); static const char *cmd[] = {"quit", NULL}; mpv_command(*ctx, cmd); mpv_destroy(*ctx); *ctx = NULL; LOG_INFO("WINDOW", "The mpv context was destroyed"); } mpv_handle * lmpv_prepare(volatile sqlite3 *db) { mpv_handle *ctx = mpv_create(); int status; RETURN_UNLESS(ctx, "Failed to create context", NULL); char _opt[LKT_LINE_MAX]; #define MPV_SET_OPTION(opt, value) \ if ((status = mpv_set_option_string(ctx, opt, value)) < 0) { \ LOG_ERROR("WINDOW", "Failed to set %s to %s: %s", \ opt, value, mpv_error_string(status)); \ return NULL; \ } #define MPV_SET_FROM_INI(opt, section, key) \ if (!database_config_get_text(db, section, key, _opt, LKT_LINE_MAX)) { \ LOG_WARN("WINDOW", "Failed to get option " \ key " in section " section); \ return ctx; \ } \ MPV_SET_OPTION(opt, _opt); MPV_SET_OPTION("input-default-bindings", "yes"); MPV_SET_OPTION("input-vo-keyboard", "yes"); MPV_SET_OPTION("replaygain", "track"); MPV_SET_OPTION("gpu-context", "x11"); /* Wayland you sucks */ MPV_SET_OPTION("demuxer-readahead-secs", "5.0"); MPV_SET_OPTION("demuxer-max-bytes", "100M"); MPV_SET_OPTION("hwdec", "yes"); MPV_SET_FROM_INI("osd-font-size", "player", "font_size"); MPV_SET_FROM_INI("osd-font", "player", "font_name"); MPV_SET_FROM_INI("osd-duration", "player", "msg_duration"); return ctx; #undef MPV_SET_OPTION #undef MPV_SET_FROM_INI } int lmpv_observe_properties(mpv_handle *ctx) { return (mpv_observe_property(ctx, 0, "ao-volume", MPV_FORMAT_INT64) >= 0) && (mpv_observe_property(ctx, 0, "duration", MPV_FORMAT_INT64) >= 0) && (mpv_observe_property(ctx, 0, "time-pos", MPV_FORMAT_INT64) >= 0) && (mpv_observe_property(ctx, 0, "pause", MPV_FORMAT_FLAG) >= 0); } mpv_handle * lmpv_new(unsigned long int wid, volatile sqlite3 *db) { mpv_handle *ctx = lmpv_prepare(db); int status; if ((status = mpv_set_property(ctx, "wid", MPV_FORMAT_INT64, &wid)) < 0) { LOG_ERROR("WINDOW", "Failed to set wid: %s", mpv_error_string(status)); goto error; } if ((status = mpv_initialize(ctx)) < 0) { LOG_ERROR("WINDOW", "Failed to init mpv: %s", mpv_error_string(status)); goto error; } if (!lmpv_observe_properties(ctx)) { LOG_ERROR("WINDOW", "Failed to observe properties"); goto error; } LOG_INFO("WINDOW", "Create mpv context"); return ctx; error: lmpv_free(&ctx); return NULL; } int lmpv_set_volume(mpv_handle *ctx, int vol) { RETURN_UNLESS(ctx, "Missing mpv ctx", 1); int status; char str[5]; memset(str, 0, 5); safe_snprintf(str, 4, "%d", vol); const char *cmd[] = {"set", "ao-volume", str, NULL}; if ((status = mpv_command_async(ctx, 0, cmd)) < 0) { LOG_ERROR("WINDOW", "Failed to execute command: %s", mpv_error_string(status)); return 1; } return 0; } int lmpv_load_file(mpv_handle *ctx, const char *file) { RETURN_UNLESS(ctx, "Missing mpv ctx", 1); RETURN_IF(access(file, R_OK), "Failed to read file", 1); const char *cmd1[] = { "loadfile", file, "replace", NULL }; const char *cmd2[] = { "set", "pause", "0", NULL }; int status; if ((status = mpv_command_async(ctx, 0, cmd1)) < 0) { LOG_ERROR("WINDOW", "Failed to add '%s': %s", file, mpv_error_string(status)); return 1; } if ((status = mpv_command_async(ctx, 0, cmd2)) < 0) { LOG_ERROR("WINDOW", "Failed to set state to play: %s", mpv_error_string(status)); return 1; } return 0; } int lmpv_toggle_pause(mpv_handle *ctx) { RETURN_UNLESS(ctx, "Missing mpv ctx", 1); const char *cmd[] = {"cycle", "pause", "up", NULL}; int status; if ((status = mpv_command_async(ctx, 0, cmd)) < 0) { LOG_ERROR("WINDOW", "Failed issus command: %s", mpv_error_string(status)); return 1; } return 0; } int lmpv_handle(struct lkt_module *mod, mpv_handle *ctx, struct queue *queue, volatile int *time_pos, volatile int *time_duration, volatile int *state) { size_t ao_volume; mpv_event *event = NULL; mpv_event_property *prop; RETURN_UNLESS(ctx, "Invalid argument", 1); loop: event = mpv_wait_event(ctx, 0); switch (event->event_id) { case MPV_EVENT_PAUSE: *state = STATE_PAUSE; lkt_queue_send(queue, lkt_event_play_toggle, LKT_PLAY_PAUSE); break; case MPV_EVENT_UNPAUSE: *state = STATE_PLAY; lkt_queue_send(queue, lkt_event_play_toggle, LKT_PLAY_PLAY); break; case MPV_EVENT_SHUTDOWN: *time_pos = (*time_duration = 0); *state = STATE_STOP; lkt_queue_send(queue, lkt_event_play_toggle, LKT_PLAY_STOP); reg_call(mod->reg, "close", 1, mod->data); return 1; case MPV_EVENT_NONE: return 1; case MPV_EVENT_IDLE: if (*state != STATE_STOP && *time_pos > 0) lkt_queue_send(queue, lkt_event_play_next, NULL); break; case MPV_EVENT_PROPERTY_CHANGE: prop = (mpv_event_property *) event->data; if (prop->format == MPV_FORMAT_NONE) break; /* MPV volume (BUG: The flag is not MPV_FORMAT_NONE only at the end of the song...) */ if (STR_MATCH(prop->name, "ao-volume") && prop->format == MPV_FORMAT_INT64) { ao_volume = *(int *) prop->data; lkt_queue_send(queue, lkt_event_prop_vol, (void *) ao_volume); } /* File duration */ if (STR_MATCH(prop->name, "duration") && prop->format == MPV_FORMAT_INT64) { *time_duration = *(int *) prop->data; lkt_queue_send(queue, lkt_event_prop_dur, (void *) (size_t) *time_duration); } if (STR_MATCH(prop->name, "time-pos") && prop->format == MPV_FORMAT_INT64) { *time_pos = *(int *) prop->data; lkt_queue_send(queue, lkt_event_prop_time, (void *) (size_t) *time_pos); } /* Pause state */ if (STR_MATCH(prop->name, "pause") && prop->format == MPV_FORMAT_FLAG) { if (* (bool *) prop->data) { lkt_queue_send(queue, lkt_event_play_toggle, LKT_PLAY_PAUSE); *state = STATE_PAUSE; } else { lkt_queue_send(queue, lkt_event_play_toggle, LKT_PLAY_PLAY); *state = STATE_PLAY; } } break; /* Ignored */ case MPV_EVENT_VIDEO_RECONFIG: case MPV_EVENT_AUDIO_RECONFIG: break; case MPV_EVENT_COMMAND_REPLY: free((void *) event->reply_userdata); break; default: LOG_WARN("WINDOW", "Unhandled mpv event '%s'", mpv_event_name(event->event_id)); break; } goto loop; /* A loop without indentation. Ugly but better for not-really-wide screens */ }