command: add a mechanism to allow scripts to intercept file loads

A vague idea to get something similar what libquvi did.

Undocumented because it might change a lot, or even be removed. To give
an idea what it does, a Lua script could do the following:

--                      type       ID priority
mp.commandv("hook_add", "on_load", 0, 0)
mp.register_script_message("hook_run", function(param, param2)
    -- param is "0", the user-chosen ID from the hook_add command
    -- param2 is the magic value that has to be passed to finish
    -- the hook
    mp.resume_all()
    -- do something, maybe set options that are reset on end:
    mp.set_property("file-local-options/name", "value")
    -- or change the URL that's being opened:
    local url = mp.get_property("stream-open-filename")
    mp.set_property("stream-open-filename", url .. ".png")
    -- let the player (or the next script) continue
    mp.commandv("hook_ack", param2)
end)
This commit is contained in:
wm4 2014-10-15 23:09:53 +02:00
parent bc0ed90481
commit 8e4fa5fcd1
8 changed files with 193 additions and 12 deletions

View File

@ -170,6 +170,9 @@ const struct mp_cmd_def mp_cmds[] = {
{ MP_CMD_WRITE_WATCH_LATER_CONFIG, "write_watch_later_config", },
{ MP_CMD_HOOK_ADD, "hook_add", { ARG_STRING, ARG_INT, ARG_INT } },
{ MP_CMD_HOOK_ACK, "hook_ack", { ARG_STRING } },
{0}
};

View File

@ -97,6 +97,9 @@ enum mp_command_type {
MP_CMD_WRITE_WATCH_LATER_CONFIG,
MP_CMD_HOOK_ADD,
MP_CMD_HOOK_ACK,
// Internal
MP_CMD_COMMAND_LIST, // list of sub-commands in args[0].v.p
};

View File

@ -82,6 +82,7 @@ typedef struct mp_cmd {
struct mp_cmd *queue_next;
double scale; // for scaling numeric arguments
const struct mp_cmd_def *def;
char *sender; // name of the client API user which sent this
} mp_cmd_t;
struct mp_input_src {

View File

@ -871,6 +871,8 @@ static int run_client_command(mpv_handle *ctx, struct mp_cmd *cmd)
if (mp_input_is_abort_cmd(cmd))
mp_cancel_trigger(ctx->mpctx->playback_abort);
cmd->sender = ctx->name;
struct cmd_request req = {
.mpctx = ctx->mpctx,
.cmd = cmd,
@ -905,6 +907,8 @@ static int run_cmd_async(mpv_handle *ctx, uint64_t ud, struct mp_cmd *cmd)
if (!cmd)
return MPV_ERROR_INVALID_PARAMETER;
cmd->sender = ctx->name;
struct cmd_request *req = talloc_ptrtype(NULL, req);
*req = (struct cmd_request){
.mpctx = ctx->mpctx,

View File

@ -85,6 +85,10 @@ struct command_ctx {
struct sub_bitmaps overlay_osd[2];
struct sub_bitmaps *overlay_osd_current;
struct hook_handler **hooks;
int num_hooks;
int64_t hook_seq; // for hook_handler.seq
struct ao_device_list *cached_ao_devices;
};
@ -94,11 +98,107 @@ struct overlay {
struct sub_bitmap osd;
};
struct hook_handler {
char *client; // client API user name
char *type; // kind of hook, e.g. "on_load"
char *user_id; // numeric user-chosen ID, printed as string
int priority; // priority for global hook order
int64_t seq; // unique ID (also age -> fixed order for equal priorities)
bool active; // hook is currently in progress (only 1 at a time for now)
};
static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype,
const char *cmd, const char *arg);
static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
struct m_obj_settings *new_chain);
bool mp_hook_test_completion(struct MPContext *mpctx, char *type)
{
struct command_ctx *cmd = mpctx->command_ctx;
for (int n = 0; n < cmd->num_hooks; n++) {
struct hook_handler *h = cmd->hooks[n];
if (h->active && strcmp(h->type, type) == 0)
return false;
}
return true;
}
static bool send_hook_msg(struct MPContext *mpctx, struct hook_handler *h,
char *cmd)
{
mpv_event_client_message *m = talloc_ptrtype(NULL, m);
*m = (mpv_event_client_message){0};
MP_TARRAY_APPEND(m, m->args, m->num_args, "hook_run");
MP_TARRAY_APPEND(m, m->args, m->num_args, talloc_strdup(m, h->user_id));
MP_TARRAY_APPEND(m, m->args, m->num_args, talloc_strdup(m, h->type));
bool r =
mp_client_send_event(mpctx, h->client, MPV_EVENT_CLIENT_MESSAGE, m) >= 0;
if (!r)
MP_WARN(mpctx, "Sending hook command failed.\n");
return r;
}
// client==NULL means start the hook chain
void mp_hook_run(struct MPContext *mpctx, char *client, char *type)
{
struct command_ctx *cmd = mpctx->command_ctx;
struct hook_handler *next = NULL;
bool found_current = !client;
for (int n = 0; n < cmd->num_hooks; n++) {
struct hook_handler *h = cmd->hooks[n];
if (!found_current) {
if (h->active && strcmp(h->type, type) == 0) {
h->active = false;
found_current = true;
}
} else if (strcmp(h->type, type) == 0) {
next = h;
break;
}
}
if (!next)
return;
MP_VERBOSE(mpctx, "Running hook: %s/%s\n", next->client, type);
next->active = true;
send_hook_msg(mpctx, next, "hook_run");
}
void mp_hook_abort(struct MPContext *mpctx, char *type)
{
struct command_ctx *cmd = mpctx->command_ctx;
for (int n = 0; n < cmd->num_hooks; n++) {
struct hook_handler *h = cmd->hooks[n];
if (h->active && strcmp(h->type, type) == 0)
send_hook_msg(mpctx, h, "hook_abort");
}
}
static int compare_hook(const void *pa, const void *pb)
{
struct hook_handler **h1 = (void *)pa;
struct hook_handler **h2 = (void *)pb;
if ((*h1)->priority != (*h2)->priority)
return (*h1)->priority - (*h2)->priority;
return (*h1)->seq - (*h2)->seq;
}
static void mp_hook_add(struct MPContext *mpctx, char *client, char *name,
int id, int pri)
{
struct command_ctx *cmd = mpctx->command_ctx;
struct hook_handler *h = talloc_ptrtype(cmd, h);
int64_t seq = cmd->hook_seq++;
*h = (struct hook_handler){
.client = talloc_strdup(h, client),
.type = talloc_strdup(h, name),
.user_id = talloc_asprintf(h, "%d", id),
.priority = pri,
.seq = seq,
};
MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h);
qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook);
}
// Call before a seek, in order to allow revert_seek to undo the seek.
static void mark_seek(struct MPContext *mpctx)
{
@ -213,6 +313,27 @@ static int mp_property_filename(void *ctx, struct m_property *prop,
return r;
}
static int mp_property_stream_open_filename(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
if (!mpctx->stream_open_filename || !mpctx->playing)
return M_PROPERTY_UNAVAILABLE;
switch (action) {
case M_PROPERTY_SET: {
if (mpctx->stream)
return M_PROPERTY_ERROR;
mpctx->stream_open_filename =
talloc_strdup(mpctx->stream_open_filename, *(char **)arg);
return M_PROPERTY_OK;
}
case M_PROPERTY_GET_TYPE:
case M_PROPERTY_GET:
return m_property_strdup_ro(action, arg, mpctx->stream_open_filename);
}
return M_PROPERTY_NOT_IMPLEMENTED;
}
static int mp_property_file_size(void *ctx, struct m_property *prop,
int action, void *arg)
{
@ -2771,6 +2892,7 @@ static const struct m_property mp_properties[] = {
{"loop-file", mp_property_generic_option},
{"speed", mp_property_playback_speed},
{"filename", mp_property_filename},
{"stream-open-filename", mp_property_stream_open_filename},
{"file-size", mp_property_file_size},
{"path", mp_property_path},
{"media-title", mp_property_media_title},
@ -4066,6 +4188,22 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
break;
}
case MP_CMD_HOOK_ADD:
if (!cmd->sender) {
MP_ERR(mpctx, "Can be used from client API only.\n");
return -1;
}
mp_hook_add(mpctx, cmd->sender, cmd->args[0].v.s, cmd->args[1].v.i,
cmd->args[2].v.i);
break;
case MP_CMD_HOOK_ACK:
if (!cmd->sender) {
MP_ERR(mpctx, "Can be used from client API only.\n");
return -1;
}
mp_hook_run(mpctx, cmd->sender, cmd->args[0].v.s);
break;
default:
MP_VERBOSE(mpctx, "Received unknown cmd %s\n", cmd->name);
return -1;

View File

@ -43,4 +43,8 @@ uint64_t mp_get_property_event_mask(const char *name);
#define INTERNAL_EVENT_BASE 24
#define MP_EVENT_CACHE_UPDATE (INTERNAL_EVENT_BASE + 0)
bool mp_hook_test_completion(struct MPContext *mpctx, char *type);
void mp_hook_run(struct MPContext *mpctx, char *client, char *type);
void mp_hook_abort(struct MPContext *mpctx, char *type);
#endif /* MPLAYER_COMMAND_H */

View File

@ -179,6 +179,7 @@ typedef struct MPContext {
struct playlist *playlist;
struct playlist_entry *playing; // currently playing file
char *filename; // immutable copy of playing->filename (or NULL)
char *stream_open_filename;
struct mp_resolve_result *resolve_result;
enum stop_play_reason stop_play;
bool playback_initialized; // playloop can be run/is running

View File

@ -794,6 +794,36 @@ static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl)
}
}
static int process_open_hooks(struct MPContext *mpctx)
{
mp_hook_run(mpctx, NULL, "on_load");
while (!mp_hook_test_completion(mpctx, "on_load")) {
mp_idle(mpctx);
if (mpctx->stop_play) {
if (mpctx->stop_play == PT_QUIT)
return -1;
// Can't exit immediately, the script would interfere with the
// next file being loaded.
mp_hook_abort(mpctx, "on_load");
}
}
// quvi stuff
char *filename = mpctx->stream_open_filename;
mpctx->resolve_result = resolve_url(filename, mpctx->global);
if (mpctx->resolve_result) {
print_resolve_contents(mpctx->log, mpctx->resolve_result);
if (mpctx->resolve_result->playlist) {
transfer_playlist(mpctx, mpctx->resolve_result->playlist);
return 1;
}
mpctx->stream_open_filename = mpctx->resolve_result->url;
}
return 0;
}
static void print_timeline(struct MPContext *mpctx)
{
if (mpctx->timeline) {
@ -936,6 +966,7 @@ static void play_current_file(struct MPContext *mpctx)
mpctx->playing->reserved += 1;
mpctx->filename = talloc_strdup(tmp, mpctx->playing->filename);
mpctx->stream_open_filename = mpctx->filename;
mpctx->add_osd_seek_info &= OSD_SEEK_INFO_EDITION;
@ -972,21 +1003,16 @@ static void play_current_file(struct MPContext *mpctx)
assert(mpctx->d_sub[0] == NULL);
assert(mpctx->d_sub[1] == NULL);
char *stream_filename = mpctx->filename;
mpctx->resolve_result = resolve_url(stream_filename, mpctx->global);
if (mpctx->resolve_result) {
talloc_steal(tmp, mpctx->resolve_result);
print_resolve_contents(mpctx->log, mpctx->resolve_result);
if (mpctx->resolve_result->playlist) {
transfer_playlist(mpctx, mpctx->resolve_result->playlist);
goto terminate_playback;
}
stream_filename = mpctx->resolve_result->url;
}
int hooks_res = process_open_hooks(mpctx);
talloc_steal(tmp, mpctx->resolve_result);
if (hooks_res)
goto terminate_playback; // quit or preloaded playlist special-case
int stream_flags = STREAM_READ;
if (!opts->load_unsafe_playlists)
stream_flags |= mpctx->playing->stream_flags;
mpctx->stream = open_stream_async(mpctx, stream_filename, stream_flags);
mpctx->stream = open_stream_async(mpctx, mpctx->stream_open_filename,
stream_flags);
if (!mpctx->stream)
goto terminate_playback;
@ -1245,6 +1271,7 @@ terminate_playback:
playlist_entry_unref(mpctx->playing);
mpctx->playing = NULL;
mpctx->filename = NULL;
mpctx->stream_open_filename = NULL;
talloc_free(tmp);
}