summaryrefslogtreecommitdiffstats
path: root/player/lua.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-02-10 21:03:59 +0100
committerwm4 <wm4@nowhere>2014-02-10 21:03:59 +0100
commit206616b697672d973b24330af9b094ac851f7ca4 (patch)
tree5baea012aa584c11f573373c1c7cbeddced209f5 /player/lua.c
parent88ae914b1ef2b76362c527985bd459b0d8226d45 (diff)
downloadmpv-206616b697672d973b24330af9b094ac851f7ca4.tar.bz2
mpv-206616b697672d973b24330af9b094ac851f7ca4.tar.xz
lua: port to client API
This is partial only, and it still accesses some MPContext internals. Specifically, chapter and track lists are still read directly, and OSD access is special-cased too. The OSC seems to work fine, except using the fast-forward/backward buttons. These buttons behave differently, because the OSC code had certain assumptions how often its update code is called. The Lua interface changes slightly. Note that this has the odd property that Lua script and video start at the same time, asynchronously. If this becomes an issue, explicit synchronization could be added.
Diffstat (limited to 'player/lua.c')
-rw-r--r--player/lua.c337
1 files changed, 185 insertions, 152 deletions
diff --git a/player/lua.c b/player/lua.c
index 79bf61b713..68b8c40ab0 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -16,9 +16,12 @@
#include "options/path.h"
#include "bstr/bstr.h"
#include "osdep/timer.h"
+#include "osdep/threads.h"
#include "sub/osd.h"
#include "core.h"
#include "command.h"
+#include "client.h"
+#include "libmpv/client.h"
#include "lua.h"
// List of builtin modules and their contents as strings.
@@ -40,25 +43,12 @@ static const char *builtin_lua_scripts[][2] = {
struct script_ctx {
const char *name;
lua_State *state;
- struct mp_log_buffer *messages;
struct mp_log *log;
+ struct mpv_handle *client;
struct MPContext *mpctx;
+ int suspended;
};
-struct lua_ctx {
- struct script_ctx **scripts;
- int num_scripts;
-};
-
-static struct script_ctx *find_script(struct lua_ctx *lctx, const char *name)
-{
- for (int n = 0; n < lctx->num_scripts; n++) {
- if (strcmp(lctx->scripts[n]->name, name) == 0)
- return lctx->scripts[n];
- }
- return NULL;
-}
-
static struct script_ctx *get_ctx(lua_State *L)
{
lua_getfield(L, LUA_REGISTRYINDEX, "ctx");
@@ -103,10 +93,29 @@ static void report_error(lua_State *L)
lua_pop(L, 1);
}
+// Check client API error code:
+// if err >= 0, return 0.
+// if err < 0, raise the error as Lua error.
+static int check_error(lua_State *L, int err)
+{
+ if (err >= 0)
+ return 0;
+ luaL_error(L, "mpv API error: %s", mpv_error_string(err));
+ abort();
+}
+
+static int run_event_loop(lua_State *L)
+{
+ lua_getglobal(L, "mp_event_loop");
+ if (lua_isnil(L, -1))
+ luaL_error(L, "no event loop function\n");
+ lua_call(L, 0, 0);
+ return 0;
+}
+
static void add_functions(struct script_ctx *ctx);
-static char *script_name_from_filename(void *talloc_ctx, struct lua_ctx *lctx,
- const char *fname)
+static char *script_name_from_filename(void *talloc_ctx, const char *fname)
{
fname = mp_basename(fname);
if (fname[0] == '@')
@@ -124,10 +133,7 @@ static char *script_name_from_filename(void *talloc_ctx, struct lua_ctx *lctx,
!(c >= '0' && c <= '9'))
name[n] = '_';
}
- // Make unique (stupid but simple)
- while (find_script(lctx, name))
- name = talloc_strdup_append(name, "_");
- return name;
+ return talloc_asprintf(talloc_ctx, "lua/%s", name);
}
static int load_file(struct script_ctx *ctx, const char *fname)
@@ -171,16 +177,28 @@ static bool require(lua_State *L, const char *name)
return true;
}
-static void mp_lua_load_script(struct MPContext *mpctx, const char *fname)
+struct thread_arg {
+ struct MPContext *mpctx;
+ mpv_handle *client;
+ const char *fname;
+};
+
+static void *lua_thread(void *p)
{
- struct lua_ctx *lctx = mpctx->lua_ctx;
+ pthread_detach(pthread_self());
+
+ struct thread_arg *arg = p;
+ struct MPContext *mpctx = arg->mpctx;
+ const char *fname = arg->fname;
+ mpv_handle *client = arg->client;
+
struct script_ctx *ctx = talloc_ptrtype(NULL, ctx);
*ctx = (struct script_ctx) {
.mpctx = mpctx,
- .name = script_name_from_filename(ctx, lctx, fname),
+ .client = client,
+ .name = mpv_client_name(client),
+ .log = mp_client_get_log(client),
};
- char *log_name = talloc_asprintf(ctx, "lua/%s", ctx->name);
- ctx->log = mp_log_new(ctx, mpctx->log, log_name);
lua_State *L = ctx->state = luaL_newstate();
if (!L)
@@ -234,30 +252,43 @@ static void mp_lua_load_script(struct MPContext *mpctx, const char *fname)
goto error_out;
}
- MP_TARRAY_APPEND(lctx, lctx->scripts, lctx->num_scripts, ctx);
- return;
+ // Call the script's event loop runs until the script terminates and unloads.
+ if (mp_cpcall(L, run_event_loop, 0) != 0)
+ report_error(L);
error_out:
+ if (ctx->suspended)
+ mpv_resume(ctx->client);
if (ctx->state)
lua_close(ctx->state);
+ mpv_destroy(ctx->client);
talloc_free(ctx);
+ talloc_free(arg);
+ return NULL;
}
-static void kill_script(struct script_ctx *ctx)
+static void mp_lua_load_script(struct MPContext *mpctx, const char *fname)
{
- if (!ctx)
+ struct thread_arg *arg = talloc_ptrtype(NULL, arg);
+ char *name = script_name_from_filename(arg, fname);
+ *arg = (struct thread_arg){
+ .mpctx = mpctx,
+ .fname = talloc_strdup(arg, fname),
+ // Create the client before creating the thread; otherwise a race
+ // condition could happen, where MPContext is destroyed while the
+ // thread tries to create the client.
+ .client = mp_new_client(mpctx->clients, name),
+ };
+ if (!arg->client) {
+ talloc_free(arg);
return;
- struct lua_ctx *lctx = ctx->mpctx->lua_ctx;
- if (ctx->messages)
- mp_msg_log_buffer_destroy(ctx->messages);
- lua_close(ctx->state);
- for (int n = 0; n < lctx->num_scripts; n++) {
- if (lctx->scripts[n] == ctx) {
- MP_TARRAY_REMOVE_AT(lctx->scripts, lctx->num_scripts, n);
- break;
- }
}
- talloc_free(ctx);
+
+ pthread_t thread;
+ if (pthread_create(&thread, NULL, lua_thread, arg))
+ talloc_free(arg);
+
+ return;
}
static int check_loglevel(lua_State *L, int arg)
@@ -308,139 +339,130 @@ static int script_find_config_file(lua_State *L)
return 1;
}
-static int run_event(lua_State *L)
+static int script_suspend(lua_State *L)
{
- lua_getglobal(L, "mp_event"); // name arg mp_event
- if (lua_isnil(L, -1))
- return 0;
- lua_insert(L, -3); // mp_event name arg
- lua_call(L, 2, 0);
+ struct script_ctx *ctx = get_ctx(L);
+ if (!ctx->suspended)
+ mpv_suspend(ctx->client);
+ ctx->suspended++;
return 0;
}
-static void poll_messages(struct script_ctx *ctx)
+static int script_resume(lua_State *L)
{
- lua_State *L = ctx->state;
+ struct script_ctx *ctx = get_ctx(L);
+ static const char *modes[] = {"normal", "all", NULL};
+ if (luaL_checkoption(L, 1, "normal", modes) == 1) {
+ if (ctx->suspended)
+ mpv_resume(ctx->client);
+ ctx->suspended = 0;
+ } else {
+ if (ctx->suspended < 1)
+ luaL_error(L, "trying to resume, but core is not suspended");
+ ctx->suspended--;
+ if (!ctx->suspended)
+ mpv_resume(ctx->client);
+ }
+ return 0;
+}
- if (!ctx->messages)
- return;
+static int script_wait_event(lua_State *L)
+{
+ struct script_ctx *ctx = get_ctx(L);
- while (1) {
- struct mp_log_buffer_entry *msg = mp_msg_log_buffer_read(ctx->messages);
- if (!msg)
- break;
+ double timeout = luaL_optnumber(L, 1, 1e20);
+
+ // This will almost surely lead to a deadlock. (Polling is still ok.)
+ if (ctx->suspended && timeout > 0)
+ luaL_error(L, "attempting to wait while core is suspended");
+
+ mpv_event *event = mpv_wait_event(ctx->client, timeout);
- lua_pushstring(L, "message"); // msg
- lua_newtable(L); // msg t
- lua_pushstring(L, msg->prefix); // msg t s
- lua_setfield(L, -2, "prefix"); // msg t
- lua_pushstring(L, mp_log_levels[msg->level]); // msg t s
- lua_setfield(L, -2, "level"); // msg t
- lua_pushstring(L, msg->text); // msg t s
- lua_setfield(L, -2, "text"); // msg t
+ lua_newtable(L); // event
+ lua_pushstring(L, mpv_event_name(event->event_id)); // event name
+ lua_setfield(L, -2, "event"); // event
- if (mp_cpcall(L, run_event, 2) != 0)
- report_error(L);
+ if (event->error < 0) {
+ lua_pushstring(L, mpv_error_string(event->error)); // event err
+ lua_setfield(L, -2, "error"); // event
+ }
- talloc_free(msg);
+ switch (event->event_id) {
+ case MPV_EVENT_LOG_MESSAGE: {
+ mpv_event_log_message *msg = event->data;
+
+ lua_pushstring(L, msg->prefix); // event s
+ lua_setfield(L, -2, "prefix"); // event
+ lua_pushstring(L, msg->level); // event s
+ lua_setfield(L, -2, "level"); // event
+ lua_pushstring(L, msg->text); // event s
+ lua_setfield(L, -2, "text"); // event
+ break;
+ }
+ case MPV_EVENT_SCRIPT_INPUT_DISPATCH: {
+ mpv_event_script_input_dispatch *msg = event->data;
+
+ lua_pushinteger(L, msg->arg0); // event i
+ lua_setfield(L, -2, "arg0"); // event
+ lua_pushstring(L, msg->type); // event s
+ lua_setfield(L, -2, "type"); // event
+ break;
+ }
+ default: ;
}
+
+ return 1;
}
-void mp_lua_event(struct MPContext *mpctx, const char *name, const char *arg)
+static int script_request_event(lua_State *L)
{
- // There is no proper subscription mechanism yet, so all scripts get it.
- struct lua_ctx *lctx = mpctx->lua_ctx;
- for (int n = 0; n < lctx->num_scripts; n++) {
- struct script_ctx *ctx = lctx->scripts[n];
- lua_State *L = ctx->state;
- lua_pushstring(L, name);
- if (arg) {
- lua_pushstring(L, arg);
- } else {
- lua_pushnil(L);
+ struct script_ctx *ctx = get_ctx(L);
+ const char *event = luaL_checkstring(L, 1);
+ bool enable = lua_toboolean(L, 2);
+ // brute force event name -> id; stops working for events > assumed max
+ int event_id = -1;
+ for (int n = 0; n < 256; n++) {
+ const char *name = mpv_event_name(n);
+ if (name && strcmp(name, event) == 0) {
+ event_id = n;
+ break;
}
- if (mp_cpcall(L, run_event, 2) != 0)
- report_error(L);
- poll_messages(ctx);
}
+ lua_pushboolean(L, mpv_request_event(ctx->client, event_id, enable) >= 0);
+ return 1;
}
static int script_enable_messages(lua_State *L)
{
struct script_ctx *ctx = get_ctx(L);
- if (ctx->messages)
- luaL_error(L, "messages already enabled");
-
- int size = luaL_checkinteger(L, 1);
- int level = check_loglevel(L, 2);
- if (size < 2 || size > 100000)
- luaL_error(L, "size argument out of range");
- ctx->messages = mp_msg_log_buffer_new(ctx->mpctx->global, size, level);
- return 0;
-}
-
-static int run_script_dispatch(lua_State *L)
-{
- int id = lua_tointeger(L, 1);
- const char *event = lua_tostring(L, 2);
- lua_getglobal(L, "mp_script_dispatch");
- if (lua_isnil(L, -1))
- return 0;
- lua_pushinteger(L, id);
- lua_pushstring(L, event);
- lua_call(L, 2, 0);
- return 0;
-}
-
-void mp_lua_script_dispatch(struct MPContext *mpctx, char *script_name,
- int id, char *event)
-{
- struct script_ctx *ctx = find_script(mpctx->lua_ctx, script_name);
- if (!ctx) {
- MP_VERBOSE(mpctx, "Can't find script '%s' when handling input.\n",
- script_name);
- return;
- }
- lua_State *L = ctx->state;
- lua_pushinteger(L, id);
- lua_pushstring(L, event);
- if (mp_cpcall(L, run_script_dispatch, 2) != 0)
- report_error(L);
+ check_loglevel(L, 1);
+ const char *level = luaL_checkstring(L, 1);
+ return check_error(L, mpv_request_log_messages(ctx->client, level));
}
static int script_send_command(lua_State *L)
{
- struct MPContext *mpctx = get_mpctx(L);
+ struct script_ctx *ctx = get_ctx(L);
const char *s = luaL_checkstring(L, 1);
- mp_cmd_t *cmd = mp_input_parse_cmd(mpctx->input, bstr0((char*)s), "<lua>");
- if (!cmd)
- luaL_error(L, "error parsing command");
- mp_input_queue_cmd(mpctx->input, cmd);
-
- return 0;
+ return check_error(L, mpv_command_string(ctx->client, s));
}
static int script_send_commandv(lua_State *L)
{
struct script_ctx *ctx = get_ctx(L);
int num = lua_gettop(L);
- bstr args[50];
- if (num > MP_ARRAY_SIZE(args))
+ const char *args[50];
+ if (num + 1 > MP_ARRAY_SIZE(args))
luaL_error(L, "too many arguments");
for (int n = 1; n <= num; n++) {
- size_t len;
- const char *s = lua_tolstring(L, n, &len);
+ const char *s = lua_tostring(L, n);
if (!s)
luaL_error(L, "argument %d is not a string", n);
- args[n - 1] = (bstr){(char *)s, len};
+ args[n - 1] = s;
}
- mp_cmd_t *cmd = mp_input_parse_cmd_bstrv(ctx->log, 0, num, args, "<lua>");
- if (!cmd)
- luaL_error(L, "error parsing command");
- mp_input_queue_cmd(ctx->mpctx->input, cmd);
-
- return 0;
+ args[num] = NULL;
+ return check_error(L, mpv_command(ctx->client, args));
}
static int script_property_list(lua_State *L)
@@ -457,18 +479,24 @@ static int script_property_list(lua_State *L)
static int script_property_string(lua_State *L)
{
- struct MPContext *mpctx = get_mpctx(L);
+ struct script_ctx *ctx = get_ctx(L);
const char *name = luaL_checkstring(L, 1);
int type = lua_tointeger(L, lua_upvalueindex(1))
- ? M_PROPERTY_PRINT : M_PROPERTY_GET_STRING;
+ ? MPV_FORMAT_OSD_STRING : MPV_FORMAT_STRING;
char *result = NULL;
- if (mp_property_do(name, type, &result, mpctx) >= 0 && result) {
+ int err = mpv_get_property(ctx->client, name, type, &result);
+ if (err >= 0) {
lua_pushstring(L, result);
talloc_free(result);
return 1;
}
- if (type == M_PROPERTY_PRINT) {
+ // out of convenience, property access errors are not hard errors
+ if (err != MPV_ERROR_PROPERTY_NOT_FOUND &&
+ err != MPV_ERROR_PROPERTY_ERROR &&
+ err != MPV_ERROR_PROPERTY_UNAVAILABLE)
+ check_error(L, err);
+ if (type == MPV_FORMAT_OSD_STRING) {
lua_pushstring(L, "");
return 1;
}
@@ -528,6 +556,7 @@ static int script_get_timer(lua_State *L)
static int script_get_chapter_list(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
+ mp_dispatch_lock(mpctx->dispatch);
lua_newtable(L); // list
int num = get_chapter_count(mpctx);
for (int n = 0; n < num; n++) {
@@ -543,6 +572,7 @@ static int script_get_chapter_list(lua_State *L)
lua_settable(L, -3); // list
talloc_free(name);
}
+ mp_dispatch_unlock(mpctx->dispatch);
return 1;
}
@@ -559,6 +589,7 @@ static const char *stream_type(enum stream_type t)
static int script_get_track_list(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
+ mp_dispatch_lock(mpctx->dispatch);
lua_newtable(L); // list
for (int n = 0; n < mpctx->num_tracks; n++) {
struct track *track = mpctx->tracks[n];
@@ -593,6 +624,7 @@ static int script_get_track_list(lua_State *L)
lua_insert(L, -2); // list n1 track
lua_settable(L, -3); // list
}
+ mp_dispatch_unlock(mpctx->dispatch);
return 1;
}
@@ -668,16 +700,24 @@ static int script_format_time(lua_State *L)
static int script_getopt(lua_State *L)
{
struct MPContext *mpctx = get_mpctx(L);
+
+ mp_dispatch_lock(mpctx->dispatch);
+
char **opts = mpctx->opts->lua_opts;
const char *name = luaL_checkstring(L, 1);
+ int r = 0;
for (int n = 0; opts && opts[n] && opts[n + 1]; n++) {
if (strcmp(opts[n], name) == 0) {
lua_pushstring(L, opts[n + 1]);
- return 1;
+ r = 1;
+ break;
}
}
- return 0;
+
+ mp_dispatch_unlock(mpctx->dispatch);
+
+ return r;
}
struct fn_entry {
@@ -689,6 +729,10 @@ struct fn_entry {
static struct fn_entry fn_list[] = {
FN_ENTRY(log),
+ FN_ENTRY(suspend),
+ FN_ENTRY(resume),
+ FN_ENTRY(wait_event),
+ FN_ENTRY(request_event),
FN_ENTRY(find_config_file),
FN_ENTRY(send_command),
FN_ENTRY(send_commandv),
@@ -730,7 +774,6 @@ static void add_functions(struct script_ctx *ctx)
void mp_lua_init(struct MPContext *mpctx)
{
- mpctx->lua_ctx = talloc_zero(NULL, struct lua_ctx);
// Load scripts from options
if (mpctx->opts->lua_load_osc)
mp_lua_load_script(mpctx, "@osc");
@@ -740,13 +783,3 @@ void mp_lua_init(struct MPContext *mpctx)
mp_lua_load_script(mpctx, files[n]);
}
}
-
-void mp_lua_uninit(struct MPContext *mpctx)
-{
- if (mpctx->lua_ctx) {
- while (mpctx->lua_ctx->num_scripts)
- kill_script(mpctx->lua_ctx->scripts[0]);
- talloc_free(mpctx->lua_ctx);
- mpctx->lua_ctx = NULL;
- }
-}