Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c226bc7616 | ||
|
|
e60a36bd4e | ||
|
|
bc97d60542 | ||
|
|
4121016689 | ||
|
|
b636b19058 | ||
|
|
cb9b60ef19 | ||
|
|
31f28da0f3 | ||
|
|
ef0b2e33b1 | ||
|
|
2a5b61244f | ||
|
|
c4ba600832 | ||
|
|
c218d9e960 | ||
|
|
a47d849df7 | ||
|
|
6980575e15 | ||
|
|
724f60bf9a | ||
|
|
74d4073771 | ||
|
|
93104142f4 | ||
|
|
9a5e062a04 | ||
|
|
6b676d82fd | ||
|
|
af103aebd7 | ||
|
|
969c011522 | ||
|
|
1a8af89b7d | ||
|
|
e057629493 | ||
|
|
cb31e72589 | ||
|
|
ed644b0d33 | ||
|
|
23993e91f3 | ||
|
|
68dc869d6a | ||
|
|
e5f61c2bd5 | ||
|
|
2fbd1d3610 | ||
|
|
05e4df3f0c | ||
|
|
4aaa83339c | ||
|
|
5d2dfbdfca | ||
|
|
4e3663a2da | ||
|
|
e6952c7053 | ||
|
|
0f83caf96a | ||
|
|
1e53fc3a15 | ||
|
|
a1dec6f54a | ||
|
|
bbcd0b6a03 | ||
|
|
7bba97b301 | ||
|
|
0a0967f48b | ||
|
|
e02cb674ce | ||
|
|
f5bbb5aed2 | ||
|
|
07f8b64754 | ||
|
|
3a7e86ff6e | ||
|
|
12e251c29e | ||
|
|
814dacdd7d | ||
|
|
86fa1e6129 | ||
|
|
795b65b2ff |
No files matched your search
@@ -13,7 +13,9 @@ If you're not using git master or the latest release, update.
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
http://sprunge.us or a similar site, and replace this text with a link to it.
|
||||
|
||||
Do not bother reporting a bug if you do not provide the required information.
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without log it might not be
|
||||
possible to analyze and fix certain bugs.
|
||||
|
||||
### Sample files
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.20.0
|
||||
--- mpv 0.19.0
|
||||
1.22 - add stream_cb API for custom protocols
|
||||
--- mpv 0.18.1 ---
|
||||
|
||||
@@ -19,6 +19,10 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.20.0 ---
|
||||
- add --image-display-duration option - this also means that image duration
|
||||
is not influenced by --mf-fps anymore in the general case (this is an
|
||||
incompatible change)
|
||||
--- mpv 0.19.0 ---
|
||||
- deprecate "balance" option/property (no replacement)
|
||||
--- mpv 0.18.1 ---
|
||||
|
||||
+2
-2
@@ -526,7 +526,7 @@ Input Commands that are Possibly Subject to Change
|
||||
``disable-section "<section>"``
|
||||
Disable the named input section. Undoes ``enable-section``.
|
||||
|
||||
``define-section "<section>" "<contents>" [default|forced]``
|
||||
``define-section "<section>" "<contents>" [default|force]``
|
||||
Create a named input section, or replace the contents of an already existing
|
||||
input section. The ``contents`` parameter uses the same syntax as the
|
||||
``input.conf`` file (except that using the section syntax in it is not
|
||||
@@ -544,7 +544,7 @@ Input Commands that are Possibly Subject to Change
|
||||
<default> (also used if parameter omitted)
|
||||
Use a key binding defined by this section only if the user hasn't
|
||||
already bound this key to a command.
|
||||
<forced>
|
||||
<force>
|
||||
Always bind a key. (The input section that was made active most recently
|
||||
wins if there are ambiguities.)
|
||||
|
||||
|
||||
+25
-4
@@ -701,6 +701,8 @@ Video
|
||||
controls how much of the image is cropped. May not work with all video
|
||||
output drivers.
|
||||
|
||||
This option has no effect if ``--video-unscaled`` option is used.
|
||||
|
||||
``--video-aspect=<ratio>``
|
||||
Override video aspect ratio, in case aspect information is incorrect or
|
||||
missing in the file being played. See also ``--no-video-aspect``.
|
||||
@@ -738,10 +740,12 @@ Video
|
||||
choices if you encounter video that has the wrong aspect ratio in mpv,
|
||||
but seems to be correct in other players.
|
||||
|
||||
``--video-unscaled``
|
||||
``--video-unscaled=<no|yes|downscale-big>``
|
||||
Disable scaling of the video. If the window is larger than the video,
|
||||
black bars are added. Otherwise, the video is cropped. The video still
|
||||
can be influenced by the other ``--video-...`` options.
|
||||
black bars are added. Otherwise, the video is cropped, unless the option
|
||||
is set to ``downscale-big``, in which case the video is fit to window. The
|
||||
video still can be influenced by the other ``--video-...`` options. This
|
||||
option disables the effect of ``--panscan``.
|
||||
|
||||
Note that the scaler algorithm may still be used, even if the video isn't
|
||||
scaled. For example, this can influence chroma conversion. The video will
|
||||
@@ -1873,6 +1877,23 @@ Window
|
||||
file.mkv normally, then fail to open ``/dev/null``, then exit). (In
|
||||
mpv 0.8.0, ``always`` was introduced, which restores the old behavior.)
|
||||
|
||||
``--image-display-duration=<seconds|inf>``
|
||||
If the current file is an image, play the image for the given amount of
|
||||
seconds (default: 1). ``inf`` means the file is kept open forever (until
|
||||
the user stops playback manually).
|
||||
|
||||
Unlike ``--keep-open``, the player is not paused, but simply continues
|
||||
playback until the time has elapsed. (It should not use any resources
|
||||
during "playback".)
|
||||
|
||||
This affects image files, which are defined as having only 1 video frame
|
||||
and no audio. The player may recognize certain non-images as images, for
|
||||
example if ``--length`` is used to reduce the length to 1 frame, or if
|
||||
you seek to the last frame.
|
||||
|
||||
This option does not affect the framerate used for ``mf://`` or
|
||||
``--merge-files``. For that, use ``--mf-fps`` instead.
|
||||
|
||||
``--force-window=<yes|no|immediate>``
|
||||
Create a video output window even if there is no video. This can be useful
|
||||
when pretending that mpv is a GUI application. Currently, the window
|
||||
@@ -3650,7 +3671,7 @@ Miscellaneous
|
||||
``--video-sync-max-audio-change=<value>``
|
||||
Maximum *additional* speed difference in percent that is applied to audio
|
||||
with ``--video-sync=display-...`` (default: 0.125). Normally, the player
|
||||
play the audio at the speed of the video. But if the difference between
|
||||
plays the audio at the speed of the video. But if the difference between
|
||||
audio and video position is too high, e.g. due to drift or other timing
|
||||
errors, it will attempt to speed up or slow down audio by this additional
|
||||
factor. Too low values could lead to video frame dropping or repeating if
|
||||
|
||||
@@ -881,6 +881,16 @@ Available video output drivers are:
|
||||
|
||||
Windows only.
|
||||
|
||||
``dcomposition=<yes|no>``
|
||||
Allows DirectComposition when using the ANGLE backend (default: yes).
|
||||
DirectComposition implies flip-model presentation, which can improve
|
||||
rendering efficiency on Windows 8+ by avoiding a copy of the video frame.
|
||||
mpv uses it by default where possible, but it can cause poor behaviour
|
||||
with some drivers, such as a black screen or graphical corruption when
|
||||
leaving full-screen mode. Use "no" to disable it.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``sw``
|
||||
Continue even if a software renderer is detected.
|
||||
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
Release 0.20.0
|
||||
==============
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- aspect: add --video-unscaled=downscale-big
|
||||
- player: add --image-display-duration option to control duration of image display (#3425)
|
||||
- vo_opengl: angle: new flag (dcomposition) to control DirectComposition
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- af_lavrresample: fix error if resampler could not be recreated
|
||||
- audio: avoid missed wakeups with ab-loops
|
||||
- audio: do not apply --audio-channels if spdif passthrough is in use (#3445)
|
||||
- cache: don't use a backbuffer if the cache is as large as the file
|
||||
- command: prevent O(n^2) behaviour for playlist property
|
||||
- demux: close underlying stream if it's fully read anyway (#3456)
|
||||
- demux: fix undefined behavior with ogg metadata update (#3451)
|
||||
- player: make looping slightly more seamless
|
||||
- player: refresh very low framerate video on filter changes (#3435)
|
||||
- stream_memory: disable stream cache
|
||||
- vf_rotate: allow arbitrary rotation (#3434)
|
||||
- vo: be more trusting to estimated display FPS (#3433)
|
||||
- w32_common: use hooks to detect parent window resize
|
||||
- x11: work around mutter fullscreen issue (#2072)
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.19.0..v0.20.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.19.0...v0.20.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.20.0..v0.20.0
|
||||
|
||||
|
||||
Release 0.19.0
|
||||
==============
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
ctx->codec_timebase = (AVRational){0};
|
||||
ctx->codec_timebase = mp_get_codec_timebase(da->codec);
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
@@ -103,6 +103,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
lavc_context->refcounted_frames = 1;
|
||||
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
lavc_context->time_base = ctx->codec_timebase;
|
||||
|
||||
if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
|
||||
lavc_context->request_channel_layout =
|
||||
|
||||
@@ -779,6 +779,12 @@ struct mp_audio *af_read_output_frame(struct af_stream *s)
|
||||
return af_dequeue_output_frame(s->last);
|
||||
}
|
||||
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
struct af_instance *af = s->last;
|
||||
MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame);
|
||||
}
|
||||
|
||||
// Make sure the caller can change data referenced by the frame.
|
||||
// Return negative error code on failure (i.e. you can't write).
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame)
|
||||
|
||||
@@ -148,6 +148,7 @@ void af_add_output_frame(struct af_instance *af, struct mp_audio *frame);
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_output_frame(struct af_stream *s, bool eof);
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s);
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame);
|
||||
|
||||
double af_calc_delay(struct af_stream *s);
|
||||
|
||||
@@ -99,10 +99,6 @@ static double get_delay(struct af_resample *s)
|
||||
return avresample_get_delay(s->avrctx) / (double)s->in_rate +
|
||||
avresample_available(s->avrctx) / (double)s->out_rate;
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return avresample_get_out_samples(s->avrctx, in_samples);
|
||||
@@ -113,10 +109,6 @@ static double get_delay(struct af_resample *s)
|
||||
int64_t base = s->in_rate * (int64_t)s->out_rate;
|
||||
return swr_get_delay(s->avrctx, base) / (double)base;
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (swr_drop_output(s->avrctx, 1000) > 0) {}
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
#if LIBSWRESAMPLE_VERSION_MAJOR > 1 || LIBSWRESAMPLE_VERSION_MINOR >= 2
|
||||
@@ -406,8 +398,17 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (s->avrctx)
|
||||
drop_all_output(s);
|
||||
if (s->avrctx) {
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
swr_close(s->avrctx);
|
||||
if (swr_init(s->avrctx) < 0) {
|
||||
close_lavrr(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
#else
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
#endif
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
@@ -479,19 +480,20 @@ static void reorder_planes(struct mp_audio *mpa, int *reorder,
|
||||
static int filter_resample(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
struct mp_audio *out = NULL;
|
||||
|
||||
if (!s->avrctx)
|
||||
goto error;
|
||||
|
||||
int samples = get_out_samples(s, in ? in->samples : 0);
|
||||
|
||||
struct mp_audio out_format = s->pool_fmt;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, &out_format, samples);
|
||||
out = mp_audio_pool_get(af->out_pool, &out_format, samples);
|
||||
if (!out)
|
||||
goto error;
|
||||
if (in)
|
||||
mp_audio_copy_attributes(out, in);
|
||||
|
||||
if (!s->avrctx)
|
||||
goto error;
|
||||
|
||||
if (out->samples) {
|
||||
out->samples = resample_frame(s->avrctx, out, in);
|
||||
if (out->samples < 0)
|
||||
|
||||
+3
-1
@@ -337,8 +337,10 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = p};
|
||||
|
||||
snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(p->alsa);
|
||||
if (!maps)
|
||||
if (!maps) {
|
||||
MP_VERBOSE(ao, "snd_pcm_query_chmaps() returned NULL\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; maps[i] != NULL; i++) {
|
||||
char aname[128];
|
||||
|
||||
+29
-2
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/log.h>
|
||||
@@ -84,6 +85,30 @@ void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Pick a "good" timebase, which will be used to convert double timestamps
|
||||
// back to fractions for passing them through libavcodec.
|
||||
AVRational mp_get_codec_timebase(struct mp_codec_params *c)
|
||||
{
|
||||
AVRational tb = {c->native_tb_num, c->native_tb_den};
|
||||
if (tb.num < 1 || tb.den < 1) {
|
||||
if (c->reliable_fps)
|
||||
tb = av_inv_q(av_d2q(c->fps, 1000000));
|
||||
if (tb.num < 1 || tb.den < 1)
|
||||
tb = AV_TIME_BASE_Q;
|
||||
}
|
||||
|
||||
// If the timebase is too coarse, raise its precision, or small adjustments
|
||||
// to timestamps done between decoder and demuxer could be lost.
|
||||
if (av_q2d(tb) > 0.001) {
|
||||
AVRational r = av_div_q(tb, (AVRational){1, 1000});
|
||||
tb.den *= (r.num + r.den - 1) / r.den;
|
||||
}
|
||||
|
||||
av_reduce(&tb.num, &tb.den, tb.num, tb.den, 1000000);
|
||||
|
||||
return tb;
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
union pts { int64_t i; double d; };
|
||||
|
||||
@@ -97,8 +122,10 @@ union pts { int64_t i; double d; };
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb)
|
||||
{
|
||||
assert(sizeof(int64_t) >= sizeof(double));
|
||||
if (tb && tb->num > 0 && tb->den > 0)
|
||||
return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : mp_pts / av_q2d(*tb);
|
||||
if (tb && tb->num > 0 && tb->den > 0) {
|
||||
return mp_pts == MP_NOPTS_VALUE ?
|
||||
AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(*tb));
|
||||
}
|
||||
// The + 0.0 is to squash possible negative zero mp_pts, which would
|
||||
// happen to end up as AV_NOPTS_VALUE.
|
||||
return (union pts){.d = mp_pts + 0.0}.i;
|
||||
|
||||
@@ -33,6 +33,7 @@ struct mp_log;
|
||||
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
|
||||
AVRational mp_get_codec_timebase(struct mp_codec_params *c);
|
||||
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
|
||||
double mp_pts_from_av(int64_t av_pts, AVRational *tb);
|
||||
|
||||
+27
-5
@@ -580,12 +580,12 @@ static bool read_packet(struct demux_internal *in)
|
||||
if (packs >= in->max_packs || bytes >= in->max_bytes) {
|
||||
if (!in->warned_queue_overflow) {
|
||||
in->warned_queue_overflow = true;
|
||||
MP_ERR(in, "Too many packets in the demuxer packet queues:\n");
|
||||
MP_WARN(in, "Too many packets in the demuxer packet queues:\n");
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->selected) {
|
||||
MP_ERR(in, " %s/%d: %zd packets, %zd bytes\n",
|
||||
stream_type_name(ds->type), n, ds->packs, ds->bytes);
|
||||
MP_WARN(in, " %s/%d: %zd packets, %zd bytes\n",
|
||||
stream_type_name(ds->type), n, ds->packs, ds->bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1031,6 +1031,7 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->ts_resets_possible = src->ts_resets_possible;
|
||||
dst->fully_read = src->fully_read;
|
||||
dst->start_time = src->start_time;
|
||||
dst->is_network = src->is_network;
|
||||
dst->priv = src->priv;
|
||||
}
|
||||
|
||||
@@ -1039,10 +1040,10 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->metadata = mp_tags_dup(dst, src->metadata);
|
||||
|
||||
if (dst->num_update_stream_tags != src->num_update_stream_tags) {
|
||||
dst->num_update_stream_tags = src->num_update_stream_tags;
|
||||
talloc_free(dst->update_stream_tags);
|
||||
dst->update_stream_tags =
|
||||
talloc_zero_array(dst, struct mp_tags *, dst->num_update_stream_tags);
|
||||
dst->num_update_stream_tags = src->num_update_stream_tags;
|
||||
}
|
||||
for (int n = 0; n < dst->num_update_stream_tags; n++) {
|
||||
talloc_free(dst->update_stream_tags[n]);
|
||||
@@ -1151,6 +1152,23 @@ static void demux_init_cuesheet(struct demuxer *demuxer)
|
||||
}
|
||||
}
|
||||
|
||||
static void demux_maybe_replace_stream(struct demuxer *demuxer)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(!in->threading && demuxer == in->d_user);
|
||||
|
||||
if (demuxer->fully_read) {
|
||||
MP_VERBOSE(demuxer, "assuming demuxer read all data; closing stream\n");
|
||||
free_stream(demuxer->stream);
|
||||
demuxer->stream = open_memory_stream(NULL, 0); // dummy
|
||||
in->d_thread->stream = demuxer->stream;
|
||||
in->d_buffer->stream = demuxer->stream;
|
||||
|
||||
if (demuxer->desc->control)
|
||||
demuxer->desc->control(in->d_thread, DEMUXER_CTRL_REPLACE_STREAM, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct demuxer_desc *desc,
|
||||
@@ -1172,6 +1190,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.log = mp_log_new(demuxer, log, desc->name),
|
||||
.glog = log,
|
||||
.filename = talloc_strdup(demuxer, stream->url),
|
||||
.is_network = stream->is_network,
|
||||
.events = DEMUX_EVENT_ALL,
|
||||
};
|
||||
demuxer->seekable = stream->seekable;
|
||||
@@ -1311,6 +1330,7 @@ done:
|
||||
// Convenience function: open the stream, enable the cache (according to params
|
||||
// and global opts.), open the demuxer.
|
||||
// (use free_demuxer_and_stream() to free the underlying stream too)
|
||||
// Also for some reason may close the opened stream if it's not needed.
|
||||
struct demuxer *demux_open_url(const char *url,
|
||||
struct demuxer_params *params,
|
||||
struct mp_cancel *cancel,
|
||||
@@ -1329,7 +1349,9 @@ struct demuxer *demux_open_url(const char *url,
|
||||
if (!params->disable_cache)
|
||||
stream_enable_cache(&s, &opts->stream_cache);
|
||||
struct demuxer *d = demux_open(s, params, global);
|
||||
if (!d) {
|
||||
if (d) {
|
||||
demux_maybe_replace_stream(d);
|
||||
} else {
|
||||
params->demuxer_failed = true;
|
||||
free_stream(s);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ enum demux_ctrl {
|
||||
DEMUXER_CTRL_STREAM_CTRL,
|
||||
DEMUXER_CTRL_GET_READER_STATE,
|
||||
DEMUXER_CTRL_GET_BITRATE_STATS, // double[STREAM_TYPE_COUNT]
|
||||
DEMUXER_CTRL_REPLACE_STREAM,
|
||||
};
|
||||
|
||||
struct demux_ctrl_reader_state {
|
||||
@@ -183,6 +184,7 @@ typedef struct demuxer {
|
||||
// packets is not slow either (unlike e.g. libavdevice pseudo-demuxers).
|
||||
// Typical examples: text subtitles, playlists
|
||||
bool fully_read;
|
||||
bool is_network; // opened directly from a network stream
|
||||
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
int events;
|
||||
@@ -219,6 +221,7 @@ typedef struct demuxer {
|
||||
// thread-safe, only the demuxer is allowed to access the stream directly.
|
||||
// You can freely use demux_stream_control() to send STREAM_CTRLs, or use
|
||||
// demux_pause() to get exclusive access to the stream.
|
||||
// Also note that the stream can get replaced if fully_read is set.
|
||||
struct stream *stream;
|
||||
} demuxer_t;
|
||||
|
||||
|
||||
+19
-4
@@ -114,6 +114,7 @@ struct format_hack {
|
||||
// Do not confuse player's position estimation (position is into external
|
||||
// segment, with e.g. HLS, player knows about the playlist main file only).
|
||||
bool clear_filepos : 1;
|
||||
bool ignore_start : 1;
|
||||
};
|
||||
|
||||
#define BLACKLIST(fmt) {fmt, .ignore = true}
|
||||
@@ -137,6 +138,9 @@ static const struct format_hack format_hacks[] = {
|
||||
{"h264", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
{"hevc", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
|
||||
// Rebasing start time to 0 is very weird with ogg shoutcast streams.
|
||||
{"ogg", .ignore_start = true},
|
||||
|
||||
TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"),
|
||||
TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"),
|
||||
TEXTSUB("sami"), TEXTSUB("srt"), TEXTSUB("stl"), TEXTSUB("subviewer"),
|
||||
@@ -158,6 +162,7 @@ static const struct format_hack format_hacks[] = {
|
||||
|
||||
typedef struct lavf_priv {
|
||||
struct stream *stream;
|
||||
bool own_stream;
|
||||
char *filename;
|
||||
struct format_hack format_hack;
|
||||
AVInputFormat *avif;
|
||||
@@ -286,8 +291,10 @@ static void convert_charset(struct demuxer *demuxer)
|
||||
if (conv.start)
|
||||
data = conv;
|
||||
}
|
||||
if (data.start)
|
||||
if (data.start) {
|
||||
priv->stream = open_memory_stream(data.start, data.len);
|
||||
priv->own_stream = true;
|
||||
}
|
||||
talloc_free(alloc);
|
||||
}
|
||||
|
||||
@@ -666,6 +673,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
if (sh->codec->lav_headers)
|
||||
mp_copy_lav_codec_headers(sh->codec->lav_headers, codec);
|
||||
#endif
|
||||
sh->codec->native_tb_num = st->time_base.num;
|
||||
sh->codec->native_tb_den = st->time_base.den;
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
sh->default_track = true;
|
||||
@@ -855,8 +864,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
demuxer->ts_resets_possible =
|
||||
priv->avif_flags & (AVFMT_TS_DISCONT | AVFMT_NOTIMESTAMPS);
|
||||
|
||||
demuxer->start_time = priv->avfc->start_time == AV_NOPTS_VALUE ?
|
||||
0 : (double)priv->avfc->start_time / AV_TIME_BASE;
|
||||
if (avfc->start_time != AV_NOPTS_VALUE && !priv->format_hack.ignore_start)
|
||||
demuxer->start_time = avfc->start_time / (double)AV_TIME_BASE;
|
||||
|
||||
demuxer->fully_read = priv->format_hack.fully_read;
|
||||
|
||||
@@ -1062,6 +1071,12 @@ redo:
|
||||
av_seek_frame(priv->avfc, 0, stream_tell(priv->stream),
|
||||
AVSEEK_FLAG_BYTE);
|
||||
return DEMUXER_CTRL_OK;
|
||||
case DEMUXER_CTRL_REPLACE_STREAM:
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
priv->own_stream = false;
|
||||
priv->stream = demuxer->stream;
|
||||
return DEMUXER_CTRL_OK;
|
||||
default:
|
||||
return DEMUXER_CTRL_NOTIMPL;
|
||||
}
|
||||
@@ -1086,7 +1101,7 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (priv->stream != demuxer->stream)
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
talloc_free(priv);
|
||||
demuxer->priv = NULL;
|
||||
|
||||
@@ -321,6 +321,7 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
c->disp_w = 0;
|
||||
c->disp_h = 0;
|
||||
c->fps = demuxer->opts->mf_fps;
|
||||
c->reliable_fps = true;
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
|
||||
@@ -145,6 +145,9 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
|
||||
c->force_channels = true;
|
||||
c->samplerate = opts->samplerate;
|
||||
|
||||
c->native_tb_num = 1;
|
||||
c->native_tb_den = c->samplerate;
|
||||
|
||||
int f = opts->aformat;
|
||||
// See PCM(): sign float bits endian
|
||||
mp_set_pcm_codec(sh->codec, f & 1, f & 2, f >> 3, f & 4);
|
||||
@@ -233,6 +236,7 @@ static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check)
|
||||
c->codec = decoder;
|
||||
c->codec_tag = imgfmt;
|
||||
c->fps = opts->fps;
|
||||
c->reliable_fps = true;
|
||||
c->disp_w = width;
|
||||
c->disp_h = height;
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
+5
-1
@@ -74,6 +74,9 @@ struct mp_codec_params {
|
||||
struct AVCodecContext *lav_headers;
|
||||
struct AVCodecParameters *lav_codecpar;
|
||||
|
||||
// Timestamp granularity for converting double<->rational timestamps.
|
||||
int native_tb_num, native_tb_den;
|
||||
|
||||
// STREAM_AUDIO
|
||||
int samplerate;
|
||||
struct mp_chmap channels;
|
||||
@@ -84,7 +87,8 @@ struct mp_codec_params {
|
||||
|
||||
// STREAM_VIDEO
|
||||
bool avi_dts; // use DTS timing; first frame and DTS is 0
|
||||
float fps; // frames per second (set only if constant fps)
|
||||
double fps; // frames per second (set only if constant fps)
|
||||
bool reliable_fps; // the fps field is definitely not broken
|
||||
int par_w, par_h; // pixel aspect ratio (0 if unknown/square)
|
||||
int disp_w, disp_h; // display size
|
||||
int rotate; // intended display rotation, in degrees, [0, 359]
|
||||
|
||||
+5
-22
@@ -894,7 +894,8 @@ static int clamp_double(const m_option_t *opt, void *val)
|
||||
v = opt->min;
|
||||
r = M_OPT_OUT_OF_RANGE;
|
||||
}
|
||||
if (!isfinite(v)) {
|
||||
// (setting max/min to INFINITY/-INFINITY is allowed)
|
||||
if (!isfinite(v) && v != opt->max && v != opt->min) {
|
||||
v = opt->min;
|
||||
r = M_OPT_OUT_OF_RANGE;
|
||||
}
|
||||
@@ -921,22 +922,8 @@ static int parse_double(struct mp_log *log, const m_option_t *opt,
|
||||
return M_OPT_INVALID;
|
||||
}
|
||||
|
||||
if (opt->flags & M_OPT_MIN)
|
||||
if (tmp_float < opt->min) {
|
||||
mp_err(log, "The %.*s option must be >= %f: %.*s\n",
|
||||
BSTR_P(name), opt->min, BSTR_P(param));
|
||||
return M_OPT_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
if (opt->flags & M_OPT_MAX)
|
||||
if (tmp_float > opt->max) {
|
||||
mp_err(log, "The %.*s option must be <= %f: %.*s\n",
|
||||
BSTR_P(name), opt->max, BSTR_P(param));
|
||||
return M_OPT_OUT_OF_RANGE;
|
||||
}
|
||||
|
||||
if (!isfinite(tmp_float)) {
|
||||
mp_err(log, "The %.*s option must be a finite number: %.*s\n",
|
||||
if (clamp_double(opt, &tmp_float) < 0) {
|
||||
mp_err(log, "The %.*s option is out of range: %.*s\n",
|
||||
BSTR_P(name), BSTR_P(param));
|
||||
return M_OPT_OUT_OF_RANGE;
|
||||
}
|
||||
@@ -990,11 +977,7 @@ static int double_set(const m_option_t *opt, void *dst, struct mpv_node *src)
|
||||
} else {
|
||||
return M_OPT_UNKNOWN;
|
||||
}
|
||||
if ((opt->flags & M_OPT_MIN) && val < opt->min)
|
||||
return M_OPT_OUT_OF_RANGE;
|
||||
if ((opt->flags & M_OPT_MAX) && val > opt->max)
|
||||
return M_OPT_OUT_OF_RANGE;
|
||||
if (!isfinite(val))
|
||||
if (clamp_double(opt, &val) < 0)
|
||||
return M_OPT_OUT_OF_RANGE;
|
||||
*(double *)dst = val;
|
||||
return 1;
|
||||
|
||||
+6
-1
@@ -25,6 +25,7 @@
|
||||
#include <stddef.h>
|
||||
#include <sys/types.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -214,6 +215,8 @@ const m_option_t mp_opts[] = {
|
||||
({"no", 0},
|
||||
{"yes", 1},
|
||||
{"always", 2})),
|
||||
OPT_DOUBLE("image-display-duration", image_display_duration,
|
||||
M_OPT_RANGE, 0, INFINITY),
|
||||
|
||||
OPT_CHOICE("index", index_mode, 0, ({"default", 1}, {"recreate", 0})),
|
||||
|
||||
@@ -451,7 +454,8 @@ const m_option_t mp_opts[] = {
|
||||
OPT_FLOATRANGE("video-pan-y", vo.pan_y, 0, -3.0, 3.0),
|
||||
OPT_FLOATRANGE("video-align-x", vo.align_x, 0, -1.0, 1.0),
|
||||
OPT_FLOATRANGE("video-align-y", vo.align_y, 0, -1.0, 1.0),
|
||||
OPT_FLAG("video-unscaled", vo.unscaled, 0),
|
||||
OPT_CHOICE("video-unscaled", vo.unscaled, 0,
|
||||
({"no", 0}, {"yes", 1}, {"downscale-big", 2})),
|
||||
OPT_FLAG("force-rgba-osd-rendering", force_rgba_osd, 0),
|
||||
OPT_CHOICE_OR_INT("video-rotate", video_rotate, 0, 0, 359,
|
||||
({"no", -1})),
|
||||
@@ -796,6 +800,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.play_frames = -1,
|
||||
.rebase_start_time = 1,
|
||||
.keep_open = 0,
|
||||
.image_display_duration = 1.0,
|
||||
.stream_id = { { [STREAM_AUDIO] = -1,
|
||||
[STREAM_VIDEO] = -1,
|
||||
[STREAM_SUB] = -1, },
|
||||
|
||||
@@ -190,6 +190,7 @@ typedef struct MPOpts {
|
||||
int ignore_path_in_watch_later_config;
|
||||
int pause;
|
||||
int keep_open;
|
||||
double image_display_duration;
|
||||
char *lavfi_complex;
|
||||
int stream_id[2][STREAM_TYPE_COUNT];
|
||||
int stream_id_ff[STREAM_TYPE_COUNT];
|
||||
|
||||
+55
-26
@@ -229,7 +229,7 @@ int reinit_audio_filters(struct MPContext *mpctx)
|
||||
mpctx->playback_pts != MP_NOPTS_VALUE && delay > 0.2)
|
||||
{
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->playback_pts,
|
||||
MPSEEK_EXACT, true);
|
||||
MPSEEK_EXACT, 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -379,14 +379,18 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
|
||||
if (opts->ao_null_fallback && !spdif_fallback)
|
||||
ao_flags |= AO_INIT_NULL_FALLBACK;
|
||||
|
||||
if (!opts->audio_output_channels.set || opts->audio_output_channels.auto_safe)
|
||||
ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
|
||||
|
||||
if (opts->audio_stream_silence)
|
||||
ao_flags |= AO_INIT_STREAM_SILENCE;
|
||||
|
||||
mp_chmap_sel_list(&afs->output.channels, opts->audio_output_channels.chmaps,
|
||||
opts->audio_output_channels.num_chmaps);
|
||||
if (af_fmt_is_pcm(afs->output.format)) {
|
||||
if (!opts->audio_output_channels.set ||
|
||||
opts->audio_output_channels.auto_safe)
|
||||
ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
|
||||
|
||||
mp_chmap_sel_list(&afs->output.channels,
|
||||
opts->audio_output_channels.chmaps,
|
||||
opts->audio_output_channels.num_chmaps);
|
||||
}
|
||||
|
||||
mp_audio_set_channels(&afs->output, &afs->output.channels);
|
||||
|
||||
@@ -722,15 +726,40 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
|
||||
}
|
||||
|
||||
|
||||
static bool copy_output(struct af_stream *afs, struct mp_audio_buffer *outbuf,
|
||||
int minsamples, bool eof)
|
||||
static bool copy_output(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
|
||||
int minsamples, double endpts, bool eof, bool *seteof)
|
||||
{
|
||||
struct af_stream *afs = mpctx->ao_chain->af;
|
||||
|
||||
while (mp_audio_buffer_samples(outbuf) < minsamples) {
|
||||
if (af_output_frame(afs, eof) < 0)
|
||||
return true; // error, stop doing stuff
|
||||
|
||||
int cursamples = mp_audio_buffer_samples(outbuf);
|
||||
int maxsamples = INT_MAX;
|
||||
if (endpts != MP_NOPTS_VALUE) {
|
||||
double rate = afs->output.rate / mpctx->audio_speed;
|
||||
double curpts = written_audio_pts(mpctx);
|
||||
if (curpts != MP_NOPTS_VALUE)
|
||||
maxsamples = (endpts - curpts - mpctx->opts->audio_delay) * rate;
|
||||
}
|
||||
|
||||
struct mp_audio *mpa = af_read_output_frame(afs);
|
||||
if (!mpa)
|
||||
return false; // out of data
|
||||
|
||||
if (cursamples + mpa->samples > maxsamples) {
|
||||
if (cursamples < maxsamples) {
|
||||
struct mp_audio pre = *mpa;
|
||||
pre.samples = maxsamples - cursamples;
|
||||
mp_audio_buffer_append(outbuf, &pre);
|
||||
mp_audio_skip_samples(mpa, pre.samples);
|
||||
}
|
||||
af_unread_output_frame(afs, mpa);
|
||||
*seteof = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
mp_audio_buffer_append(outbuf, mpa);
|
||||
talloc_free(mpa);
|
||||
}
|
||||
@@ -764,20 +793,24 @@ static int decode_new_frame(struct ao_chain *ao_c)
|
||||
* Return 0 on success, or negative AD_* error code.
|
||||
* In the former case outbuf has at least minsamples buffered on return.
|
||||
* In case of EOF/error it might or might not be. */
|
||||
static int filter_audio(struct ao_chain *ao_c, struct mp_audio_buffer *outbuf,
|
||||
static int filter_audio(struct MPContext *mpctx, struct mp_audio_buffer *outbuf,
|
||||
int minsamples)
|
||||
{
|
||||
struct ao_chain *ao_c = mpctx->ao_chain;
|
||||
struct af_stream *afs = ao_c->af;
|
||||
if (afs->initialized < 1)
|
||||
return AD_ERR;
|
||||
|
||||
MP_STATS(ao_c, "start audio");
|
||||
|
||||
double endpts = get_play_end_pts(mpctx);
|
||||
|
||||
bool eof = false;
|
||||
int res;
|
||||
while (1) {
|
||||
res = 0;
|
||||
|
||||
if (copy_output(afs, outbuf, minsamples, false))
|
||||
if (copy_output(mpctx, outbuf, minsamples, endpts, false, &eof))
|
||||
break;
|
||||
|
||||
res = decode_new_frame(ao_c);
|
||||
@@ -785,13 +818,13 @@ static int filter_audio(struct ao_chain *ao_c, struct mp_audio_buffer *outbuf,
|
||||
break;
|
||||
if (res < 0) {
|
||||
// drain filters first (especially for true EOF case)
|
||||
copy_output(afs, outbuf, minsamples, true);
|
||||
copy_output(mpctx, outbuf, minsamples, endpts, true, &eof);
|
||||
break;
|
||||
}
|
||||
|
||||
// On format change, make sure to drain the filter chain.
|
||||
if (!mp_audio_config_equals(&afs->input, ao_c->input_frame)) {
|
||||
copy_output(afs, outbuf, minsamples, true);
|
||||
copy_output(mpctx, outbuf, minsamples, endpts, true, &eof);
|
||||
res = AD_NEW_FMT;
|
||||
break;
|
||||
}
|
||||
@@ -817,6 +850,9 @@ static int filter_audio(struct ao_chain *ao_c, struct mp_audio_buffer *outbuf,
|
||||
return AD_ERR;
|
||||
}
|
||||
|
||||
if (res == 0 && mp_audio_buffer_samples(outbuf) < minsamples && eof)
|
||||
res = AD_EOF;
|
||||
|
||||
MP_STATS(ao_c, "end audio");
|
||||
|
||||
return res;
|
||||
@@ -826,6 +862,7 @@ 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);
|
||||
|
||||
@@ -918,10 +955,10 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
|
||||
playsize = playsize / align * align;
|
||||
|
||||
int status = AD_OK;
|
||||
int status = mpctx->audio_status >= STATUS_DRAINING ? AD_EOF : AD_OK;
|
||||
bool working = false;
|
||||
if (playsize > mp_audio_buffer_samples(ao_c->ao_buffer)) {
|
||||
status = filter_audio(mpctx->ao_chain, ao_c->ao_buffer, playsize);
|
||||
status = filter_audio(mpctx, ao_c->ao_buffer, playsize);
|
||||
if (status == AD_WAIT)
|
||||
return;
|
||||
if (status == AD_NO_PROGRESS) {
|
||||
@@ -1015,17 +1052,6 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
bool partial_fill = false;
|
||||
int playflags = 0;
|
||||
|
||||
double endpts = get_play_end_pts(mpctx);
|
||||
if (endpts != MP_NOPTS_VALUE) {
|
||||
double samples = (endpts - written_audio_pts(mpctx) - opts->audio_delay)
|
||||
* play_samplerate;
|
||||
if (playsize > samples) {
|
||||
playsize = MPMAX((int)samples / align * align, 0);
|
||||
audio_eof = true;
|
||||
partial_fill = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (playsize > mp_audio_buffer_samples(ao_c->ao_buffer)) {
|
||||
playsize = mp_audio_buffer_samples(ao_c->ao_buffer);
|
||||
partial_fill = true;
|
||||
@@ -1057,8 +1083,11 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
|
||||
mpctx->audio_status = STATUS_DRAINING;
|
||||
// Wait until the AO has played all queued data. In the gapless case,
|
||||
// we trigger EOF immediately, and let it play asynchronously.
|
||||
if (ao_eof_reached(mpctx->ao) || opts->gapless_audio)
|
||||
if (ao_eof_reached(mpctx->ao) || opts->gapless_audio) {
|
||||
mpctx->audio_status = STATUS_EOF;
|
||||
if (!was_eof)
|
||||
mpctx->sleeptime = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-55
@@ -75,8 +75,6 @@ struct command_ctx {
|
||||
double last_seek_pts;
|
||||
double marked_pts;
|
||||
|
||||
double prev_pts;
|
||||
|
||||
char **warned_deprecated;
|
||||
int num_warned_deprecated;
|
||||
|
||||
@@ -220,7 +218,7 @@ static void mp_hook_add(struct MPContext *mpctx, char *client, char *name,
|
||||
}
|
||||
|
||||
// Call before a seek, in order to allow revert-seek to undo the seek.
|
||||
static void mark_seek(struct MPContext *mpctx)
|
||||
void mark_seek(struct MPContext *mpctx)
|
||||
{
|
||||
struct command_ctx *cmd = mpctx->command_ctx;
|
||||
double now = mp_time_sec();
|
||||
@@ -431,11 +429,9 @@ static int mp_property_stream_path(void *ctx, struct m_property *prop,
|
||||
int action, void *arg)
|
||||
{
|
||||
MPContext *mpctx = ctx;
|
||||
// demuxer->stream as well as stream->url are immutable -> ok to access
|
||||
struct stream *stream = mpctx->demuxer ? mpctx->demuxer->stream : NULL;
|
||||
if (!stream || !stream->url)
|
||||
if (!mpctx->demuxer || !mpctx->demuxer->filename)
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
return m_property_strdup_ro(action, arg, stream->url);
|
||||
return m_property_strdup_ro(action, arg, mpctx->demuxer->filename);
|
||||
}
|
||||
|
||||
struct change_stream_capture_args {
|
||||
@@ -649,7 +645,7 @@ static int mp_property_percent_pos(void *ctx, struct m_property *prop,
|
||||
switch (action) {
|
||||
case M_PROPERTY_SET: {
|
||||
double pos = *(double *)arg;
|
||||
queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, MPSEEK_DEFAULT, true);
|
||||
queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, MPSEEK_DEFAULT, 0);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
case M_PROPERTY_GET: {
|
||||
@@ -694,7 +690,7 @@ static int mp_property_time_pos(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
if (action == M_PROPERTY_SET) {
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, true);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, 0);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return property_time(action, arg, get_current_time(mpctx));
|
||||
@@ -743,7 +739,7 @@ static int mp_property_playback_time(void *ctx, struct m_property *prop,
|
||||
return M_PROPERTY_UNAVAILABLE;
|
||||
|
||||
if (action == M_PROPERTY_SET) {
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, true);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, 0);
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
return property_time(action, arg, get_playback_time(mpctx));
|
||||
@@ -844,7 +840,7 @@ static int mp_property_chapter(void *ctx, struct m_property *prop,
|
||||
} else {
|
||||
double pts = chapter_start_time(mpctx, chapter);
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, MPSEEK_DEFAULT, true);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, MPSEEK_DEFAULT, 0);
|
||||
mpctx->last_chapter_seek = chapter;
|
||||
mpctx->last_chapter_pts = pts;
|
||||
}
|
||||
@@ -2210,7 +2206,7 @@ static int mp_property_hwdec(void *ctx, struct m_property *prop,
|
||||
video_vd_control(vd, VDCTRL_REINIT, NULL);
|
||||
double last_pts = mpctx->last_vo_pts;
|
||||
if (last_pts != MP_NOPTS_VALUE)
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, true);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, 0);
|
||||
}
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
@@ -3194,10 +3190,30 @@ static int mp_property_playlist_pos_1(void *ctx, struct m_property *prop,
|
||||
return mp_property_playlist_pos_x(ctx, prop, action, arg, 1);
|
||||
}
|
||||
|
||||
struct get_playlist_ctx {
|
||||
struct MPContext *mpctx;
|
||||
int last_index;
|
||||
struct playlist_entry *last_entry;
|
||||
};
|
||||
|
||||
static int get_playlist_entry(int item, int action, void *arg, void *ctx)
|
||||
{
|
||||
struct MPContext *mpctx = ctx;
|
||||
struct playlist_entry *e = playlist_entry_from_index(mpctx->playlist, item);
|
||||
struct get_playlist_ctx *p = ctx;
|
||||
struct MPContext *mpctx = p->mpctx;
|
||||
|
||||
struct playlist_entry *e;
|
||||
// This is an optimization that prevents O(n^2) behaviour when the entire
|
||||
// playlist is requested. If a request is made for the last requested entry
|
||||
// or the entry immediately following it, it can be found without a full
|
||||
// traversal of the linked list.
|
||||
if (p->last_entry && item == p->last_index)
|
||||
e = p->last_entry;
|
||||
else if (p->last_entry && item == p->last_index + 1)
|
||||
e = p->last_entry->next;
|
||||
else
|
||||
e = playlist_entry_from_index(mpctx->playlist, item);
|
||||
p->last_index = item;
|
||||
p->last_entry = e;
|
||||
if (!e)
|
||||
return M_PROPERTY_ERROR;
|
||||
|
||||
@@ -3237,8 +3253,10 @@ static int mp_property_playlist(void *ctx, struct m_property *prop,
|
||||
*(char **)arg = res;
|
||||
return M_PROPERTY_OK;
|
||||
}
|
||||
|
||||
struct get_playlist_ctx p = { .mpctx = mpctx };
|
||||
return m_property_read_list(action, arg, playlist_entry_count(mpctx->playlist),
|
||||
get_playlist_entry, mpctx);
|
||||
get_playlist_entry, &p);
|
||||
}
|
||||
|
||||
static char *print_obj_osd_list(struct m_obj_settings *list)
|
||||
@@ -3301,9 +3319,11 @@ static int mp_property_ab_loop(void *ctx, struct m_property *prop,
|
||||
}
|
||||
int r = mp_property_generic_option(mpctx, prop, action, arg);
|
||||
if (r > 0 && action == M_PROPERTY_SET) {
|
||||
mpctx->ab_loop_clip = mpctx->playback_pts < opts->ab_loop[1];
|
||||
if (strcmp(prop->name, "ab-loop-b") == 0) {
|
||||
struct command_ctx *cctx = mpctx->command_ctx;
|
||||
cctx->prev_pts = opts->ab_loop[0];
|
||||
if (opts->ab_loop[1] != MP_NOPTS_VALUE &&
|
||||
mpctx->playback_pts <= opts->ab_loop[1])
|
||||
mpctx->ab_loop_clip = true;
|
||||
}
|
||||
// Update if visible
|
||||
set_osd_bar_chapters(mpctx, OSD_BAR_SEEK);
|
||||
@@ -4622,19 +4642,19 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
mark_seek(mpctx);
|
||||
switch (abs) {
|
||||
case 0: { // Relative seek
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, false);
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, MPSEEK_FLAG_DELAY);
|
||||
set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW);
|
||||
break;
|
||||
}
|
||||
case 1: { // Absolute seek by percentage
|
||||
double ratio = v / 100.0;
|
||||
double cur_pos = get_current_pos_ratio(mpctx, false);
|
||||
queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, false);
|
||||
queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, MPSEEK_FLAG_DELAY);
|
||||
set_osd_function(mpctx, cur_pos < ratio ? OSD_FFW : OSD_REW);
|
||||
break;
|
||||
}
|
||||
case 2: { // Absolute seek to a timestamp in seconds
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, false);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, MPSEEK_FLAG_DELAY);
|
||||
set_osd_function(mpctx,
|
||||
v > get_current_time(mpctx) ? OSD_FFW : OSD_REW);
|
||||
break;
|
||||
@@ -4642,7 +4662,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
case 3: { // Relative seek by percentage
|
||||
queue_seek(mpctx, MPSEEK_FACTOR,
|
||||
get_current_pos_ratio(mpctx, false) + v / 100.0,
|
||||
precision, false);
|
||||
precision, MPSEEK_FLAG_DELAY);
|
||||
set_osd_function(mpctx, v > 0 ? OSD_FFW : OSD_REW);
|
||||
break;
|
||||
}}
|
||||
@@ -4664,7 +4684,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
} else if (oldpts != MP_NOPTS_VALUE) {
|
||||
cmdctx->last_seek_pts = get_current_time(mpctx);
|
||||
cmdctx->marked_pts = MP_NOPTS_VALUE;
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, oldpts, MPSEEK_EXACT, false);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, oldpts, MPSEEK_EXACT,
|
||||
MPSEEK_FLAG_DELAY);
|
||||
set_osd_function(mpctx, OSD_REW);
|
||||
if (bar_osd)
|
||||
mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR;
|
||||
@@ -4850,7 +4871,8 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re
|
||||
// rounding for the mess of it.
|
||||
a[0] += 0.01 * (a[1] >= 0 ? 1 : -1);
|
||||
mark_seek(mpctx);
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, a[0], MPSEEK_EXACT, false);
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, a[0], MPSEEK_EXACT,
|
||||
MPSEEK_FLAG_DELAY);
|
||||
set_osd_function(mpctx, (a[0] > 0) ? OSD_FFW : OSD_REW);
|
||||
if (bar_osd)
|
||||
mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR;
|
||||
@@ -5373,7 +5395,6 @@ void command_init(struct MPContext *mpctx)
|
||||
mpctx->command_ctx = talloc(NULL, struct command_ctx);
|
||||
*mpctx->command_ctx = (struct command_ctx){
|
||||
.last_seek_pts = MP_NOPTS_VALUE,
|
||||
.prev_pts = MP_NOPTS_VALUE,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5386,8 +5407,6 @@ static void command_event(struct MPContext *mpctx, int event, void *arg)
|
||||
ctx->marked_pts = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
if (event == MPV_EVENT_SEEK)
|
||||
ctx->prev_pts = MP_NOPTS_VALUE;
|
||||
if (event == MPV_EVENT_IDLE)
|
||||
ctx->is_idle = true;
|
||||
if (event == MPV_EVENT_START_FILE)
|
||||
@@ -5398,35 +5417,6 @@ static void command_event(struct MPContext *mpctx, int event, void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
void handle_ab_loop(struct MPContext *mpctx)
|
||||
{
|
||||
struct command_ctx *ctx = mpctx->command_ctx;
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
if (opts->pause)
|
||||
return;
|
||||
|
||||
double now = mpctx->restart_complete ? mpctx->playback_pts : MP_NOPTS_VALUE;
|
||||
if (now != MP_NOPTS_VALUE && (opts->ab_loop[0] != MP_NOPTS_VALUE ||
|
||||
opts->ab_loop[1] != MP_NOPTS_VALUE))
|
||||
{
|
||||
double start = opts->ab_loop[0];
|
||||
if (start == MP_NOPTS_VALUE)
|
||||
start = 0;
|
||||
double end = opts->ab_loop[1];
|
||||
if (end == MP_NOPTS_VALUE)
|
||||
end = INFINITY;
|
||||
if (ctx->prev_pts >= start && ctx->prev_pts < end &&
|
||||
(now >= end || mpctx->stop_play == AT_END_OF_FILE))
|
||||
{
|
||||
mark_seek(mpctx);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, start,
|
||||
MPSEEK_EXACT, false);
|
||||
}
|
||||
}
|
||||
ctx->prev_pts = now;
|
||||
}
|
||||
|
||||
void handle_command_updates(struct MPContext *mpctx)
|
||||
{
|
||||
struct command_ctx *ctx = mpctx->command_ctx;
|
||||
|
||||
+1
-1
@@ -57,6 +57,6 @@ enum {
|
||||
bool mp_hook_test_completion(struct MPContext *mpctx, char *type);
|
||||
void mp_hook_run(struct MPContext *mpctx, char *client, char *type);
|
||||
|
||||
void handle_ab_loop(struct MPContext *mpctx);
|
||||
void mark_seek(struct MPContext *mpctx);
|
||||
|
||||
#endif /* MPLAYER_COMMAND_H */
|
||||
+8
-2
@@ -80,6 +80,11 @@ enum seek_precision {
|
||||
MPSEEK_VERY_EXACT,
|
||||
};
|
||||
|
||||
enum seek_flags {
|
||||
MPSEEK_FLAG_DELAY = 1 << 0, // give player chance to coalesce multiple seeks
|
||||
MPSEEK_FLAG_NOFLUSH = 1 << 1, // keeping remaining data for seamless loops
|
||||
};
|
||||
|
||||
enum video_sync {
|
||||
VS_DEFAULT = 0,
|
||||
VS_DISP_RESAMPLE,
|
||||
@@ -322,6 +327,7 @@ typedef struct MPContext {
|
||||
bool hrseek_lastframe; // drop everything until last frame reached
|
||||
bool hrseek_backstep; // go to frame before seek target
|
||||
double hrseek_pts;
|
||||
bool ab_loop_clip; // clip to the "b" part of an A-B loop if available
|
||||
// AV sync: the next frame should be shown when the audio out has this
|
||||
// much (in seconds) buffered data left. Increased when more data is
|
||||
// written to the ao, decreased when moving to the next video frame.
|
||||
@@ -380,7 +386,7 @@ typedef struct MPContext {
|
||||
enum seek_type type;
|
||||
enum seek_precision exact;
|
||||
double amount;
|
||||
bool immediate; // disable seek delay logic
|
||||
unsigned flags; // MPSEEK_FLAG_*
|
||||
} seek;
|
||||
|
||||
// Allow audio to issue a second seek if audio is too far ahead (for non-hr
|
||||
@@ -512,7 +518,7 @@ void pause_player(struct MPContext *mpctx);
|
||||
void unpause_player(struct MPContext *mpctx);
|
||||
void add_step_frame(struct MPContext *mpctx, int dir);
|
||||
void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
|
||||
enum seek_precision exact, bool immediate);
|
||||
enum seek_precision exact, int flags);
|
||||
double get_time_length(struct MPContext *mpctx);
|
||||
double get_current_time(struct MPContext *mpctx);
|
||||
double get_playback_time(struct MPContext *mpctx);
|
||||
|
||||
+3
-3
@@ -634,7 +634,7 @@ void autoload_external_files(struct MPContext *mpctx)
|
||||
char *lang = list[i].lang;
|
||||
for (int n = 0; n < mpctx->num_tracks; n++) {
|
||||
struct track *t = mpctx->tracks[n];
|
||||
if (t->demuxer && strcmp(t->demuxer->stream->url, filename) == 0)
|
||||
if (t->demuxer && strcmp(t->demuxer->filename, filename) == 0)
|
||||
goto skip;
|
||||
}
|
||||
if (list[i].type == STREAM_SUB && !sc[STREAM_VIDEO] && !sc[STREAM_AUDIO])
|
||||
@@ -1029,7 +1029,7 @@ reopen_file:
|
||||
int entry_stream_flags = 0;
|
||||
if (!pl->disable_safety) {
|
||||
entry_stream_flags = STREAM_SAFE_ONLY;
|
||||
if (mpctx->demuxer->stream->is_network)
|
||||
if (mpctx->demuxer->is_network)
|
||||
entry_stream_flags |= STREAM_NETWORK_ONLY;
|
||||
}
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
||||
@@ -1140,7 +1140,7 @@ reopen_file:
|
||||
startpos = start;
|
||||
}
|
||||
if (startpos != MP_NOPTS_VALUE) {
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, 0, true);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, startpos, MPSEEK_DEFAULT, 0);
|
||||
execute_queued_seek(mpctx);
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -90,6 +90,12 @@ double get_play_end_pts(struct MPContext *mpctx)
|
||||
if (cend != MP_NOPTS_VALUE && (end == MP_NOPTS_VALUE || cend < end))
|
||||
end = cend;
|
||||
}
|
||||
if (mpctx->ab_loop_clip && opts->ab_loop[1] != MP_NOPTS_VALUE &&
|
||||
opts->ab_loop[1] > opts->ab_loop[0])
|
||||
{
|
||||
if (end == MP_NOPTS_VALUE || end > opts->ab_loop[1])
|
||||
end = opts->ab_loop[1];
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
@@ -143,8 +149,8 @@ void update_vo_playback_state(struct MPContext *mpctx)
|
||||
if ((oldstate.playing && oldstate.taskbar_progress) ||
|
||||
(newstate.playing && newstate.taskbar_progress))
|
||||
{
|
||||
vo_control(mpctx->video_out,
|
||||
VOCTRL_UPDATE_PLAYBACK_STATE, &newstate);
|
||||
vo_control_async(mpctx->video_out,
|
||||
VOCTRL_UPDATE_PLAYBACK_STATE, &newstate);
|
||||
}
|
||||
mpctx->vo_playback_state = newstate;
|
||||
}
|
||||
|
||||
+31
-10
@@ -141,7 +141,7 @@ void add_step_frame(struct MPContext *mpctx, int dir)
|
||||
unpause_player(mpctx);
|
||||
} else if (dir < 0) {
|
||||
if (!mpctx->hrseek_active) {
|
||||
queue_seek(mpctx, MPSEEK_BACKSTEP, 0, MPSEEK_VERY_EXACT, true);
|
||||
queue_seek(mpctx, MPSEEK_BACKSTEP, 0, MPSEEK_VERY_EXACT, 0);
|
||||
pause_player(mpctx);
|
||||
}
|
||||
}
|
||||
@@ -172,6 +172,7 @@ void reset_playback_state(struct MPContext *mpctx)
|
||||
mpctx->last_seek_pts = MP_NOPTS_VALUE;
|
||||
mpctx->cache_wait_time = 0;
|
||||
mpctx->step_frames = 0;
|
||||
mpctx->ab_loop_clip = true;
|
||||
mpctx->restart_complete = false;
|
||||
|
||||
#if HAVE_ENCODING
|
||||
@@ -272,7 +273,9 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
}
|
||||
}
|
||||
|
||||
clear_audio_output_buffers(mpctx);
|
||||
if (!(seek.flags & MPSEEK_FLAG_NOFLUSH))
|
||||
clear_audio_output_buffers(mpctx);
|
||||
|
||||
reset_playback_state(mpctx);
|
||||
|
||||
/* Use the target time as "current position" for further relative
|
||||
@@ -301,11 +304,13 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
|
||||
|
||||
mpctx->audio_allow_second_chance_seek =
|
||||
!hr_seek && !(demux_flags & SEEK_FORWARD);
|
||||
|
||||
mpctx->ab_loop_clip = mpctx->last_seek_pts < opts->ab_loop[1];
|
||||
}
|
||||
|
||||
// This combines consecutive seek requests.
|
||||
void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
|
||||
enum seek_precision exact, bool immediate)
|
||||
enum seek_precision exact, int flags)
|
||||
{
|
||||
struct seek_params *seek = &mpctx->seek;
|
||||
|
||||
@@ -314,7 +319,7 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
|
||||
|
||||
switch (type) {
|
||||
case MPSEEK_RELATIVE:
|
||||
seek->immediate |= immediate;
|
||||
seek->flags |= flags;
|
||||
if (seek->type == MPSEEK_FACTOR)
|
||||
return; // Well... not common enough to bother doing better
|
||||
seek->amount += amount;
|
||||
@@ -332,7 +337,7 @@ void queue_seek(struct MPContext *mpctx, enum seek_type type, double amount,
|
||||
.type = type,
|
||||
.amount = amount,
|
||||
.exact = exact,
|
||||
.immediate = immediate,
|
||||
.flags = flags,
|
||||
};
|
||||
return;
|
||||
case MPSEEK_NONE:
|
||||
@@ -351,7 +356,8 @@ void execute_queued_seek(struct MPContext *mpctx)
|
||||
/* If the user seeks continuously (keeps arrow key down)
|
||||
* try to finish showing a frame from one location before doing
|
||||
* another seek (which could lead to unchanging display). */
|
||||
if (!mpctx->seek.immediate && mpctx->video_status < STATUS_PLAYING &&
|
||||
bool delay = mpctx->seek.flags & MPSEEK_FLAG_DELAY;
|
||||
if (delay && mpctx->video_status < STATUS_PLAYING &&
|
||||
mp_time_sec() - mpctx->start_timestamp < 0.3)
|
||||
return;
|
||||
mp_seek(mpctx, mpctx->seek);
|
||||
@@ -682,7 +688,7 @@ static void handle_sstep(struct MPContext *mpctx)
|
||||
|
||||
if (opts->step_sec > 0 && !mpctx->paused) {
|
||||
set_osd_function(mpctx, OSD_FFW);
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, opts->step_sec, MPSEEK_DEFAULT, true);
|
||||
queue_seek(mpctx, MPSEEK_RELATIVE, opts->step_sec, MPSEEK_DEFAULT, 0);
|
||||
}
|
||||
|
||||
if (mpctx->video_status >= STATUS_EOF) {
|
||||
@@ -696,10 +702,25 @@ static void handle_sstep(struct MPContext *mpctx)
|
||||
static void handle_loop_file(struct MPContext *mpctx)
|
||||
{
|
||||
struct MPOpts *opts = mpctx->opts;
|
||||
|
||||
if (mpctx->stop_play == AT_END_OF_FILE &&
|
||||
(opts->ab_loop[0] != MP_NOPTS_VALUE || opts->ab_loop[1] != MP_NOPTS_VALUE))
|
||||
{
|
||||
// Assumes execute_queued_seek() happens before next audio/video is
|
||||
// attempted to be decoded or filtered.
|
||||
mpctx->stop_play = KEEP_PLAYING;
|
||||
double start = 0;
|
||||
if (opts->ab_loop[0] != MP_NOPTS_VALUE)
|
||||
start = opts->ab_loop[0];
|
||||
mark_seek(mpctx);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, start, MPSEEK_EXACT,
|
||||
MPSEEK_FLAG_NOFLUSH);
|
||||
}
|
||||
|
||||
if (opts->loop_file && mpctx->stop_play == AT_END_OF_FILE) {
|
||||
mpctx->stop_play = KEEP_PLAYING;
|
||||
set_osd_function(mpctx, OSD_FFW);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, 0, MPSEEK_DEFAULT, true);
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, 0, MPSEEK_DEFAULT, MPSEEK_FLAG_NOFLUSH);
|
||||
if (opts->loop_file > 0)
|
||||
opts->loop_file--;
|
||||
}
|
||||
@@ -893,6 +914,7 @@ static void handle_playback_restart(struct MPContext *mpctx)
|
||||
mpctx->hrseek_active = false;
|
||||
mpctx->restart_complete = true;
|
||||
mpctx->audio_allow_second_chance_seek = false;
|
||||
handle_playback_time(mpctx);
|
||||
mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL);
|
||||
if (!mpctx->playing_msg_shown) {
|
||||
if (opts->playing_msg && opts->playing_msg[0]) {
|
||||
@@ -912,6 +934,7 @@ static void handle_playback_restart(struct MPContext *mpctx)
|
||||
}
|
||||
mpctx->playing_msg_shown = true;
|
||||
mpctx->sleeptime = 0;
|
||||
mpctx->ab_loop_clip = mpctx->playback_pts < opts->ab_loop[1];
|
||||
MP_VERBOSE(mpctx, "playback restart complete\n");
|
||||
}
|
||||
}
|
||||
@@ -1012,8 +1035,6 @@ void run_playloop(struct MPContext *mpctx)
|
||||
|
||||
handle_loop_file(mpctx);
|
||||
|
||||
handle_ab_loop(mpctx);
|
||||
|
||||
handle_keep_open(mpctx);
|
||||
|
||||
handle_sstep(mpctx);
|
||||
|
||||
+34
-19
@@ -217,8 +217,8 @@ static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
|
||||
if (mpctx->opts->deinterlace >= 0)
|
||||
video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0});
|
||||
|
||||
if (params.rotate && (params.rotate % 90 == 0)) {
|
||||
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90)) {
|
||||
if (params.rotate) {
|
||||
if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90) || params.rotate % 90) {
|
||||
// Try to insert a rotation filter.
|
||||
char *args[] = {"angle", "auto", NULL};
|
||||
if (try_filter(vo_c, "rotate", "autorotate", args) < 0)
|
||||
@@ -539,11 +539,13 @@ void mp_force_video_refresh(struct MPContext *mpctx)
|
||||
return;
|
||||
|
||||
// If not paused, the next frame should come soon enough.
|
||||
if (opts->pause && mpctx->video_status == STATUS_PLAYING &&
|
||||
if ((opts->pause || mpctx->time_frame >= 0.5) &&
|
||||
(mpctx->video_status >= STATUS_PLAYING ||
|
||||
mpctx->video_status <= STATUS_DRAINING) &&
|
||||
mpctx->last_vo_pts != MP_NOPTS_VALUE)
|
||||
{
|
||||
queue_seek(mpctx, MPSEEK_ABSOLUTE, mpctx->last_vo_pts,
|
||||
MPSEEK_VERY_EXACT, true);
|
||||
MPSEEK_VERY_EXACT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -840,9 +842,11 @@ static int video_output_image(struct MPContext *mpctx)
|
||||
struct mp_image *img = vf_read_output_frame(vo_c->vf);
|
||||
if (img) {
|
||||
double endpts = get_play_end_pts(mpctx);
|
||||
if (endpts != MP_NOPTS_VALUE && img->pts >= endpts) {
|
||||
r = VD_EOF;
|
||||
} else if (mpctx->max_frames == 0) {
|
||||
if ((endpts != MP_NOPTS_VALUE && img->pts >= endpts) ||
|
||||
mpctx->max_frames == 0)
|
||||
{
|
||||
vf_unread_output_frame(vo_c->vf, img);
|
||||
img = NULL;
|
||||
r = VD_EOF;
|
||||
} else if (hrseek && mpctx->hrseek_lastframe) {
|
||||
mp_image_setrefp(&mpctx->saved_frame, img);
|
||||
@@ -1283,10 +1287,6 @@ static void calculate_frame_duration(struct MPContext *mpctx)
|
||||
double pts1 = mpctx->next_frames[1]->pts;
|
||||
if (pts0 != MP_NOPTS_VALUE && pts1 != MP_NOPTS_VALUE && pts1 >= pts0)
|
||||
duration = pts1 - pts0;
|
||||
} else {
|
||||
// E.g. last frame on EOF. Only use it if it's significant.
|
||||
if (demux_duration >= 0.1)
|
||||
duration = demux_duration;
|
||||
}
|
||||
|
||||
// The following code tries to compensate for rounded Matroska timestamps
|
||||
@@ -1350,17 +1350,32 @@ void write_video(struct MPContext *mpctx)
|
||||
return;
|
||||
|
||||
if (r == VD_EOF) {
|
||||
int prev_state = mpctx->video_status;
|
||||
mpctx->video_status = STATUS_EOF;
|
||||
if (mpctx->num_past_frames > 0 && mpctx->past_frames[0].duration > 0) {
|
||||
if (vo_still_displaying(vo))
|
||||
mpctx->video_status = STATUS_DRAINING;
|
||||
}
|
||||
mpctx->delay = 0;
|
||||
mpctx->last_av_difference = 0;
|
||||
|
||||
if (mpctx->video_status <= STATUS_PLAYING) {
|
||||
mpctx->video_status = STATUS_DRAINING;
|
||||
get_relative_time(mpctx);
|
||||
if (mpctx->num_past_frames == 1 && mpctx->past_frames[0].pts == 0 &&
|
||||
!mpctx->ao_chain)
|
||||
{
|
||||
MP_VERBOSE(mpctx, "assuming this is an image\n");
|
||||
mpctx->time_frame += opts->image_display_duration;
|
||||
} else {
|
||||
mpctx->time_frame = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (mpctx->video_status == STATUS_DRAINING) {
|
||||
mpctx->time_frame -= get_relative_time(mpctx);
|
||||
mpctx->sleeptime = MPMIN(mpctx->sleeptime, mpctx->time_frame);
|
||||
if (mpctx->time_frame <= 0) {
|
||||
MP_VERBOSE(mpctx, "video EOF reached\n");
|
||||
mpctx->video_status = STATUS_EOF;
|
||||
}
|
||||
}
|
||||
|
||||
MP_DBG(mpctx, "video EOF (status=%d)\n", mpctx->video_status);
|
||||
if (prev_state != mpctx->video_status)
|
||||
mpctx->sleeptime = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+14
-10
@@ -325,6 +325,14 @@ static int resize_cache(struct priv *s, int64_t size)
|
||||
int64_t min_size = FILL_LIMIT * 2;
|
||||
int64_t max_size = ((size_t)-1) / 8;
|
||||
|
||||
if (s->stream_size > 0) {
|
||||
size = MPMIN(size, s->stream_size);
|
||||
if (size >= s->stream_size) {
|
||||
MP_VERBOSE(s, "no backbuffer needed\n");
|
||||
s->back_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t buffer_size = MPCLAMP(size, min_size, max_size);
|
||||
s->back_size = MPCLAMP(s->back_size, min_size, max_size);
|
||||
buffer_size += s->back_size;
|
||||
@@ -373,6 +381,10 @@ static int resize_cache(struct priv *s, int64_t size)
|
||||
if (s->seek_limit > s->buffer_size - FILL_LIMIT)
|
||||
s->seek_limit = s->buffer_size - FILL_LIMIT;
|
||||
|
||||
MP_VERBOSE(s, "Cache size set to %lld KiB (%lld KiB backbuffer)\n",
|
||||
(long long)(s->buffer_size / 1024),
|
||||
(long long)(s->back_size / 1024));
|
||||
|
||||
assert(s->back_size < s->buffer_size);
|
||||
|
||||
return STREAM_OK;
|
||||
@@ -675,22 +687,14 @@ int stream_cache_init(stream_t *cache, stream_t *stream,
|
||||
s->seek_limit = opts->seek_min * 1024ULL;
|
||||
s->back_size = opts->back_buffer * 1024ULL;
|
||||
|
||||
int64_t cache_size = opts->size * 1024ULL;
|
||||
s->stream_size = stream_get_size(stream);
|
||||
|
||||
int64_t file_size = stream_get_size(stream);
|
||||
if (file_size >= 0)
|
||||
cache_size = MPMIN(cache_size, file_size);
|
||||
|
||||
if (resize_cache(s, cache_size) != STREAM_OK) {
|
||||
if (resize_cache(s, opts->size * 1024ULL) != STREAM_OK) {
|
||||
MP_ERR(s, "Failed to allocate cache buffer.\n");
|
||||
talloc_free(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
MP_VERBOSE(cache, "Cache size set to %lld KiB (%lld KiB backbuffer)\n",
|
||||
(long long)(s->buffer_size / 1024),
|
||||
(long long)(s->back_size / 1024));
|
||||
|
||||
pthread_mutex_init(&s->mutex, NULL);
|
||||
pthread_cond_init(&s->wakeup, NULL);
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ static int open_f(stream_t *stream)
|
||||
stream->seekable = true;
|
||||
stream->control = control;
|
||||
stream->read_chunk = 1024 * 1024;
|
||||
stream->allow_caching = false;
|
||||
|
||||
struct priv *p = talloc_zero(stream, struct priv);
|
||||
stream->priv = p;
|
||||
|
||||
@@ -94,6 +94,9 @@ bool ta_vasprintf_append_buffer(char **str, const char *fmt, va_list ap) TA_PRF(
|
||||
|
||||
#define ta_steal(ta_parent, ptr) (TA_TYPEOF(ptr))ta_steal_(ta_parent, ptr)
|
||||
|
||||
#define ta_dup_ptrtype(ta_parent, ptr) \
|
||||
(TA_TYPEOF(ptr))ta_memdup(ta_parent, ptr, sizeof(*(ptr)))
|
||||
|
||||
// Ugly macros that crash on OOM.
|
||||
// All of these mirror real functions (with a 'x' added after the 'ta_'
|
||||
// prefix), and the only difference is that they will call abort() on allocation
|
||||
@@ -121,6 +124,7 @@ bool ta_vasprintf_append_buffer(char **str, const char *fmt, va_list ap) TA_PRF(
|
||||
#define ta_xnew_array_size(...) ta_oom_p(ta_new_array_size(__VA_ARGS__))
|
||||
#define ta_xnew_ptrtype(...) ta_oom_g(ta_new_ptrtype(__VA_ARGS__))
|
||||
#define ta_xnew_array_ptrtype(...) ta_oom_g(ta_new_array_ptrtype(__VA_ARGS__))
|
||||
#define ta_xdup_ptrtype(...) ta_oom_g(ta_dup_ptrtype(__VA_ARGS__))
|
||||
|
||||
#define ta_xsteal(ta_parent, ptr) (TA_TYPEOF(ptr))ta_xsteal_(ta_parent, ptr)
|
||||
#define ta_xrealloc(ta_parent, ptr, type, count) \
|
||||
|
||||
@@ -449,10 +449,7 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
|
||||
if (!lavc_codec)
|
||||
return;
|
||||
|
||||
ctx->codec_timebase = (AVRational){0};
|
||||
if (strstr(decoder, "_mmal") || strstr(decoder, "_mediacodec"))
|
||||
ctx->codec_timebase = (AVRational){1, 1000000};
|
||||
|
||||
ctx->codec_timebase = mp_get_codec_timebase(vd->codec);
|
||||
ctx->pix_fmt = AV_PIX_FMT_NONE;
|
||||
ctx->hwdec = hwdec;
|
||||
ctx->hwdec_fmt = 0;
|
||||
@@ -463,9 +460,7 @@ static void init_avctx(struct dec_video *vd, const char *decoder,
|
||||
avctx->opaque = vd;
|
||||
avctx->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
avctx->codec_id = lavc_codec->id;
|
||||
|
||||
if (ctx->codec_timebase.num)
|
||||
avctx->time_base = ctx->codec_timebase;
|
||||
avctx->time_base = ctx->codec_timebase;
|
||||
|
||||
avctx->refcounted_frames = 1;
|
||||
ctx->pic = av_frame_alloc();
|
||||
@@ -828,7 +823,6 @@ static void decode(struct dec_video *vd, struct demux_packet *packet,
|
||||
struct demux_packet *cc = new_demux_packet_from(sd->data, sd->size);
|
||||
cc->pts = vd->codec_pts;
|
||||
cc->dts = vd->codec_dts;
|
||||
cc->pos = -1;
|
||||
demuxer_feed_caption(vd->header, cc);
|
||||
}
|
||||
|
||||
|
||||
@@ -458,6 +458,13 @@ struct mp_image *vf_read_output_frame(struct vf_chain *c)
|
||||
return vf_dequeue_output_frame(c->last);
|
||||
}
|
||||
|
||||
// Undo the previous vf_read_output_frame().
|
||||
void vf_unread_output_frame(struct vf_chain *c, struct mp_image *img)
|
||||
{
|
||||
struct vf_instance *vf = c->last;
|
||||
MP_TARRAY_INSERT_AT(vf, vf->out_queued, vf->num_out_queued, 0, img);
|
||||
}
|
||||
|
||||
// Some filters (vf_vapoursynth) filter on separate threads, and may need new
|
||||
// input from the decoder, even though the core does not need a new output image
|
||||
// yet (this is required to get proper pipelining in the filter). If the filter
|
||||
|
||||
@@ -157,6 +157,7 @@ int vf_filter_frame(struct vf_chain *c, struct mp_image *img);
|
||||
int vf_output_frame(struct vf_chain *c, bool eof);
|
||||
int vf_needs_input(struct vf_chain *c);
|
||||
struct mp_image *vf_read_output_frame(struct vf_chain *c);
|
||||
void vf_unread_output_frame(struct vf_chain *c, struct mp_image *img);
|
||||
void vf_seek_reset(struct vf_chain *c);
|
||||
struct vf_instance *vf_append_filter(struct vf_chain *c, const char *name,
|
||||
char **args);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "common/msg.h"
|
||||
@@ -46,11 +47,17 @@ static int lavfi_reconfig(struct vf_instance *vf,
|
||||
struct vf_priv_s *p = vf_lw_old_priv(vf);
|
||||
if (p->angle == 4) { // "auto"
|
||||
int r = in->rotate;
|
||||
if (r < 0 || r >= 360 || (r % 90) != 0) {
|
||||
if (r < 0 || r >= 360) {
|
||||
MP_ERR(vf, "Can't apply rotation of %d degrees.\n", r);
|
||||
return -1;
|
||||
}
|
||||
vf_lw_update_graph(vf, NULL, "%s", rot[(r / 90) % 360]);
|
||||
if (r % 90) {
|
||||
double a = r / 180.0 * M_PI;
|
||||
vf_lw_update_graph(vf, NULL, "rotate=%f:ow=rotw(%f):oh=roth(%f)",
|
||||
a, a, a);
|
||||
} else {
|
||||
vf_lw_update_graph(vf, NULL, "%s", rot[(r / 90) % 360]);
|
||||
}
|
||||
out->rotate = 0;
|
||||
}
|
||||
return 0;
|
||||
|
||||
+7
-3
@@ -27,10 +27,12 @@
|
||||
#include "sub/osd.h"
|
||||
|
||||
static void aspect_calc_panscan(struct mp_vo_opts *opts,
|
||||
int w, int h, int d_w, int d_h, bool unscaled,
|
||||
int w, int h, int d_w, int d_h, int unscaled,
|
||||
int window_w, int window_h, double monitor_par,
|
||||
int *out_w, int *out_h)
|
||||
{
|
||||
w *= monitor_par;
|
||||
|
||||
int fwidth = window_w;
|
||||
int fheight = (float)window_w / d_w * d_h / monitor_par;
|
||||
if (fheight > window_h || fheight < h) {
|
||||
@@ -51,9 +53,11 @@ static void aspect_calc_panscan(struct mp_vo_opts *opts,
|
||||
}
|
||||
|
||||
if (unscaled) {
|
||||
fwidth = w * monitor_par;
|
||||
fheight = h;
|
||||
vo_panscan_area = 0;
|
||||
if (unscaled != 2 || (w <= window_w && h <= window_h)) {
|
||||
fwidth = w;
|
||||
fheight = h;
|
||||
}
|
||||
}
|
||||
|
||||
*out_w = fwidth + vo_panscan_area * opts->panscan * f_w;
|
||||
|
||||
@@ -32,6 +32,7 @@ enum {
|
||||
VOFLAG_GL_DEBUG = 1 << 2, // Hint to request debug OpenGL context
|
||||
VOFLAG_ALPHA = 1 << 3, // Hint to request alpha framebuffer
|
||||
VOFLAG_SW = 1 << 4, // Hint to accept a software GL renderer
|
||||
VOFLAG_ANGLE_DCOMP = 1 << 5, // Whether DirectComposition is allowed
|
||||
};
|
||||
|
||||
extern const int mpgl_preferred_gl_versions[];
|
||||
|
||||
@@ -306,8 +306,13 @@ static int angle_init(struct MPGLContext *ctx, int flags)
|
||||
|
||||
// EGL_DIRECT_COMPOSITION_ANGLE enables the use of flip-mode present, which
|
||||
// avoids a copy of the video image and lowers vsync jitter, though the
|
||||
// extension is only present on Windows 8 and up.
|
||||
if (strstr(exts, "EGL_ANGLE_direct_composition")) {
|
||||
// extension is only present on Windows 8 and up, and might have subpar
|
||||
// behavior with some drivers (Intel? symptom - whole desktop is black for
|
||||
// some seconds after spending some minutes in fullscreen and then leaving
|
||||
// fullscreen).
|
||||
if ((flags & VOFLAG_ANGLE_DCOMP) &&
|
||||
strstr(exts, "EGL_ANGLE_direct_composition"))
|
||||
{
|
||||
MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len,
|
||||
EGL_DIRECT_COMPOSITION_ANGLE);
|
||||
MP_TARRAY_APPEND(NULL, window_attribs, window_attribs_len, EGL_TRUE);
|
||||
|
||||
+36
-15
@@ -297,12 +297,17 @@ autoprobe:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void terminate_vo(void *p)
|
||||
{
|
||||
struct vo *vo = p;
|
||||
struct vo_internal *in = vo->in;
|
||||
in->terminate = true;
|
||||
}
|
||||
|
||||
void vo_destroy(struct vo *vo)
|
||||
{
|
||||
struct vo_internal *in = vo->in;
|
||||
mp_dispatch_lock(in->dispatch);
|
||||
vo->in->terminate = true;
|
||||
mp_dispatch_unlock(in->dispatch);
|
||||
mp_dispatch_run(in->dispatch, terminate_vo, vo);
|
||||
pthread_join(vo->in->thread, NULL);
|
||||
dealloc_vo(vo);
|
||||
}
|
||||
@@ -339,9 +344,7 @@ static void check_estimated_display_fps(struct vo *vo)
|
||||
struct vo_internal *in = vo->in;
|
||||
|
||||
bool use_estimated = false;
|
||||
if (in->num_total_vsync_samples >= MAX_VSYNC_SAMPLES * 2 &&
|
||||
fabs((in->nominal_vsync_interval - in->estimated_vsync_interval))
|
||||
>= 0.01 * in->nominal_vsync_interval &&
|
||||
if (in->num_total_vsync_samples >= MAX_VSYNC_SAMPLES / 2 &&
|
||||
in->estimated_vsync_interval <= 1e6 / 20.0 &&
|
||||
in->estimated_vsync_interval >= 1e6 / 99.0)
|
||||
{
|
||||
@@ -358,12 +361,11 @@ static void check_estimated_display_fps(struct vo *vo)
|
||||
}
|
||||
if (use_estimated == (in->vsync_interval == in->nominal_vsync_interval)) {
|
||||
if (use_estimated) {
|
||||
MP_WARN(vo, "Reported display FPS seems incorrect.\n"
|
||||
"Assuming a value closer to %.3f Hz.\n",
|
||||
1e6 / in->estimated_vsync_interval);
|
||||
MP_VERBOSE(vo, "adjusting display FPS to a value closer to %.3f Hz\n",
|
||||
1e6 / in->estimated_vsync_interval);
|
||||
} else {
|
||||
MP_WARN(vo, "Switching back to assuming %.3f Hz.\n",
|
||||
1e6 / in->nominal_vsync_interval);
|
||||
MP_VERBOSE(vo, "switching back to assuming display fps = %.3f Hz\n",
|
||||
1e6 / in->nominal_vsync_interval);
|
||||
}
|
||||
}
|
||||
in->vsync_interval = use_estimated ? (int64_t)in->estimated_vsync_interval
|
||||
@@ -537,20 +539,39 @@ static void run_control(void *p)
|
||||
{
|
||||
void **pp = p;
|
||||
struct vo *vo = pp[0];
|
||||
uint32_t request = *(int *)pp[1];
|
||||
int request = (intptr_t)pp[1];
|
||||
void *data = pp[2];
|
||||
int ret = vo->driver->control(vo, request, data);
|
||||
*(int *)pp[3] = ret;
|
||||
if (pp[3])
|
||||
*(int *)pp[3] = ret;
|
||||
}
|
||||
|
||||
int vo_control(struct vo *vo, uint32_t request, void *data)
|
||||
int vo_control(struct vo *vo, int request, void *data)
|
||||
{
|
||||
int ret;
|
||||
void *p[] = {vo, &request, data, &ret};
|
||||
void *p[] = {vo, (void *)(intptr_t)request, data, &ret};
|
||||
mp_dispatch_run(vo->in->dispatch, run_control, p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Run vo_control() without waiting for a reply.
|
||||
// (Only works for some VOCTRLs.)
|
||||
void vo_control_async(struct vo *vo, int request, void *data)
|
||||
{
|
||||
void *p[4] = {vo, (void *)(intptr_t)request, NULL, NULL};
|
||||
void **d = talloc_memdup(NULL, p, sizeof(p));
|
||||
|
||||
switch (request) {
|
||||
case VOCTRL_UPDATE_PLAYBACK_STATE:
|
||||
d[2] = ta_xdup_ptrtype(d, (struct voctrl_playback_state *)data);
|
||||
break;
|
||||
default:
|
||||
abort(); // requires explicit support
|
||||
}
|
||||
|
||||
mp_dispatch_enqueue_autofree(vo->in->dispatch, run_control, d);
|
||||
}
|
||||
|
||||
// must be called locked
|
||||
static void forget_frames(struct vo *vo)
|
||||
{
|
||||
|
||||
+2
-1
@@ -341,7 +341,8 @@ struct mpv_global;
|
||||
struct vo *init_best_video_out(struct mpv_global *global, struct vo_extra *ex);
|
||||
int vo_reconfig(struct vo *vo, struct mp_image_params *p);
|
||||
|
||||
int vo_control(struct vo *vo, uint32_t request, void *data);
|
||||
int vo_control(struct vo *vo, int request, void *data);
|
||||
void vo_control_async(struct vo *vo, int request, void *data);
|
||||
bool vo_is_ready_for_frame(struct vo *vo, int64_t next_pts);
|
||||
void vo_queue_frame(struct vo *vo, struct vo_frame *frame);
|
||||
void vo_wait_frame(struct vo *vo);
|
||||
|
||||
@@ -70,6 +70,7 @@ struct gl_priv {
|
||||
int allow_sw;
|
||||
int swap_interval;
|
||||
int dwm_flush;
|
||||
int allow_direct_composition;
|
||||
int opt_vsync_fences;
|
||||
|
||||
char *backend;
|
||||
@@ -409,6 +410,9 @@ static int preinit(struct vo *vo)
|
||||
if (p->allow_sw)
|
||||
vo_flags |= VOFLAG_SW;
|
||||
|
||||
if (p->allow_direct_composition)
|
||||
vo_flags |= VOFLAG_ANGLE_DCOMP;
|
||||
|
||||
p->glctx = mpgl_init(vo, p->backend, vo_flags);
|
||||
if (!p->glctx)
|
||||
goto err_out;
|
||||
@@ -460,6 +464,7 @@ static const struct m_option options[] = {
|
||||
OPT_INT("swapinterval", swap_interval, 0, OPTDEF_INT(1)),
|
||||
OPT_CHOICE("dwmflush", dwm_flush, 0,
|
||||
({"no", -1}, {"auto", 0}, {"windowed", 1}, {"yes", 2})),
|
||||
OPT_FLAG("dcomposition", allow_direct_composition, 0, OPTDEF_INT(1)),
|
||||
OPT_FLAG("debug", use_gl_debug, 0),
|
||||
OPT_STRING_VALIDATE("backend", backend, 0, mpgl_validate_backend_opt),
|
||||
OPT_FLAG("sw", allow_sw, 0),
|
||||
|
||||
+117
-35
@@ -48,8 +48,6 @@
|
||||
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
|
||||
#define HINST_THISCOMPONENT ((HINSTANCE)&__ImageBase)
|
||||
|
||||
static const wchar_t classname[] = L"mpv";
|
||||
|
||||
static __thread struct vo_w32_state *w32_thread_context;
|
||||
|
||||
struct vo_w32_state {
|
||||
@@ -64,6 +62,8 @@ struct vo_w32_state {
|
||||
|
||||
HWND window;
|
||||
HWND parent; // 0 normally, set in embedding mode
|
||||
HHOOK parent_win_hook;
|
||||
HWINEVENTHOOK parent_evt_hook;
|
||||
|
||||
HMONITOR monitor; // Handle of the current screen
|
||||
struct mp_rect screenrc; // Size and virtual position of the current screen
|
||||
@@ -747,11 +747,19 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
// is that will make us lose WM_USER wakeups.
|
||||
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
|
||||
return 0;
|
||||
case WM_NCDESTROY: // Sometimes only WM_NCDESTROY is received in --wid mode
|
||||
case WM_DESTROY:
|
||||
if (w32->destroyed)
|
||||
break;
|
||||
// If terminate is not set, something else destroyed the window. This
|
||||
// can also happen in --wid mode when the parent window is destroyed.
|
||||
if (!w32->terminate)
|
||||
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
|
||||
RevokeDragDrop(w32->window);
|
||||
w32->destroyed = true;
|
||||
w32->window = NULL;
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
break;
|
||||
case WM_SYSCOMMAND:
|
||||
switch (wParam) {
|
||||
case SC_SCREENSAVE:
|
||||
@@ -901,27 +909,109 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
return DefWindowProcW(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
static pthread_once_t window_class_init_once = PTHREAD_ONCE_INIT;
|
||||
static ATOM window_class;
|
||||
static void register_window_class(void)
|
||||
{
|
||||
window_class = RegisterClassExW(&(WNDCLASSEXW) {
|
||||
.cbSize = sizeof(WNDCLASSEXW),
|
||||
.style = CS_HREDRAW | CS_VREDRAW,
|
||||
.lpfnWndProc = WndProc,
|
||||
.hInstance = HINST_THISCOMPONENT,
|
||||
.hIcon = LoadIconW(HINST_THISCOMPONENT, L"IDI_ICON1"),
|
||||
.hCursor = LoadCursor(NULL, IDC_ARROW),
|
||||
.lpszClassName = L"mpv",
|
||||
});
|
||||
}
|
||||
|
||||
static ATOM get_window_class(void)
|
||||
{
|
||||
pthread_once(&window_class_init_once, register_window_class);
|
||||
return window_class;
|
||||
}
|
||||
|
||||
static void resize_child_win(HWND parent)
|
||||
{
|
||||
// Check if an mpv window is a child of this window. This will not
|
||||
// necessarily be the case because the hook functions will run for all
|
||||
// windows on the parent window's thread.
|
||||
ATOM cls = get_window_class();
|
||||
HWND child = FindWindowExW(parent, NULL, (LPWSTR)MAKEINTATOM(cls), NULL);
|
||||
if (!child)
|
||||
return;
|
||||
// Make sure the window was created by this instance
|
||||
if (GetWindowLongPtrW(child, GWLP_HINSTANCE) != (LONG_PTR)HINST_THISCOMPONENT)
|
||||
return;
|
||||
|
||||
// Resize the mpv window to match its parent window's size
|
||||
RECT rm, rp;
|
||||
if (!GetClientRect(child, &rm))
|
||||
return;
|
||||
if (!GetClientRect(parent, &rp))
|
||||
return;
|
||||
if (EqualRect(&rm, &rp))
|
||||
return;
|
||||
SetWindowPos(child, NULL, 0, 0, rp.right, rp.bottom, SWP_ASYNCWINDOWPOS |
|
||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK parent_win_hook(int nCode, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (nCode != HC_ACTION)
|
||||
goto done;
|
||||
CWPSTRUCT *cwp = (CWPSTRUCT*)lParam;
|
||||
if (cwp->message != WM_WINDOWPOSCHANGED)
|
||||
goto done;
|
||||
resize_child_win(cwp->hwnd);
|
||||
done:
|
||||
return CallNextHookEx(NULL, nCode, wParam, lParam);
|
||||
}
|
||||
|
||||
static void CALLBACK parent_evt_hook(HWINEVENTHOOK hWinEventHook, DWORD event,
|
||||
HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread,
|
||||
DWORD dwmsEventTime)
|
||||
{
|
||||
if (event != EVENT_OBJECT_LOCATIONCHANGE)
|
||||
return;
|
||||
if (!hwnd || idObject != OBJID_WINDOW || idChild != CHILDID_SELF)
|
||||
return;
|
||||
resize_child_win(hwnd);
|
||||
}
|
||||
|
||||
static void install_parent_hook(struct vo_w32_state *w32)
|
||||
{
|
||||
DWORD pid;
|
||||
DWORD tid = GetWindowThreadProcessId(w32->parent, &pid);
|
||||
|
||||
// If the parent lives inside the current process, install a Windows hook
|
||||
if (pid == GetCurrentProcessId()) {
|
||||
w32->parent_win_hook = SetWindowsHookExW(WH_CALLWNDPROC,
|
||||
parent_win_hook, NULL, tid);
|
||||
} else {
|
||||
// Otherwise, use a WinEvent hook. These don't seem to be as smooth as
|
||||
// Windows hooks, but they can be delivered across process boundaries.
|
||||
w32->parent_evt_hook = SetWinEventHook(
|
||||
EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE,
|
||||
NULL, parent_evt_hook, pid, tid, WINEVENT_OUTOFCONTEXT);
|
||||
}
|
||||
}
|
||||
|
||||
static void remove_parent_hook(struct vo_w32_state *w32)
|
||||
{
|
||||
if (w32->parent_win_hook)
|
||||
UnhookWindowsHookEx(w32->parent_win_hook);
|
||||
if (w32->parent_evt_hook)
|
||||
UnhookWinEvent(w32->parent_evt_hook);
|
||||
}
|
||||
|
||||
// Dispatch incoming window events and handle them.
|
||||
// This returns only when the thread is asked to terminate.
|
||||
static void run_message_loop(struct vo_w32_state *w32)
|
||||
{
|
||||
MSG msg;
|
||||
while (GetMessageW(&msg, 0, 0, 0) > 0) {
|
||||
while (GetMessageW(&msg, 0, 0, 0) > 0)
|
||||
DispatchMessageW(&msg);
|
||||
|
||||
if (w32->parent) {
|
||||
RECT r, rp;
|
||||
BOOL res = GetClientRect(w32->window, &r);
|
||||
res = res && GetClientRect(w32->parent, &rp);
|
||||
if (res && (r.right != rp.right || r.bottom != rp.bottom))
|
||||
MoveWindow(w32->window, 0, 0, rp.right, rp.bottom, FALSE);
|
||||
|
||||
// Window has probably been closed, e.g. due to parent program crash
|
||||
if (!IsWindow(w32->parent))
|
||||
mp_input_put_key(w32->input_ctx, MP_KEY_CLOSE_WIN);
|
||||
}
|
||||
}
|
||||
|
||||
// Even if the message loop somehow exits, we still have to respond to
|
||||
// external requests until termination is requested.
|
||||
while (!w32->terminate)
|
||||
@@ -1222,33 +1312,26 @@ static void *gui_thread(void *ptr)
|
||||
|
||||
thread_disable_ime();
|
||||
|
||||
WNDCLASSEXW wcex = {
|
||||
.cbSize = sizeof wcex,
|
||||
.style = CS_HREDRAW | CS_VREDRAW,
|
||||
.lpfnWndProc = WndProc,
|
||||
.hInstance = HINST_THISCOMPONENT,
|
||||
.hIcon = LoadIconW(HINST_THISCOMPONENT, L"IDI_ICON1"),
|
||||
.hCursor = LoadCursor(NULL, IDC_ARROW),
|
||||
.lpszClassName = classname,
|
||||
};
|
||||
RegisterClassExW(&wcex);
|
||||
|
||||
w32_thread_context = w32;
|
||||
|
||||
if (w32->opts->WinID >= 0)
|
||||
w32->parent = (HWND)(intptr_t)(w32->opts->WinID);
|
||||
|
||||
ATOM cls = get_window_class();
|
||||
if (w32->parent) {
|
||||
RECT r;
|
||||
GetClientRect(w32->parent, &r);
|
||||
w32->window = CreateWindowExW(WS_EX_NOPARENTNOTIFY, classname,
|
||||
classname,
|
||||
w32->window = CreateWindowExW(WS_EX_NOPARENTNOTIFY,
|
||||
(LPWSTR)MAKEINTATOM(cls), L"mpv",
|
||||
WS_CHILD | WS_VISIBLE,
|
||||
0, 0, r.right, r.bottom,
|
||||
w32->parent, 0, HINST_THISCOMPONENT, NULL);
|
||||
|
||||
// Install a hook to get notifications when the parent changes size
|
||||
if (w32->window)
|
||||
install_parent_hook(w32);
|
||||
} else {
|
||||
w32->window = CreateWindowExW(0, classname,
|
||||
classname,
|
||||
w32->window = CreateWindowExW(0, (LPWSTR)MAKEINTATOM(cls), L"mpv",
|
||||
update_style(w32, 0),
|
||||
CW_USEDEFAULT, SW_HIDE, 100, 100,
|
||||
0, 0, HINST_THISCOMPONENT, NULL);
|
||||
@@ -1323,10 +1406,9 @@ done:
|
||||
|
||||
MP_VERBOSE(w32, "uninit\n");
|
||||
|
||||
if (w32->window) {
|
||||
RevokeDragDrop(w32->window);
|
||||
remove_parent_hook(w32);
|
||||
if (w32->window && !w32->destroyed)
|
||||
DestroyWindow(w32->window);
|
||||
}
|
||||
if (w32->taskbar_list)
|
||||
ITaskbarList2_Release(w32->taskbar_list);
|
||||
if (w32->taskbar_list3)
|
||||
|
||||
+11
-3
@@ -1748,17 +1748,25 @@ static void vo_x11_fullscreen(struct vo *vo)
|
||||
x11->nofsrc = x11->winrc;
|
||||
}
|
||||
|
||||
struct mp_rect rc = x11->nofsrc;
|
||||
|
||||
if (x11->wm_type & vo_wm_FULLSCREEN) {
|
||||
x11_set_ewmh_state(x11, "_NET_WM_STATE_FULLSCREEN", x11->fs);
|
||||
if (!x11->fs && (x11->pos_changed_during_fs ||
|
||||
x11->size_changed_during_fs))
|
||||
{
|
||||
if (x11->screenrc.x0 == rc.x0 && x11->screenrc.x1 == rc.x1 &&
|
||||
x11->screenrc.y0 == rc.y0 && x11->screenrc.y1 == rc.y1)
|
||||
{
|
||||
// Workaround for some WMs switching back to FS in this case.
|
||||
MP_VERBOSE(x11, "avoiding triggering old-style fullscreen\n");
|
||||
rc.x1 -= 1;
|
||||
rc.y1 -= 1;
|
||||
}
|
||||
vo_x11_move_resize(vo, x11->pos_changed_during_fs,
|
||||
x11->size_changed_during_fs,
|
||||
x11->nofsrc);
|
||||
x11->size_changed_during_fs, rc);
|
||||
}
|
||||
} else {
|
||||
struct mp_rect rc = x11->nofsrc;
|
||||
if (x11->fs) {
|
||||
vo_x11_update_screeninfo(vo);
|
||||
rc = x11->screenrc;
|
||||
|
||||
@@ -508,7 +508,7 @@ FFmpeg/Libav libraries. You need at least {0}. Aborting.".format(libav_versions_
|
||||
}, {
|
||||
'name': 'avutil-mastering-metadata',
|
||||
'desc': 'libavutil mastering display metadata struct',
|
||||
'func': check_statement('libavutil/mastering_display_metadata.h',
|
||||
'func': check_statement('libavutil/frame.h',
|
||||
'AV_FRAME_DATA_MASTERING_DISPLAY_METADATA',
|
||||
use='libav'),
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user