summaryrefslogtreecommitdiffstats
path: root/player
diff options
context:
space:
mode:
Diffstat (limited to 'player')
-rw-r--r--player/audio.c84
-rw-r--r--player/client.c148
-rw-r--r--player/client.h1
-rw-r--r--player/command.c834
-rw-r--r--player/command.h8
-rw-r--r--player/configfiles.c79
-rw-r--r--player/core.h18
-rw-r--r--player/loadfile.c15
-rw-r--r--player/lua.c45
-rw-r--r--player/lua/defaults.lua33
-rw-r--r--player/lua/osc.lua276
-rw-r--r--player/lua/ytdl_hook.lua91
-rw-r--r--player/main.c210
-rw-r--r--player/misc.c28
-rw-r--r--player/osd.c48
-rw-r--r--player/playloop.c121
-rw-r--r--player/scripting.c46
-rw-r--r--player/sub.c3
-rw-r--r--player/video.c61
19 files changed, 1412 insertions, 737 deletions
diff --git a/player/audio.c b/player/audio.c
index 89f75d9095..3f173f140d 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -334,7 +334,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
if (!mp_audio_config_valid(&in_format)) {
// We don't know the audio format yet - so configure it later as we're
// resyncing. fill_audio_buffers() will call this function again.
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
return;
}
@@ -382,6 +382,9 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
if (opts->audio_stream_silence)
ao_flags |= AO_INIT_STREAM_SILENCE;
+ if (opts->audio_exclusive)
+ ao_flags |= AO_INIT_EXCLUSIVE;
+
if (af_fmt_is_pcm(afs->output.format)) {
if (!opts->audio_output_channels.set ||
opts->audio_output_channels.auto_safe)
@@ -394,8 +397,8 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
mp_audio_set_channels(&afs->output, &afs->output.channels);
- mpctx->ao = ao_init_best(mpctx->global, ao_flags, mpctx->input,
- mpctx->encode_lavc_ctx, afs->output.rate,
+ mpctx->ao = ao_init_best(mpctx->global, ao_flags, mp_wakeup_core_cb,
+ mpctx, mpctx->encode_lavc_ctx, afs->output.rate,
afs->output.format, afs->output.channels);
ao_c->ao = mpctx->ao;
@@ -424,7 +427,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
goto init_error;
reset_audio_state(mpctx);
ao_c->input_format = (struct mp_audio){0};
- mpctx->sleeptime = 0; // reinit with new format next time
+ mp_wakeup_core(mpctx); // reinit with new format next time
return;
}
@@ -547,7 +550,7 @@ void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src)
mp_audio_buffer_reinit(ao_c->ao_buffer, &fmt);
}
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
return;
init_error:
@@ -858,32 +861,47 @@ static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
return res;
}
+void reload_audio_output(struct MPContext *mpctx)
+{
+ if (!mpctx->ao)
+ return;
+
+ ao_reset(mpctx->ao);
+ uninit_audio_out(mpctx);
+ reinit_audio_filters(mpctx); // mostly to issue refresh seek
+
+ // Whether we can use spdif might have changed. If we failed to use spdif
+ // in the previous initialization, try it with spdif again (we'll fallback
+ // to PCM again if necessary).
+ struct ao_chain *ao_c = mpctx->ao_chain;
+ if (ao_c) {
+ struct dec_audio *d_audio = ao_c->audio_src;
+ if (d_audio && ao_c->spdif_failed) {
+ ao_c->spdif_passthrough = true;
+ ao_c->spdif_failed = false;
+ d_audio->try_spdif = true;
+ ao_c->af->initialized = 0;
+ if (!audio_init_best_codec(d_audio)) {
+ MP_ERR(mpctx, "Error reinitializing audio.\n");
+ error_on_track(mpctx, ao_c->track);
+ }
+ }
+ }
+
+ mp_wakeup_core(mpctx);
+}
+
void fill_audio_out_buffers(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
- struct ao_chain *ao_c = mpctx->ao_chain;
bool was_eof = mpctx->audio_status == STATUS_EOF;
dump_audio_stats(mpctx);
- if (mpctx->ao && ao_query_and_reset_events(mpctx->ao, AO_EVENT_RELOAD)) {
- ao_reset(mpctx->ao);
- uninit_audio_out(mpctx);
- if (ao_c) {
- struct dec_audio *d_audio = ao_c->audio_src;
- if (d_audio && ao_c->spdif_failed) {
- ao_c->spdif_failed = false;
- d_audio->try_spdif = true;
- if (!audio_init_best_codec(d_audio)) {
- MP_ERR(mpctx, "Error reinitializing audio.\n");
- error_on_track(mpctx, ao_c->track);
- return;
- }
- }
- mpctx->audio_status = STATUS_SYNCING;
- }
- }
+ if (mpctx->ao && ao_query_and_reset_events(mpctx->ao, AO_EVENT_RELOAD))
+ reload_audio_output(mpctx);
+ struct ao_chain *ao_c = mpctx->ao_chain;
if (!ao_c)
return;
@@ -898,20 +916,20 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
return;
}
reinit_audio_filters_and_output(mpctx);
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
return; // try again next iteration
}
if (ao_c->ao_resume_time > mp_time_sec()) {
double remaining = ao_c->ao_resume_time - mp_time_sec();
- mpctx->sleeptime = MPMIN(mpctx->sleeptime, remaining);
+ mp_set_timeout(mpctx, remaining);
return;
}
if (mpctx->vo_chain && ao_c->pts_reset) {
MP_VERBOSE(mpctx, "Reset playback due to audio timestamp reset.\n");
reset_playback_state(mpctx);
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
return;
}
@@ -962,7 +980,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (status == AD_WAIT)
return;
if (status == AD_NO_PROGRESS) {
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
return;
}
if (status == AD_NEW_FMT) {
@@ -973,18 +991,20 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (mpctx->opts->gapless_audio < 1)
uninit_audio_out(mpctx);
reinit_audio_filters_and_output(mpctx);
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
return; // retry on next iteration
}
if (status == AD_ERR)
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
working = true;
}
// If EOF was reached before, but now something can be decoded, try to
// restart audio properly. This helps with video files where audio starts
// later. Retrying is needed to get the correct sync PTS.
- if (mpctx->audio_status >= STATUS_DRAINING && status == AD_OK) {
+ if (mpctx->audio_status >= STATUS_DRAINING &&
+ mp_audio_buffer_samples(ao_c->ao_buffer) > 0)
+ {
mpctx->audio_status = STATUS_SYNCING;
return; // retry on next iteration
}
@@ -1028,7 +1048,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (status != AD_OK && !mp_audio_buffer_samples(ao_c->ao_buffer))
mpctx->audio_status = STATUS_EOF;
if (working || end_sync)
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
return; // continue on next iteration
}
@@ -1086,7 +1106,7 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
if (ao_eof_reached(mpctx->ao) || opts->gapless_audio) {
mpctx->audio_status = STATUS_EOF;
if (!was_eof)
- mpctx->sleeptime = 0;
+ mp_wakeup_core(mpctx);
}
}
}
diff --git a/player/client.c b/player/client.c
index b34247d730..774a4e05c0 100644
--- a/player/client.c
+++ b/player/client.c
@@ -66,7 +66,8 @@ struct mp_client_api {
struct mpv_handle **clients;
int num_clients;
- uint64_t event_masks; // combined events of all clients, or 0 if unknown
+ uint64_t event_masks; // combined events of all clients, or 0 if unknown
+ bool shutting_down; // do not allow new clients
struct mp_custom_protocol *custom_protocols;
int num_custom_protocols;
@@ -206,8 +207,17 @@ bool mp_client_exists(struct MPContext *mpctx, const char *client_name)
return r;
}
+void mp_client_enter_shutdown(struct MPContext *mpctx)
+{
+ pthread_mutex_lock(&mpctx->clients->lock);
+ mpctx->clients->shutting_down = true;
+ pthread_mutex_unlock(&mpctx->clients->lock);
+}
+
struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name)
{
+ pthread_mutex_lock(&clients->lock);
+
char nname[MAX_CLIENT_NAME];
for (int n = 1; n < 1000; n++) {
if (!name)
@@ -222,10 +232,10 @@ struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name
nname[0] = '\0';
}
- if (!nname[0])
+ if (!nname[0] || clients->shutting_down) {
+ pthread_mutex_unlock(&clients->lock);
return NULL;
-
- pthread_mutex_lock(&clients->lock);
+ }
int num_events = 1000;
@@ -321,6 +331,8 @@ void mpv_suspend(mpv_handle *ctx)
{
bool do_suspend = false;
+ MP_WARN(ctx, "warning: mpv_suspend() is deprecated.\n");
+
pthread_mutex_lock(&ctx->lock);
if (ctx->suspend_count == INT_MAX) {
MP_ERR(ctx, "suspend counter overflow");
@@ -330,8 +342,11 @@ void mpv_suspend(mpv_handle *ctx)
}
pthread_mutex_unlock(&ctx->lock);
- if (do_suspend)
- mp_dispatch_suspend(ctx->mpctx->dispatch);
+ if (do_suspend) {
+ mp_dispatch_lock(ctx->mpctx->dispatch);
+ ctx->mpctx->suspend_count++;
+ mp_dispatch_unlock(ctx->mpctx->dispatch);
+ }
}
void mpv_resume(mpv_handle *ctx)
@@ -347,8 +362,12 @@ void mpv_resume(mpv_handle *ctx)
}
pthread_mutex_unlock(&ctx->lock);
- if (do_resume)
- mp_dispatch_resume(ctx->mpctx->dispatch);
+ if (do_resume) {
+ mp_dispatch_lock(ctx->mpctx->dispatch);
+ ctx->mpctx->suspend_count--;
+ mp_dispatch_unlock(ctx->mpctx->dispatch);
+ mp_dispatch_interrupt(ctx->mpctx->dispatch);
+ }
}
void mp_resume_all(mpv_handle *ctx)
@@ -358,20 +377,21 @@ void mp_resume_all(mpv_handle *ctx)
ctx->suspend_count = 0;
pthread_mutex_unlock(&ctx->lock);
- if (do_resume)
- mp_dispatch_resume(ctx->mpctx->dispatch);
+ if (do_resume) {
+ mp_dispatch_lock(ctx->mpctx->dispatch);
+ ctx->mpctx->suspend_count--;
+ mp_dispatch_unlock(ctx->mpctx->dispatch);
+ }
}
static void lock_core(mpv_handle *ctx)
{
- if (ctx->mpctx->initialized)
- mp_dispatch_lock(ctx->mpctx->dispatch);
+ mp_dispatch_lock(ctx->mpctx->dispatch);
}
static void unlock_core(mpv_handle *ctx)
{
- if (ctx->mpctx->initialized)
- mp_dispatch_unlock(ctx->mpctx->dispatch);
+ mp_dispatch_unlock(ctx->mpctx->dispatch);
}
void mpv_wait_async_requests(mpv_handle *ctx)
@@ -406,6 +426,8 @@ void mpv_detach_destroy(mpv_handle *ctx)
ctx->num_events--;
}
mp_msg_log_buffer_destroy(ctx->messages);
+ osd_set_external(ctx->mpctx->osd, ctx, 0, 0, NULL);
+ mp_input_remove_sections_by_owner(ctx->mpctx->input, ctx->name);
pthread_cond_destroy(&ctx->wakeup);
pthread_mutex_destroy(&ctx->wakeup_lock);
pthread_mutex_destroy(&ctx->lock);
@@ -417,8 +439,7 @@ void mpv_detach_destroy(mpv_handle *ctx)
ctx = NULL;
// shutdown_clients() sleeps to avoid wasting CPU.
// mp_hook_test_completion() also relies on this a bit.
- if (clients->mpctx->input)
- mp_input_wakeup(clients->mpctx->input);
+ mp_wakeup_core(clients->mpctx);
break;
}
}
@@ -438,7 +459,7 @@ void mpv_terminate_destroy(mpv_handle *ctx)
mpv_command(ctx, (const char*[]){"quit", NULL});
- if (!ctx->owner || !ctx->mpctx->initialized) {
+ if (!ctx->owner) {
mpv_detach_destroy(ctx);
return;
}
@@ -458,6 +479,26 @@ void mpv_terminate_destroy(mpv_handle *ctx)
pthread_join(playthread, NULL);
}
+static void *playback_thread(void *p)
+{
+ struct MPContext *mpctx = p;
+ mpctx->autodetach = true;
+
+ mpthread_set_name("mpv core");
+
+ while (!mpctx->initialized && mpctx->stop_play != PT_QUIT)
+ mp_idle(mpctx);
+
+ if (mpctx->initialized)
+ mp_play_files(mpctx);
+
+ // This actually waits until all clients are gone before actually
+ // destroying mpctx.
+ mp_destroy(mpctx);
+
+ return NULL;
+}
+
// We mostly care about LC_NUMERIC, and how "." vs. "," is treated,
// Other locale stuff might break too, but probably isn't too bad.
static bool check_locale(void)
@@ -484,6 +525,13 @@ mpv_handle *mpv_create(void)
} else {
mp_destroy(mpctx);
}
+
+ pthread_t thread;
+ if (pthread_create(&thread, NULL, playback_thread, ctx->mpctx) != 0) {
+ mpv_terminate_destroy(ctx);
+ return NULL;
+ }
+
return ctx;
}
@@ -491,40 +539,25 @@ mpv_handle *mpv_create_client(mpv_handle *ctx, const char *name)
{
if (!ctx)
return mpv_create();
- if (!ctx->mpctx->initialized)
- return NULL;
mpv_handle *new = mp_new_client(ctx->mpctx->clients, name);
if (new)
mpv_wait_event(new, 0); // set fuzzy_initialized
return new;
}
-static void *playback_thread(void *p)
+static void doinit(void *ctx)
{
- struct MPContext *mpctx = p;
- mpctx->autodetach = true;
-
- mpthread_set_name("playback core");
-
- mp_play_files(mpctx);
+ void **args = ctx;
- // This actually waits until all clients are gone before actually
- // destroying mpctx.
- mp_destroy(mpctx);
-
- return NULL;
+ *(int *)args[1] = mp_initialize(args[0], NULL);
}
int mpv_initialize(mpv_handle *ctx)
{
- if (mp_initialize(ctx->mpctx, NULL) < 0)
- return MPV_ERROR_INVALID_PARAMETER;
-
- pthread_t thread;
- if (pthread_create(&thread, NULL, playback_thread, ctx->mpctx) != 0)
- return MPV_ERROR_NOMEM;
-
- return 0;
+ int res = 0;
+ void *args[2] = {ctx->mpctx, &res};
+ mp_dispatch_run(ctx->mpctx->dispatch, doinit, args);
+ return res < 0 ? MPV_ERROR_INVALID_PARAMETER : 0;
}
// set ev->data to a new copy of the original data
@@ -740,8 +773,8 @@ mpv_event *mpv_wait_event(mpv_handle *ctx, double timeout)
pthread_mutex_lock(&ctx->lock);
- if (!ctx->fuzzy_initialized && ctx->clients->mpctx->input)
- mp_input_wakeup(ctx->clients->mpctx->input);
+ if (!ctx->fuzzy_initialized)
+ mp_wakeup_core(ctx->clients->mpctx);
ctx->fuzzy_initialized = true;
if (timeout < 0)
@@ -1099,8 +1132,17 @@ static void setproperty_fn(void *arg)
int mpv_set_property(mpv_handle *ctx, const char *name, mpv_format format,
void *data)
{
- if (!ctx->mpctx->initialized)
- return MPV_ERROR_UNINITIALIZED;
+ if (!ctx->mpctx->initialized) {
+ int r = mpv_set_option(ctx, name, format, data);
+ if (r == MPV_ERROR_OPTION_NOT_FOUND &&
+ mp_get_property_id(ctx->mpctx, name) >= 0)
+ return MPV_ERROR_PROPERTY_UNAVAILABLE;
+ switch (r) {
+ case MPV_ERROR_OPTION_FORMAT: return MPV_ERROR_PROPERTY_FORMAT;
+ case MPV_ERROR_OPTION_NOT_FOUND: return MPV_ERROR_PROPERTY_NOT_FOUND;
+ default: return MPV_ERROR_PROPERTY_ERROR;
+ }
+ }
if (!get_mp_type(format))
return MPV_ERROR_PROPERTY_FORMAT;
@@ -1319,7 +1361,7 @@ int mpv_observe_property(mpv_handle *ctx, uint64_t userdata,
*prop = (struct observe_property){
.client = ctx,
.name = talloc_strdup(prop, name),
- .id = mp_get_property_id(name),
+ .id = mp_get_property_id(ctx->mpctx, name),
.event_mask = mp_get_property_event_mask(name),
.reply_id = userdata,
.format = format,
@@ -1366,18 +1408,16 @@ int mpv_unobserve_property(mpv_handle *ctx, uint64_t userdata)
static void mark_property_changed(struct mpv_handle *client, int index)
{
struct observe_property *prop = client->properties[index];
- if (!prop->changed && !prop->need_new_value) {
- prop->changed = true;
- prop->need_new_value = prop->format != 0;
- client->lowest_changed = MPMIN(client->lowest_changed, index);
- }
+ prop->changed = true;
+ prop->need_new_value = prop->format != 0;
+ client->lowest_changed = MPMIN(client->lowest_changed, index);
}
// Broadcast that a property has changed.
void mp_client_property_change(struct MPContext *mpctx, const char *name)
{
struct mp_client_api *clients = mpctx->clients;
- int id = mp_get_property_id(name);
+ int id = mp_get_property_id(mpctx, name);
pthread_mutex_lock(&clients->lock);
@@ -1592,7 +1632,7 @@ static const char *const err_table[] = {
[-MPV_ERROR_COMMAND] = "error running command",
[-MPV_ERROR_LOADING_FAILED] = "loading failed",
[-MPV_ERROR_AO_INIT_FAILED] = "audio output initialization failed",
- [-MPV_ERROR_VO_INIT_FAILED] = "audio output initialization failed",
+ [-MPV_ERROR_VO_INIT_FAILED] = "video output initialization failed",
[-MPV_ERROR_NOTHING_TO_PLAY] = "no audio or video data played",
[-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format",
[-MPV_ERROR_UNSUPPORTED] = "not supported",
@@ -1661,8 +1701,12 @@ void kill_video(struct mp_client_api *client_api)
{
struct MPContext *mpctx = client_api->mpctx;
mp_dispatch_lock(mpctx->dispatch);
- mp_switch_track(mpctx, STREAM_VIDEO, NULL, 0);
+ struct track *track = mpctx->vo_chain ? mpctx->vo_chain->track : NULL;
uninit_video_out(mpctx);
+ if (track) {
+ mpctx->error_playing = MPV_ERROR_VO_INIT_FAILED;
+ error_on_track(mpctx, track);
+ }
mp_dispatch_unlock(mpctx->dispatch);
}
diff --git a/player/client.h b/player/client.h
index e8866225a7..e39d0e676a 100644
--- a/player/client.h
+++ b/player/client.h
@@ -16,6 +16,7 @@ struct mp_log;
#define MAX_CLIENT_NAME 64
void mp_clients_init(struct MPContext *mpctx);
+void mp_client_enter_shutdown(struct MPContext *mpctx);
void mp_clients_destroy(struct MPContext *mpctx);
int mp_clients_num(struct MPContext *mpctx);
bool mp_clients_all_initialized(struct MPContext *mpctx);
diff --git a/player/command.c b/player/command.c
index f4c10d48b9..1fa4695a91 100644
--- a/player/command.c
+++ b/player/command.c
@@ -68,7 +68,14 @@
#include "core.h"
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
struct command_ctx {
+ // All properties, terminated with a {0} item.
+ struct m_property *properties;
+
bool is_idle;
double last_seek_time;
@@ -94,6 +101,9 @@ struct command_ctx {
int64_t hook_seq; // for hook_handler.seq
struct ao_hotplug *hotplug;
+
+ char *cur_ipc;
+ char *cur_ipc_input;
};
struct overlay {
@@ -123,6 +133,9 @@ static int edit_filters(struct MPContext *mpctx, struct mp_log *log,
static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
struct m_obj_settings *new_chain);
+static int mp_property_do_silent(const char *name, int action, void *val,
+ struct MPContext *ctx);
+
static void hook_remove(struct MPContext *mpctx, int index)
{
struct command_ctx *cmd = mpctx->command_ctx;
@@ -174,6 +187,7 @@ void mp_hook_run(struct MPContext *mpctx, char *client, char *type)
if (h->active && strcmp(h->type, type) == 0) {
h->active = false;
found_current = true;
+ mp_wakeup_core(mpctx);
}
} else if (strcmp(h->type, type) == 0) {
index = n;
@@ -187,7 +201,7 @@ void mp_hook_run(struct MPContext *mpctx, char *client, char *type)
next->active = true;
if (!send_hook_msg(mpctx, next, "hook_run")) {
hook_remove(mpctx, index);
- mp_input_wakeup(mpctx->input); // repeat next iteration to finish
+ mp_wakeup_core(mpctx); // repeat next iteration to finish
}
}
@@ -250,12 +264,83 @@ static char *format_delay(double time)
return talloc_asprintf(NULL, "%d ms", (int)lrint(time * 1000));
}
+// Option-property bridge. This is used so that setting options via various
+// mechanisms (including command line parsing, config files, per-file options)
+// updates state associated with them. For that, they have to go through the
+// property layer. (Ideally, this would be the other way around, and there
+// would be per-option change handlers instead.)
+// Note that the property-option bridge sidesteps this, as we'd get infinite
+// recursion.
+int mp_on_set_option(void *ctx, struct m_config_option *co, void *data, int flags)
+{
+ struct MPContext *mpctx = ctx;
+
+ // These options are too inconsistent as they could be pulled through the
+ // property layer. Ideally we'd remove these inconsistencies in the future,
+ // though the actual problem is compatibility to user-expected behavior.
+ // What matters is whether _write_ access is different - property read
+ // access is not used here.
+ // We're also fine with cases where the property restricts the writable
+ // value range if playback is active, but not otherwise.
+ // OK, restrict during playback: vid, aid, sid, deinterlace, video-aspect,
+ // vf*, af*, chapter
+ // OK, is handled separately: playlist
+ // OK, does not conflict on low level: audio-file, sub-file, external-file
+ // OK, different value ranges, but happens to work for now: volume, edition
+ // All the other properties are deprecated in their current form.
+ static const char *const no_property[] = {
+ "demuxer", "idle", "length", "audio-samplerate", "audio-channels",
+ "audio-format", "fps", "cache", "playlist-pos", "chapter",
+ NULL
+ };
+
+ // Normalize "vf*" to "vf"
+ const char *name = co->name;
+ bstr bname = bstr0(name);
+ char tmp[50];
+ if (bstr_eatend0(&bname, "*")) {
+ snprintf(tmp, sizeof(name), "%.*s", BSTR_P(bname));
+ name = tmp;
+ }
+
+ for (int n = 0; no_property[n]; n++) {
+ if (strcmp(co->name, no_property[n]) == 0)
+ goto direct_option;
+ }
+
+ struct m_option type = {0};
+
+ int r = mp_property_do_silent(name, M_PROPERTY_GET_TYPE, &type, mpctx);
+ if (r == M_PROPERTY_UNKNOWN)
+ goto direct_option; // not mapped as property
+ if (r != M_PROPERTY_OK)
+ return M_OPT_INVALID; // shouldn't happen
+
+ assert(type.type == co->opt->type);
+ assert(type.max == co->opt->max);
+ assert(type.min == co->opt->min);
+
+ r = mp_property_do_silent(name, M_PROPERTY_SET, data, mpctx);
+ if (r != M_PROPERTY_OK)
+ return M_OPT_INVALID;
+
+ // The flags can't be passed through the property layer correctly.
+ m_config_mark_co_flags(co, flags);
+
+ return 0;
+
+direct_option:
+ mp_notify_property(mpctx, name);
+ return m_config_set_option_raw_direct(mpctx->mconfig, co, data, flags);
+}
+
// Property-option bridge. (Maps the property to the option with the same name.)
static int mp_property_generic_option(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
const char *optname = prop->name;
+ int flags = M_SETOPT_RUNTIME;
struct m_config_option *opt = m_config_get_co(mpctx->mconfig,
bstr0(optname));
@@ -272,12 +357,24 @@ static int mp_property_generic_option(void *ctx, struct m_property *prop,
m_option_copy(opt->opt, arg, valptr);
return M_PROPERTY_OK;
case M_PROPERTY_SET:
- m_option_copy(opt->opt, valptr, arg);
+ if (m_config_set_option_raw_direct(mpctx->mconfig, opt, arg, flags) < 0)
+ return M_PROPERTY_ERROR;
return M_PROPERTY_OK;
}
return M_PROPERTY_NOT_IMPLEMENTED;
}
+// Dumb special-case: the option name ends in a "*".
+static int mp_property_generic_option_star(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ struct m_property prop2 = *prop;
+ char name[80];
+ snprintf(name, sizeof(name), "%s*", prop->name);
+ prop2.name = name;
+ return mp_property_generic_option(ctx, &prop2, action, arg);
+}
+
/// Playback speed (RW)
static int mp_property_playback_speed(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -288,6 +385,7 @@ static int mp_property_playback_speed(void *ctx, struct m_property *prop,
case M_PROPERTY_SET: {
mpctx->opts->playback_speed = *(double *)arg;
update_playback_speed(mpctx);
+ mp_wakeup_core(mpctx);
return M_PROPERTY_OK;
}
case M_PROPERTY_PRINT:
@@ -449,10 +547,7 @@ static int mp_property_stream_capture(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->demuxer)
- return M_PROPERTY_UNAVAILABLE;
-
- if (action == M_PROPERTY_SET) {
+ if (mpctx->demuxer && action == M_PROPERTY_SET) {
struct change_stream_capture_args args = {*(char **)arg, mpctx->demuxer};
demux_run_on_thread(mpctx->demuxer, do_change_stream_capture, &args);
// fall through to mp_property_generic_option
@@ -576,7 +671,7 @@ static int mp_property_drop_frame_cnt(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- if (!mpctx->vo_chain)
+ if (!mpctx->vo_chain)
return M_PROPERTY_UNAVAILABLE;
return m_property_int_ro(action, arg, mpctx->vo_chain->video_src->dropped_frames);
@@ -696,6 +791,18 @@ static int mp_property_time_pos(void *ctx, struct m_property *prop,
return property_time(action, arg, get_current_time(mpctx));
}
+/// Current audio pts in seconds (R)
+static int mp_property_audio_pts(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ if (!mpctx->playback_initialized || mpctx->audio_status < STATUS_PLAYING ||
+ mpctx->audio_status >= STATUS_EOF)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return property_time(action, arg, playing_audio_pts(mpctx));
+}
+
static bool time_remaining(MPContext *mpctx, double *remaining)
{
double len = get_time_length(mpctx);
@@ -783,6 +890,9 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
+ if (!mpctx->playback_initialized)
+ return M_PROPERTY_UNAVAILABLE;
+
int chapter = get_current_chapter(mpctx);
int num = get_chapter_count(mpctx);
if (chapter < -1)
@@ -820,7 +930,9 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
if (current_chapter_start != MP_NOPTS_VALUE &&
get_current_time(mpctx) - current_chapter_start >
mpctx->opts->chapter_seek_threshold)
+ {
step_all++;
+ }
}
} else // Absolute set
step_all = *(int *)arg - chapter;
@@ -836,6 +948,7 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
return M_PROPERTY_UNAVAILABLE;
if (!mpctx->stop_play)
mpctx->stop_play = PT_NEXT_ENTRY;
+ mp_wakeup_core(mpctx);
}
} else {
double pts = chapter_start_time(mpctx, chapter);
@@ -865,12 +978,75 @@ static int get_chapter_entry(int item, int action, void *arg, void *ctx)
return r;
}
+static int parse_node_chapters(struct MPContext *mpctx,
+ struct mpv_node *given_chapters)
+{
+ if (!mpctx->demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+
+ if (given_chapters->format != MPV_FORMAT_NODE_ARRAY)
+ return M_PROPERTY_ERROR;
+
+ double len = get_time_length(mpctx);
+
+ talloc_free(mpctx->chapters);
+ mpctx->num_chapters = 0;
+ mpctx->chapters = talloc_array(NULL, struct demux_chapter, 0);
+
+ for (int n = 0; n < given_chapters->u.list->num; n++) {
+ struct mpv_node *chapter_data = &given_chapters->u.list->values[n];
+
+ if (chapter_data->format != MPV_FORMAT_NODE_MAP)
+ continue;
+
+ mpv_node_list *chapter_data_elements = chapter_data->u.list;
+
+ double time = -1;
+ char *title = 0;
+
+ for (int e = 0; e < chapter_data_elements->num; e++) {
+ struct mpv_node *chapter_data_element =
+ &chapter_data_elements->values[e];
+ char *key = chapter_data_elements->keys[e];
+ switch (chapter_data_element->format) {
+ case MPV_FORMAT_INT64:
+ if (strcmp(key, "time") == 0)
+ time = (double)chapter_data_element->u.int64;
+ break;
+ case MPV_FORMAT_DOUBLE:
+ if (strcmp(key, "time") == 0)
+ time = chapter_data_element->u.double_;
+ break;
+ case MPV_FORMAT_STRING:
+ if (strcmp(key, "title") == 0)
+ title = chapter_data_element->u.string;
+ break;
+ }
+ }
+
+ if (time >= 0 && time < len) {
+ struct demux_chapter new = {
+ .pts = time,
+ .metadata = talloc_zero(mpctx->chapters, struct mp_tags),
+ };
+ if (title)
+ mp_tags_set_str(new.metadata, "title", title);
+ MP_TARRAY_APPEND(NULL, mpctx->chapters, mpctx->num_chapters, new);
+ }
+ }
+
+ mp_notify(mpctx, MPV_EVENT_CHAPTER_CHANGE, NULL);
+
+ return M_PROPERTY_OK;
+}
+
static int mp_property_list_chapters(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
int count = get_chapter_count(mpctx);
- if (action == M_PROPERTY_PRINT) {
+ switch (action) {
+ case M_PROPERTY_PRINT: {
int cur = mpctx->playback_initialized ? get_current_chapter(mpctx) : -1;
char *res = NULL;
int n;
@@ -893,6 +1069,11 @@ static int mp_property_list_chapters(void *ctx, struct m_property *prop,
*(char **)arg = res;
return M_PROPERTY_OK;
}
+ case M_PROPERTY_SET: {
+ struct mpv_node *given_chapters = arg;
+ return parse_node_chapters(mpctx, given_chapters);
+ }
+ }
return m_property_read_list(action, arg, count, get_chapter_entry, mpctx);
}
@@ -900,12 +1081,9 @@ static int mp_property_edition(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
- struct MPOpts *opts = mpctx->opts;
struct demuxer *demuxer = mpctx->demuxer;
- if (!demuxer)
- return M_PROPERTY_UNAVAILABLE;
- if (demuxer->num_editions <= 0)
- return M_PROPERTY_UNAVAILABLE;
+ if (!mpctx->playback_initialized || !demuxer || demuxer->num_editions <