diff options
Diffstat (limited to 'player')
-rw-r--r-- | player/audio.c | 84 | ||||
-rw-r--r-- | player/client.c | 148 | ||||
-rw-r--r-- | player/client.h | 1 | ||||
-rw-r--r-- | player/command.c | 834 | ||||
-rw-r--r-- | player/command.h | 8 | ||||
-rw-r--r-- | player/configfiles.c | 79 | ||||
-rw-r--r-- | player/core.h | 18 | ||||
-rw-r--r-- | player/loadfile.c | 15 | ||||
-rw-r--r-- | player/lua.c | 45 | ||||
-rw-r--r-- | player/lua/defaults.lua | 33 | ||||
-rw-r--r-- | player/lua/osc.lua | 276 | ||||
-rw-r--r-- | player/lua/ytdl_hook.lua | 91 | ||||
-rw-r--r-- | player/main.c | 210 | ||||
-rw-r--r-- | player/misc.c | 28 | ||||
-rw-r--r-- | player/osd.c | 48 | ||||
-rw-r--r-- | player/playloop.c | 121 | ||||
-rw-r--r-- | player/scripting.c | 46 | ||||
-rw-r--r-- | player/sub.c | 3 | ||||
-rw-r--r-- | player/video.c | 61 |
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 < |