2013-10-29 21:38:29 +00:00
|
|
|
/*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2013-10-29 21:38:29 +00:00
|
|
|
*
|
player: change license of most core files to LGPL
These files have all in common that they were fully or mostly taken from
mplayer.c. (mplayer.c was a huge file that contains almost all of the
playback core, until it was split into multiple parts.) This was
probably the hardest part to relicense, because so much code was moved
around all the time.
player/audio.c still does not compile. We'll have to redo audio
filtering. Once that is done, we can probably actually provide an
actual LGPL configure switch.
Here is a relatively detailed list of potential issues:
8d190244: author did not reply, parts were made GPL-only in a previous
commit.
7882ea9b: author could not be reached, but the code is gone. wscript
still has --datadir switch, but I don't think this is relevant to
copyright.
f197efd5: unclear origin, but I consider the code gone anyway (replaced
with generic OSD mechanisms).
8337d9c2: author did not reply, but only the option still exists (under
a different name), other code was removed.
d8fd7131: did not reply. Disabled in a previous commit.
05258251: same author as above. Both fields actually seem to have
vanished (even when tracking renames), so no action taken.
d459e644, 268b2c1a: author did not reply, but we reuse only the options
(with different names and slightly or fully different semantics, and
completely different implementations), so I don't think this is relevant
for copyright.
09e742fe, 17c39c4e: same as above.
e8a173de, bff4b3ee: author could not be reached. The commands were
reworked to properties, and the code outside of the TV code were moved
back to the TV code. So I don't think copyright applies to the current
command.c parts (mp_property_tv_color, mp_property_tv_freq,
mp_property_tv_scan). The TV parts remain GPL.
0810e427: could not be reached. Disabled in a previous commit.
43744a2d: unknown author, but this was replaced by dynamic alloc (if the
change is even copyrightable).
116ca0c7: unknown author; reasoning see input.c relicensing commit.
e7e4d1d8: these semantics still exist, but as generic code, and this
code was fully removed.
f1175cd9: the author of the cited patch is unknown, and upon inspection
it turns out that I was only using the idea to pause the player on EOF,
so I claim it's not copyright relevant.
25affdcc: author could not be reached (yet) - but it's only a function
rename, not copyrightable.
5728504c was committed by Arpi (who agreed), but hints that it might be
by a different author. In fact it seems to be mostly this patch:
http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2001-November/002041.html
The author did not respond, but it all seems to have been removed later.
It's a terrible mess though. Arpi reverted the A-V sync code at first,
but left the RTC code for a while. The following commits remove these
changes 100%: 14b35442, 7181a091, 31482783, 614f8475, df58e822.
cehoyos did explicitly not agree to LGPL, but was involved in the
following changes:
c99d8fc8: applied a patch and didn't modify it, the original author
agreed.
40ac0d31: author could not be reached, but all code is gone anyway. The
"af" command has a similar function, but works completely different and
actually reuses a mechanism older than this patch.
54350436: applied a patch, but didn't modify it, except for adding a
German translation, which was removed later.
a2dda036: same situation as above
240b743e: this was made GPL-only in a previous commit
7b25afd7: same as above (for now)
kirijua could not be reached, but was a regular patch contributor:
c2c997fd: video equalizer code move; probably not copyrightable. Is GPL
due to Nick anyway.
be54f481: technically, this became the audio track property later. But
all what is left is the fact that you pass a track ID to it, so consider
the original coypright non-relevant.
2f376d1b: this was rewritten in b7052b43, but for now we can afford to
be careful, so this was marked as GPL only in a previous commit.
43844d09: remaining parts in main.c were reverted in a previous commit.
anders has mostly disagreed with the LGPL relicensing. Does not want
libaf to become LGPL, but made some concessions. In particular, he
granted us permission to relicense 4943e9c52c and 242aa6ebd4. We also
consider some of his changes remaining in mpv not relevant for copyright
(such as 735de602 - we won't remove the this option completely). We will
completely remove his other contributions, including the entire audio
filter chain. For now, this stuff is marked as GPL only. The remaining
question is how much code in player/audio.c (based on the former
mplayer.c and dec_audio.c) is under his copyright. I made claims about
this in a previous commit.
Nick(ols) Kurshev, svn username "nick" and "nickols_k", could not be
reached. He had a lot of changes in early MPlayer. It seems all of that
was removed, at least in mpv. His main work, like VIDIX or libswscale
work, does not exist in mpv anymore, but the changes to mplayer.c and
other core parts still deserve attention:
a4119f6b, fb927549, ad3529b8, e11b23dc, 5f2178be, 93c371d5: removed in
b43d67e0, d1628d12, 24ed01fe, df58e822.
0a83c6ec, 104c125e, 4e067f62, aec5dcc8, b587a3d6, f3de6e6b: DR, VAA, and
"tune" stuff was fully removed later on or replaced with other
mechanisms.
340183b0: screenshots were redone later (the VOCTRL was even removed,
with an independent implementation using the same VOCTRL a few years
later), so not relevant anymore. Basically only the 's' shortcut remains
(but not its implementation).
92c5c274, bffd4007, 555c6766: for now marked as GPL only in a previous
commit.
Might contain some trace amounts of "michael"'s copyright, who agreed to
LGPL only once the core is relicensed. This will still be respected, but
I don't think it matters at this in this case. (Some code touched by him
was merged into mplayer.c, and then disappeared after heavy
refactoring.)
I tried to be as careful and as complete as possible. It can't be
excluded that amends to this will be made later.
This does not make the player LGPL yet.
2017-06-23 13:53:41 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2013-10-29 21:38:29 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2013-10-29 21:38:29 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
player: change license of most core files to LGPL
These files have all in common that they were fully or mostly taken from
mplayer.c. (mplayer.c was a huge file that contains almost all of the
playback core, until it was split into multiple parts.) This was
probably the hardest part to relicense, because so much code was moved
around all the time.
player/audio.c still does not compile. We'll have to redo audio
filtering. Once that is done, we can probably actually provide an
actual LGPL configure switch.
Here is a relatively detailed list of potential issues:
8d190244: author did not reply, parts were made GPL-only in a previous
commit.
7882ea9b: author could not be reached, but the code is gone. wscript
still has --datadir switch, but I don't think this is relevant to
copyright.
f197efd5: unclear origin, but I consider the code gone anyway (replaced
with generic OSD mechanisms).
8337d9c2: author did not reply, but only the option still exists (under
a different name), other code was removed.
d8fd7131: did not reply. Disabled in a previous commit.
05258251: same author as above. Both fields actually seem to have
vanished (even when tracking renames), so no action taken.
d459e644, 268b2c1a: author did not reply, but we reuse only the options
(with different names and slightly or fully different semantics, and
completely different implementations), so I don't think this is relevant
for copyright.
09e742fe, 17c39c4e: same as above.
e8a173de, bff4b3ee: author could not be reached. The commands were
reworked to properties, and the code outside of the TV code were moved
back to the TV code. So I don't think copyright applies to the current
command.c parts (mp_property_tv_color, mp_property_tv_freq,
mp_property_tv_scan). The TV parts remain GPL.
0810e427: could not be reached. Disabled in a previous commit.
43744a2d: unknown author, but this was replaced by dynamic alloc (if the
change is even copyrightable).
116ca0c7: unknown author; reasoning see input.c relicensing commit.
e7e4d1d8: these semantics still exist, but as generic code, and this
code was fully removed.
f1175cd9: the author of the cited patch is unknown, and upon inspection
it turns out that I was only using the idea to pause the player on EOF,
so I claim it's not copyright relevant.
25affdcc: author could not be reached (yet) - but it's only a function
rename, not copyrightable.
5728504c was committed by Arpi (who agreed), but hints that it might be
by a different author. In fact it seems to be mostly this patch:
http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2001-November/002041.html
The author did not respond, but it all seems to have been removed later.
It's a terrible mess though. Arpi reverted the A-V sync code at first,
but left the RTC code for a while. The following commits remove these
changes 100%: 14b35442, 7181a091, 31482783, 614f8475, df58e822.
cehoyos did explicitly not agree to LGPL, but was involved in the
following changes:
c99d8fc8: applied a patch and didn't modify it, the original author
agreed.
40ac0d31: author could not be reached, but all code is gone anyway. The
"af" command has a similar function, but works completely different and
actually reuses a mechanism older than this patch.
54350436: applied a patch, but didn't modify it, except for adding a
German translation, which was removed later.
a2dda036: same situation as above
240b743e: this was made GPL-only in a previous commit
7b25afd7: same as above (for now)
kirijua could not be reached, but was a regular patch contributor:
c2c997fd: video equalizer code move; probably not copyrightable. Is GPL
due to Nick anyway.
be54f481: technically, this became the audio track property later. But
all what is left is the fact that you pass a track ID to it, so consider
the original coypright non-relevant.
2f376d1b: this was rewritten in b7052b43, but for now we can afford to
be careful, so this was marked as GPL only in a previous commit.
43844d09: remaining parts in main.c were reverted in a previous commit.
anders has mostly disagreed with the LGPL relicensing. Does not want
libaf to become LGPL, but made some concessions. In particular, he
granted us permission to relicense 4943e9c52c and 242aa6ebd4. We also
consider some of his changes remaining in mpv not relevant for copyright
(such as 735de602 - we won't remove the this option completely). We will
completely remove his other contributions, including the entire audio
filter chain. For now, this stuff is marked as GPL only. The remaining
question is how much code in player/audio.c (based on the former
mplayer.c and dec_audio.c) is under his copyright. I made claims about
this in a previous commit.
Nick(ols) Kurshev, svn username "nick" and "nickols_k", could not be
reached. He had a lot of changes in early MPlayer. It seems all of that
was removed, at least in mpv. His main work, like VIDIX or libswscale
work, does not exist in mpv anymore, but the changes to mplayer.c and
other core parts still deserve attention:
a4119f6b, fb927549, ad3529b8, e11b23dc, 5f2178be, 93c371d5: removed in
b43d67e0, d1628d12, 24ed01fe, df58e822.
0a83c6ec, 104c125e, 4e067f62, aec5dcc8, b587a3d6, f3de6e6b: DR, VAA, and
"tune" stuff was fully removed later on or replaced with other
mechanisms.
340183b0: screenshots were redone later (the VOCTRL was even removed,
with an independent implementation using the same VOCTRL a few years
later), so not relevant anymore. Basically only the 's' shortcut remains
(but not its implementation).
92c5c274, bffd4007, 555c6766: for now marked as GPL only in a previous
commit.
Might contain some trace amounts of "michael"'s copyright, who agreed to
LGPL only once the core is relicensed. This will still be respected, but
I don't think it matters at this in this case. (Some code touched by him
was merged into mplayer.c, and then disappeared after heavy
refactoring.)
I tried to be as careful and as complete as possible. It can't be
excluded that amends to this will be made later.
This does not make the player LGPL yet.
2017-06-23 13:53:41 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2013-10-29 21:38:29 +00:00
|
|
|
*
|
player: change license of most core files to LGPL
These files have all in common that they were fully or mostly taken from
mplayer.c. (mplayer.c was a huge file that contains almost all of the
playback core, until it was split into multiple parts.) This was
probably the hardest part to relicense, because so much code was moved
around all the time.
player/audio.c still does not compile. We'll have to redo audio
filtering. Once that is done, we can probably actually provide an
actual LGPL configure switch.
Here is a relatively detailed list of potential issues:
8d190244: author did not reply, parts were made GPL-only in a previous
commit.
7882ea9b: author could not be reached, but the code is gone. wscript
still has --datadir switch, but I don't think this is relevant to
copyright.
f197efd5: unclear origin, but I consider the code gone anyway (replaced
with generic OSD mechanisms).
8337d9c2: author did not reply, but only the option still exists (under
a different name), other code was removed.
d8fd7131: did not reply. Disabled in a previous commit.
05258251: same author as above. Both fields actually seem to have
vanished (even when tracking renames), so no action taken.
d459e644, 268b2c1a: author did not reply, but we reuse only the options
(with different names and slightly or fully different semantics, and
completely different implementations), so I don't think this is relevant
for copyright.
09e742fe, 17c39c4e: same as above.
e8a173de, bff4b3ee: author could not be reached. The commands were
reworked to properties, and the code outside of the TV code were moved
back to the TV code. So I don't think copyright applies to the current
command.c parts (mp_property_tv_color, mp_property_tv_freq,
mp_property_tv_scan). The TV parts remain GPL.
0810e427: could not be reached. Disabled in a previous commit.
43744a2d: unknown author, but this was replaced by dynamic alloc (if the
change is even copyrightable).
116ca0c7: unknown author; reasoning see input.c relicensing commit.
e7e4d1d8: these semantics still exist, but as generic code, and this
code was fully removed.
f1175cd9: the author of the cited patch is unknown, and upon inspection
it turns out that I was only using the idea to pause the player on EOF,
so I claim it's not copyright relevant.
25affdcc: author could not be reached (yet) - but it's only a function
rename, not copyrightable.
5728504c was committed by Arpi (who agreed), but hints that it might be
by a different author. In fact it seems to be mostly this patch:
http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2001-November/002041.html
The author did not respond, but it all seems to have been removed later.
It's a terrible mess though. Arpi reverted the A-V sync code at first,
but left the RTC code for a while. The following commits remove these
changes 100%: 14b35442, 7181a091, 31482783, 614f8475, df58e822.
cehoyos did explicitly not agree to LGPL, but was involved in the
following changes:
c99d8fc8: applied a patch and didn't modify it, the original author
agreed.
40ac0d31: author could not be reached, but all code is gone anyway. The
"af" command has a similar function, but works completely different and
actually reuses a mechanism older than this patch.
54350436: applied a patch, but didn't modify it, except for adding a
German translation, which was removed later.
a2dda036: same situation as above
240b743e: this was made GPL-only in a previous commit
7b25afd7: same as above (for now)
kirijua could not be reached, but was a regular patch contributor:
c2c997fd: video equalizer code move; probably not copyrightable. Is GPL
due to Nick anyway.
be54f481: technically, this became the audio track property later. But
all what is left is the fact that you pass a track ID to it, so consider
the original coypright non-relevant.
2f376d1b: this was rewritten in b7052b43, but for now we can afford to
be careful, so this was marked as GPL only in a previous commit.
43844d09: remaining parts in main.c were reverted in a previous commit.
anders has mostly disagreed with the LGPL relicensing. Does not want
libaf to become LGPL, but made some concessions. In particular, he
granted us permission to relicense 4943e9c52c and 242aa6ebd4. We also
consider some of his changes remaining in mpv not relevant for copyright
(such as 735de602 - we won't remove the this option completely). We will
completely remove his other contributions, including the entire audio
filter chain. For now, this stuff is marked as GPL only. The remaining
question is how much code in player/audio.c (based on the former
mplayer.c and dec_audio.c) is under his copyright. I made claims about
this in a previous commit.
Nick(ols) Kurshev, svn username "nick" and "nickols_k", could not be
reached. He had a lot of changes in early MPlayer. It seems all of that
was removed, at least in mpv. His main work, like VIDIX or libswscale
work, does not exist in mpv anymore, but the changes to mplayer.c and
other core parts still deserve attention:
a4119f6b, fb927549, ad3529b8, e11b23dc, 5f2178be, 93c371d5: removed in
b43d67e0, d1628d12, 24ed01fe, df58e822.
0a83c6ec, 104c125e, 4e067f62, aec5dcc8, b587a3d6, f3de6e6b: DR, VAA, and
"tune" stuff was fully removed later on or replaced with other
mechanisms.
340183b0: screenshots were redone later (the VOCTRL was even removed,
with an independent implementation using the same VOCTRL a few years
later), so not relevant anymore. Basically only the 's' shortcut remains
(but not its implementation).
92c5c274, bffd4007, 555c6766: for now marked as GPL only in a previous
commit.
Might contain some trace amounts of "michael"'s copyright, who agreed to
LGPL only once the core is relicensed. This will still be respected, but
I don't think it matters at this in this case. (Some code touched by him
was merged into mplayer.c, and then disappeared after heavy
refactoring.)
I tried to be as careful and as complete as possible. It can't be
excluded that amends to this will be made later.
This does not make the player LGPL yet.
2017-06-23 13:53:41 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2013-10-29 21:38:29 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdbool.h>
|
2014-07-10 06:28:03 +00:00
|
|
|
#include <strings.h>
|
2013-10-29 21:38:29 +00:00
|
|
|
#include <inttypes.h>
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include <libavutil/avutil.h>
|
|
|
|
|
|
|
|
#include "config.h"
|
2016-01-11 18:03:40 +00:00
|
|
|
#include "mpv_talloc.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
#include "misc/thread_pool.h"
|
|
|
|
#include "misc/thread_tools.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
#include "osdep/io.h"
|
2013-12-19 20:31:27 +00:00
|
|
|
#include "osdep/terminal.h"
|
2018-03-01 20:47:56 +00:00
|
|
|
#include "osdep/threads.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
#include "osdep/timer.h"
|
|
|
|
|
scripting: change when/how player waits for scripts being loaded
Fundamentally, scripts are loaded asynchronously, but as a feature,
there was code to wait until a script is loaded (for a certain arbitrary
definition of "loaded"). This was done in scripting.c with the
wait_loaded() function.
This called mp_idle(), and since there are commands to load/unload
scripts, it meant the player core loop could be entered recursively. I
think this is a major complication and has some problems. For example,
if you had a script that does 'os.execute("sleep inf")', then every time
you ran a command to load an instance of the script would add a new
stack frame of mp_idle(). This would lead to some sort of reentrancy
horror that is hard to debug. Also misc/dispatch.c contains a somewhat
tricky mess to support such recursive invocations. There were also some
bugs due to this and due to unforeseen interactions with other messes.
This scripting stuff was the only thing making use of that reentrancy,
and future commands that have "logical" waiting for something should be
implemented differently. So get rid of it.
Change the code to wait only in the player initialization phase: the
only place where it really has to wait is before playback is started,
because scripts might want to set options or hooks that interact with
playback initialization. Unloading of builtin scripts (can happen with
e.g. "set osc no") is left asynchronous; the unloading wasn't too robust
anyway, and this change won't make a difference if someone is trying to
break it intentionally. Note that this is not in mp_initialize(),
because mpv_initialize() uses this by locking the core, which would have
the same problem.
In the future, commands which logically wait should use different
mechanisms. Originally I thought the current approach (that is removed
with this commit) should be used, but it's too much of a mess and can't
even be used in some cases. Examples are:
- "loadfile" should be made blocking (needs to run the normal player
code and manually unblock the thread issuing the command)
- "add-sub" should not freeze the player until the URL is opened (needs
to run opening on a separate thread)
Possibly the current scripting behavior could be restored once new
mechanisms exist, and if it turns out that anyone needs it.
With this commit there should be no further instances of recursive
playloop invocations (other than the case in the following commit),
since all mp_idle()/mp_wait_events() calls are done strictly from the
main thread (and not commands/properties or libmpv client API that
"lock" the main thread).
2018-04-15 08:14:00 +00:00
|
|
|
#include "client.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/msg.h"
|
2019-11-17 23:44:12 +00:00
|
|
|
#include "common/msg_control.h"
|
2014-10-06 19:20:38 +00:00
|
|
|
#include "common/global.h"
|
2013-12-17 01:02:25 +00:00
|
|
|
#include "options/path.h"
|
|
|
|
#include "options/m_config.h"
|
|
|
|
#include "options/parse_configfile.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/playlist.h"
|
2013-12-17 01:02:25 +00:00
|
|
|
#include "options/options.h"
|
|
|
|
#include "options/m_property.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/common.h"
|
|
|
|
#include "common/encode.h"
|
2017-02-07 16:05:17 +00:00
|
|
|
#include "common/recorder.h"
|
stats: some more performance graphs
Add an infrastructure for collecting performance-related data, use it in
some places. Add rendering of them to stats.lua.
There were two main goals: minimal impact on the normal code and normal
playback. So all these stats_* function calls either happen only during
initialization, or return immediately if no stats collection is going
on. That's why it does this lazily adding of stats entries etc. (a first
iteration made each stats entry an API thing, instead of just a single
stats_ctx, but I thought that was getting too intrusive in the "normal"
code, even if everything gets worse inside of stats.c).
You could get most of this information from various profilers (including
the extremely primitive --dump-stats thing in mpv), but this makes it
easier to see the most important information at once (at least in
theory), partially because we know best about the context of various
things.
Not very happy with this. It's all pretty primitive and dumb. At this
point I just wanted to get over with it, without necessarily having to
revisit it later, but with having my stupid statistics.
Somehow the code feels terrible. There are a lot of meh decisions in
there that could be better or worse (but mostly could be better), and it
just sucks but it's also trivial and uninteresting and does the job. I
guess I hate programming. It's so tedious and the result is always shit.
Anyway, enjoy.
2020-04-08 22:27:54 +00:00
|
|
|
#include "common/stats.h"
|
2013-12-17 00:23:09 +00:00
|
|
|
#include "input/input.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
#include "audio/out/ao.h"
|
video: make decoder wrapper a filter
Move dec_video.c to filters/f_decoder_wrapper.c. It essentially becomes
a source filter. vd.h mostly disappears, because mp_filter takes care of
the dataflow, but its remains are in struct mp_decoder_fns.
One goal is to simplify dataflow by letting the filter framework handle
it (or more accurately, using its conventions). One result is that the
decode calls disappear from video.c, because we simply connect the
decoder wrapper and the filter chain with mp_pin_connect().
Another goal is to eventually remove the code duplication between the
audio and video paths for this. This commit prepares for this by trying
to make f_decoder_wrapper.c extensible, so it can be used for audio as
well later.
Decoder framedropping changes a bit. It doesn't seem to be worse than
before, and it's an obscure feature, so I'm content with its new state.
Some special code that was apparently meant to avoid dropping too many
frames in a row is removed, though.
I'm not sure how the source code tree should be organized. For one,
video/decode/vd_lavc.c is the only file in its directory, which is a bit
annoying.
2018-01-28 09:08:45 +00:00
|
|
|
#include "filters/f_decoder_wrapper.h"
|
2018-01-26 03:36:47 +00:00
|
|
|
#include "filters/f_lavfi.h"
|
|
|
|
#include "filters/filter_internal.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
#include "demux/demux.h"
|
|
|
|
#include "stream/stream.h"
|
|
|
|
#include "sub/dec_sub.h"
|
2015-09-20 16:05:06 +00:00
|
|
|
#include "external_files.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
#include "video/out/vo.h"
|
|
|
|
|
2013-12-17 00:08:53 +00:00
|
|
|
#include "core.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
#include "command.h"
|
2014-02-10 20:01:35 +00:00
|
|
|
#include "libmpv/client.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2018-05-19 16:14:20 +00:00
|
|
|
// Called from the demuxer thread if a new packet is available, or other changes.
|
|
|
|
static void wakeup_demux(void *pctx)
|
|
|
|
{
|
|
|
|
struct MPContext *mpctx = pctx;
|
|
|
|
mp_wakeup_core(mpctx);
|
|
|
|
}
|
|
|
|
|
2017-01-18 16:13:26 +00:00
|
|
|
// Called by foreign threads when playback should be stopped and such.
|
|
|
|
void mp_abort_playback_async(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
mp_cancel_trigger(mpctx->playback_abort);
|
|
|
|
|
2018-05-12 14:51:53 +00:00
|
|
|
pthread_mutex_lock(&mpctx->abort_lock);
|
2018-05-12 16:46:37 +00:00
|
|
|
|
|
|
|
for (int n = 0; n < mpctx->num_abort_list; n++) {
|
|
|
|
struct mp_abort_entry *abort = mpctx->abort_list[n];
|
|
|
|
if (abort->coupled_to_playback)
|
|
|
|
mp_abort_trigger_locked(mpctx, abort);
|
|
|
|
}
|
|
|
|
|
2018-05-12 14:51:53 +00:00
|
|
|
pthread_mutex_unlock(&mpctx->abort_lock);
|
2017-01-18 16:13:26 +00:00
|
|
|
}
|
|
|
|
|
2018-05-12 16:46:37 +00:00
|
|
|
// Add it to the global list, and allocate required data structures.
|
|
|
|
void mp_abort_add(struct MPContext *mpctx, struct mp_abort_entry *abort)
|
|
|
|
{
|
|
|
|
pthread_mutex_lock(&mpctx->abort_lock);
|
|
|
|
assert(!abort->cancel);
|
|
|
|
abort->cancel = mp_cancel_new(NULL);
|
|
|
|
MP_TARRAY_APPEND(NULL, mpctx->abort_list, mpctx->num_abort_list, abort);
|
|
|
|
mp_abort_recheck_locked(mpctx, abort);
|
|
|
|
pthread_mutex_unlock(&mpctx->abort_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove Add it to the global list, and free/clear required data structures.
|
|
|
|
// Does not deallocate the abort value itself.
|
|
|
|
void mp_abort_remove(struct MPContext *mpctx, struct mp_abort_entry *abort)
|
|
|
|
{
|
|
|
|
pthread_mutex_lock(&mpctx->abort_lock);
|
|
|
|
for (int n = 0; n < mpctx->num_abort_list; n++) {
|
|
|
|
if (mpctx->abort_list[n] == abort) {
|
|
|
|
MP_TARRAY_REMOVE_AT(mpctx->abort_list, mpctx->num_abort_list, n);
|
|
|
|
TA_FREEP(&abort->cancel);
|
|
|
|
abort = NULL; // it's not free'd, just clear for the assert below
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(!abort); // should have been in the list
|
|
|
|
pthread_mutex_unlock(&mpctx->abort_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify whether the abort needs to be signaled after changing certain fields
|
|
|
|
// in abort.
|
|
|
|
void mp_abort_recheck_locked(struct MPContext *mpctx,
|
|
|
|
struct mp_abort_entry *abort)
|
|
|
|
{
|
2018-05-13 11:48:47 +00:00
|
|
|
if ((abort->coupled_to_playback && mp_cancel_test(mpctx->playback_abort)) ||
|
|
|
|
mpctx->abort_all)
|
|
|
|
{
|
2018-05-12 16:46:37 +00:00
|
|
|
mp_abort_trigger_locked(mpctx, abort);
|
2018-05-13 11:48:47 +00:00
|
|
|
}
|
2018-05-12 16:46:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void mp_abort_trigger_locked(struct MPContext *mpctx,
|
|
|
|
struct mp_abort_entry *abort)
|
|
|
|
{
|
|
|
|
mp_cancel_trigger(abort->cancel);
|
|
|
|
}
|
|
|
|
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
static void kill_demuxers_reentrant(struct MPContext *mpctx,
|
|
|
|
struct demuxer **demuxers, int num_demuxers)
|
|
|
|
{
|
|
|
|
struct demux_free_async_state **items = NULL;
|
|
|
|
int num_items = 0;
|
|
|
|
|
|
|
|
for (int n = 0; n < num_demuxers; n++) {
|
|
|
|
struct demuxer *d = demuxers[n];
|
|
|
|
|
|
|
|
if (!demux_cancel_test(d)) {
|
|
|
|
// Make sure it is set if it wasn't yet.
|
|
|
|
demux_set_wakeup_cb(d, wakeup_demux, mpctx);
|
|
|
|
|
|
|
|
struct demux_free_async_state *item = demux_free_async(d);
|
|
|
|
if (item) {
|
|
|
|
MP_TARRAY_APPEND(NULL, items, num_items, item);
|
|
|
|
d = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
demux_cancel_and_free(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!num_items)
|
|
|
|
return;
|
|
|
|
|
|
|
|
MP_DBG(mpctx, "Terminating demuxers...\n");
|
|
|
|
|
|
|
|
double end = mp_time_sec() + mpctx->opts->demux_termination_timeout;
|
|
|
|
bool force = false;
|
|
|
|
while (num_items) {
|
|
|
|
double wait = end - mp_time_sec();
|
|
|
|
|
|
|
|
for (int n = 0; n < num_items; n++) {
|
|
|
|
struct demux_free_async_state *item = items[n];
|
|
|
|
if (demux_free_async_finish(item)) {
|
|
|
|
items[n] = items[num_items - 1];
|
|
|
|
num_items -= 1;
|
|
|
|
n--;
|
|
|
|
goto repeat;
|
|
|
|
} else if (wait < 0) {
|
|
|
|
demux_free_async_force(item);
|
|
|
|
if (!force)
|
|
|
|
MP_VERBOSE(mpctx, "Forcefully terminating demuxers...\n");
|
|
|
|
force = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wait >= 0)
|
|
|
|
mp_set_timeout(mpctx, wait);
|
|
|
|
mp_idle(mpctx);
|
|
|
|
repeat:;
|
|
|
|
}
|
|
|
|
|
|
|
|
talloc_free(items);
|
|
|
|
|
|
|
|
MP_DBG(mpctx, "Done terminating demuxers.\n");
|
2017-01-18 16:13:26 +00:00
|
|
|
}
|
|
|
|
|
2014-10-03 17:57:49 +00:00
|
|
|
static void uninit_demuxer(struct MPContext *mpctx)
|
2013-12-24 16:46:14 +00:00
|
|
|
{
|
2020-04-15 15:03:37 +00:00
|
|
|
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
|
|
|
|
for (int r = 0; r < num_ptracks[t]; r++)
|
2014-10-03 17:57:49 +00:00
|
|
|
mpctx->current_track[r][t] = NULL;
|
|
|
|
}
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
|
2015-02-22 18:06:21 +00:00
|
|
|
talloc_free(mpctx->chapters);
|
|
|
|
mpctx->chapters = NULL;
|
|
|
|
mpctx->num_chapters = 0;
|
|
|
|
|
demux, command: add a third stream recording mechanism
That's right, and it's probably not the end of it. I'll just claim that
I have no idea how to create a proper user interface for this, so I'm
creating multiple partially-orthogonal, of which some may work better in
each of its special use cases.
Until now, there was --record-file. You get relatively good control
about what is muxed, and it can use the cache. But it sucks that it's
bound to playback. If you pause while it's set, muxing stops. If you
seek while it's set, the output will be sort-of trashed, and that's by
design.
Then --stream-record was added. This is a bit better (especially for
live streams), but you can't really control well when muxing stops or
ends. In particular, it can't use the cache (it just dumps whatever the
underlying demuxer returns).
Today, the idea is that the user should just be able to select a time
range to dump to a file, and it should not affected by the user seeking
around in the cache. In addition, the stream may still be running, so
there's some need to continue dumping, even if it's redundant to
--stream-record.
One notable thing is that it uses the async command shit. Not sure
whether this is a good idea. Maybe not, but whatever. Also, a user can
always use the "async" prefix to pretend it doesn't.
Much of this was barely tested (especially the reinterleaving crap),
let's just hope it mostly works. I'm sure you can tolerate the one or
other crash?
2019-07-07 18:38:22 +00:00
|
|
|
mp_abort_cache_dumping(mpctx);
|
|
|
|
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
struct demuxer **demuxers = NULL;
|
|
|
|
int num_demuxers = 0;
|
|
|
|
|
|
|
|
if (mpctx->demuxer)
|
|
|
|
MP_TARRAY_APPEND(NULL, demuxers, num_demuxers, mpctx->demuxer);
|
|
|
|
mpctx->demuxer = NULL;
|
|
|
|
|
2015-12-26 17:32:27 +00:00
|
|
|
for (int i = 0; i < mpctx->num_tracks; i++) {
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
struct track *track = mpctx->tracks[i];
|
|
|
|
|
2018-06-30 15:15:29 +00:00
|
|
|
assert(!track->dec && !track->d_sub);
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
assert(!track->vo_c && !track->ao_c);
|
|
|
|
assert(!track->sink);
|
|
|
|
assert(!track->remux_sink);
|
|
|
|
|
|
|
|
// Demuxers can be added in any order (if they appear mid-stream), and
|
|
|
|
// we can't know which tracks uses which, so here's some O(n^2) trash.
|
|
|
|
for (int n = 0; n < num_demuxers; n++) {
|
|
|
|
if (demuxers[n] == track->demuxer) {
|
|
|
|
track->demuxer = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (track->demuxer)
|
|
|
|
MP_TARRAY_APPEND(NULL, demuxers, num_demuxers, track->demuxer);
|
|
|
|
|
|
|
|
talloc_free(track);
|
2015-12-26 17:32:27 +00:00
|
|
|
}
|
2015-02-22 18:06:21 +00:00
|
|
|
mpctx->num_tracks = 0;
|
|
|
|
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
kill_demuxers_reentrant(mpctx, demuxers, num_demuxers);
|
|
|
|
talloc_free(demuxers);
|
2013-12-24 16:46:14 +00:00
|
|
|
}
|
|
|
|
|
2014-07-13 18:12:13 +00:00
|
|
|
#define APPEND(s, ...) mp_snprintf_cat(s, sizeof(s), __VA_ARGS__)
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
static void print_stream(struct MPContext *mpctx, struct track *t)
|
|
|
|
{
|
|
|
|
struct sh_stream *s = t->stream;
|
|
|
|
const char *tname = "?";
|
|
|
|
const char *selopt = "?";
|
|
|
|
const char *langopt = "?";
|
|
|
|
switch (t->type) {
|
|
|
|
case STREAM_VIDEO:
|
2014-07-13 18:12:13 +00:00
|
|
|
tname = "Video"; selopt = "vid"; langopt = NULL;
|
2013-10-29 21:38:29 +00:00
|
|
|
break;
|
|
|
|
case STREAM_AUDIO:
|
2014-07-13 18:12:13 +00:00
|
|
|
tname = "Audio"; selopt = "aid"; langopt = "alang";
|
2013-10-29 21:38:29 +00:00
|
|
|
break;
|
|
|
|
case STREAM_SUB:
|
2014-07-13 18:12:13 +00:00
|
|
|
tname = "Subs"; selopt = "sid"; langopt = "slang";
|
2013-10-29 21:38:29 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-07-13 18:12:13 +00:00
|
|
|
char b[2048] = {0};
|
2015-01-09 22:56:49 +00:00
|
|
|
APPEND(b, " %3s %-5s", t->selected ? "(+)" : "", tname);
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " --%s=%d", selopt, t->user_tid);
|
2013-10-29 21:38:29 +00:00
|
|
|
if (t->lang && langopt)
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " --%s=%s", langopt, t->lang);
|
2013-10-29 21:38:29 +00:00
|
|
|
if (t->default_track)
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " (*)");
|
2015-06-27 20:02:24 +00:00
|
|
|
if (t->forced_track)
|
|
|
|
APPEND(b, " (f)");
|
2013-10-29 21:38:29 +00:00
|
|
|
if (t->attached_picture)
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " [P]");
|
2013-10-29 21:38:29 +00:00
|
|
|
if (t->title)
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " '%s'", t->title);
|
2016-01-12 22:48:19 +00:00
|
|
|
const char *codec = s ? s->codec->codec : NULL;
|
2017-03-26 11:30:27 +00:00
|
|
|
APPEND(b, " (%s", codec ? codec : "<unknown>");
|
|
|
|
if (t->type == STREAM_VIDEO) {
|
|
|
|
if (s && s->codec->disp_w)
|
|
|
|
APPEND(b, " %dx%d", s->codec->disp_w, s->codec->disp_h);
|
|
|
|
if (s && s->codec->fps)
|
2017-03-26 12:13:36 +00:00
|
|
|
APPEND(b, " %.3ffps", s->codec->fps);
|
2017-03-26 11:30:27 +00:00
|
|
|
} else if (t->type == STREAM_AUDIO) {
|
|
|
|
if (s && s->codec->channels.num)
|
2017-03-26 12:13:36 +00:00
|
|
|
APPEND(b, " %dch", s->codec->channels.num);
|
2017-03-26 11:30:27 +00:00
|
|
|
if (s && s->codec->samplerate)
|
2017-03-26 12:13:36 +00:00
|
|
|
APPEND(b, " %dHz", s->codec->samplerate);
|
2017-03-26 11:30:27 +00:00
|
|
|
}
|
|
|
|
APPEND(b, ")");
|
2020-02-19 15:26:22 +00:00
|
|
|
if (s->hls_bitrate > 0)
|
2020-02-20 11:13:32 +00:00
|
|
|
APPEND(b, " (%d kbps)", (s->hls_bitrate + 500) / 1000);
|
2013-10-29 21:38:29 +00:00
|
|
|
if (t->is_external)
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " (external)");
|
|
|
|
MP_INFO(mpctx, "%s\n", b);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
2015-06-03 19:50:37 +00:00
|
|
|
void print_track_list(struct MPContext *mpctx, const char *msg)
|
2015-04-28 20:04:37 +00:00
|
|
|
{
|
2015-06-03 19:50:37 +00:00
|
|
|
if (msg)
|
|
|
|
MP_INFO(mpctx, "%s\n", msg);
|
2015-04-28 20:04:37 +00:00
|
|
|
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++)
|
|
|
|
if (mpctx->tracks[n]->type == t)
|
|
|
|
print_stream(mpctx, mpctx->tracks[n]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-16 20:40:21 +00:00
|
|
|
void update_demuxer_properties(struct MPContext *mpctx)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
2016-02-15 20:03:51 +00:00
|
|
|
struct demuxer *demuxer = mpctx->demuxer;
|
2014-07-16 20:40:21 +00:00
|
|
|
if (!demuxer)
|
|
|
|
return;
|
demux: redo timed metadata
The old implementation didn't work for the OGG case. Discard the old
shit code (instead of fixing it), and write new shit code. The old code
was already over a year old, so it's about time to rewrite it for no
reason anyway.
While it's true that the old code appears to be broken, the main reason
to rewrite this is to make it simpler. While the amount of code seems to
be about the same, both the concept and the actual tag handling are
simpler. The result is probably a bit more correct.
The packet struct shrinks by 8 byte. That fact that it wasted 8 bytes
per packet for a rather obscure use case was the reason I started this
at all (and when I found that OGG updates didn't work). While these 8
bytes aren't going to hurt, the packet struct was getting too bloated.
If you buffer a lot of data, these extra fields will add up. Still quite
some effort for 8 bytes. Fortunately, it's not like there are any
managers that need to be convinced whether it's worth doing. The freedom
to waste time on dumb shit.
The old implementation attached the current metadata to each packet.
When the decoder read the packet, the packet's metadata was made
current. The new implementation stores metadata as separate list, and
requires that the player frontend tells it the current playback time,
which will be used to find the currently valid metadata. In both cases,
the objective was to correctly update metadata even if a lot of data is
buffered ahead (and to update them correctly when seeking within the
demuxer cache).
The new implementation is actually slightly more correct, because it
uses the playback time for the metadata lookup. Consider if you have an
audio filter which buffers 15 seconds (unfortunately such a filter
exists), then the old code would update the current title 15 seconds too
early, while the new one does it correctly.
The new code also simplifies mixing the 3 metadata sources (global, per
stream, ICY). We assume these aren't mixed in a meaningful way. The old
code tried to be a bit more "exact". I didn't bother to look how the old
code did this, but the new code simply always "merges" with the previous
metadata, so if a newer tag removes a field, it's going to stick around
anyway.
I tried to keep it simple. Other approaches include making metadata a
special sh_stream with metadata packets. This would have been
conceptually clean, but the implementation would probably have been
unnatural (and doesn't match well with libavformat's API anyway). It
would have been nice to make the metadata updates chapter points (makes
a lot of sense for the intended use case, web radio current song
information), but I don't think it would have been a good idea to make
chapters suddenly so dynamic. (Still an idea to keep in mind; the new
code actually makes it easier to work towards this.)
You could mention how subtitles are timed metadata, and actually are
implemented as sparse packet streams in some formats. mp4 implements
chapters as special subtitle stream, AFAIK. (Ironically, this is very
not-ideal for files. It would be useful for streaming like web radio,
but mp4 is extremely bad for streaming by design for other reasons.)
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla
2019-06-10 00:18:20 +00:00
|
|
|
demux_update(demuxer, get_current_time(mpctx));
|
2014-07-16 20:40:21 +00:00
|
|
|
int events = demuxer->events;
|
|
|
|
if ((events & DEMUX_EVENT_INIT) && demuxer->num_editions > 1) {
|
2014-01-22 23:54:08 +00:00
|
|
|
for (int n = 0; n < demuxer->num_editions; n++) {
|
|
|
|
struct demux_edition *edition = &demuxer->editions[n];
|
2014-07-13 18:12:13 +00:00
|
|
|
char b[128] = {0};
|
2015-01-09 22:56:49 +00:00
|
|
|
APPEND(b, " %3s --edition=%d",
|
2014-07-13 18:12:13 +00:00
|
|
|
n == demuxer->edition ? "(+)" : "", n);
|
2014-01-22 23:54:08 +00:00
|
|
|
char *name = mp_tags_get_str(edition->metadata, "title");
|
|
|
|
if (name)
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " '%s'", name);
|
2014-01-22 23:54:08 +00:00
|
|
|
if (edition->default_edition)
|
2014-07-13 18:12:13 +00:00
|
|
|
APPEND(b, " (*)");
|
|
|
|
MP_INFO(mpctx, "%s\n", b);
|
2014-01-22 23:54:08 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-15 20:03:51 +00:00
|
|
|
struct demuxer *tracks = mpctx->demuxer;
|
2014-07-20 18:02:58 +00:00
|
|
|
if (tracks->events & DEMUX_EVENT_STREAMS) {
|
|
|
|
add_demuxer_tracks(mpctx, tracks);
|
2015-06-03 19:50:37 +00:00
|
|
|
print_track_list(mpctx, NULL);
|
2014-07-20 18:02:58 +00:00
|
|
|
tracks->events &= ~DEMUX_EVENT_STREAMS;
|
2014-07-16 20:40:21 +00:00
|
|
|
}
|
2014-12-19 22:54:21 +00:00
|
|
|
if (events & DEMUX_EVENT_METADATA) {
|
2014-12-29 21:51:18 +00:00
|
|
|
struct mp_tags *info =
|
|
|
|
mp_tags_filtered(mpctx, demuxer->metadata, mpctx->opts->display_tags);
|
2014-12-19 22:54:21 +00:00
|
|
|
// prev is used to attempt to print changed tags only (to some degree)
|
2014-12-29 21:51:18 +00:00
|
|
|
struct mp_tags *prev = mpctx->filtered_tags;
|
2014-12-19 22:54:21 +00:00
|
|
|
int n_prev = 0;
|
|
|
|
bool had_output = false;
|
|
|
|
for (int n = 0; n < info->num_keys; n++) {
|
|
|
|
if (prev && n_prev < prev->num_keys) {
|
|
|
|
if (strcmp(prev->keys[n_prev], info->keys[n]) == 0) {
|
|
|
|
n_prev++;
|
|
|
|
if (strcmp(prev->values[n_prev - 1], info->values[n]) == 0)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2015-10-01 19:10:59 +00:00
|
|
|
struct mp_log *log = mp_log_new(NULL, mpctx->log, "!display-tags");
|
2014-12-19 22:54:21 +00:00
|
|
|
if (!had_output)
|
2015-10-01 19:10:59 +00:00
|
|
|
mp_info(log, "File tags:\n");
|
|
|
|
mp_info(log, " %s: %s\n", info->keys[n], info->values[n]);
|
2014-12-19 22:54:21 +00:00
|
|
|
had_output = true;
|
2015-10-01 19:10:59 +00:00
|
|
|
talloc_free(log);
|
2014-12-19 22:54:21 +00:00
|
|
|
}
|
2014-12-29 21:51:18 +00:00
|
|
|
talloc_free(mpctx->filtered_tags);
|
|
|
|
mpctx->filtered_tags = info;
|
2021-12-02 16:19:39 +00:00
|
|
|
mp_notify(mpctx, MP_EVENT_METADATA_UPDATE, NULL);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
2017-12-24 00:56:09 +00:00
|
|
|
if (events & DEMUX_EVENT_DURATION)
|
|
|
|
mp_notify(mpctx, MP_EVENT_DURATION_UPDATE, NULL);
|
2014-07-20 18:02:58 +00:00
|
|
|
demuxer->events = 0;
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
2016-02-25 21:44:50 +00:00
|
|
|
// Enables or disables the stream for the given track, according to
|
|
|
|
// track->selected.
|
2020-11-19 14:12:04 +00:00
|
|
|
// With refresh_only=true, refreshes the stream if it's enabled.
|
|
|
|
void reselect_demux_stream(struct MPContext *mpctx, struct track *track,
|
|
|
|
bool refresh_only)
|
2014-07-29 15:55:28 +00:00
|
|
|
{
|
2016-02-25 21:44:50 +00:00
|
|
|
if (!track->stream)
|
|
|
|
return;
|
player: improve instant track switching
When switching tracks, we normally have the problem that data gets lost
due to readahead buffering. (Which in turn is because we're stubborn and
instruct the demuxers to discard data on unselected streams.) The
demuxer layer has a hack that re-reads discarded buffered data if a
stream is enabled mid-stream, so track switching will seem instant.
A somewhat similar problem is when all tracks of an external files were
disabled - when enabling the first track, we have to seek to the target
position.
Handle these with the same mechanism. Pass the "current time" to the
demuxer's stream switch function, and let the demuxer figure out what to
do. The demuxer will issue a refresh seek (if possible) to update the
new stream, or will issue a "normal" seek if there was no active stream
yet.
One case that changes is when a video/audio stream is enabled on an
external file with only a subtitle stream active, and the demuxer does
not support rrefresh seeks. This is a fuzzy case, because subtitles are
sparse, and the demuxer might have skipped large amounts of data. We
used to seek (and send the subtitle decoder some subtitle packets
twice). This case is sort of obscure and insane, and the fix would be
questionable, so we simply don't care.
Should mostly fix #3392.
2016-08-06 13:47:04 +00:00
|
|
|
double pts = get_current_time(mpctx);
|
2018-06-30 15:55:21 +00:00
|
|
|
if (pts != MP_NOPTS_VALUE) {
|
player: improve instant track switching
When switching tracks, we normally have the problem that data gets lost
due to readahead buffering. (Which in turn is because we're stubborn and
instruct the demuxers to discard data on unselected streams.) The
demuxer layer has a hack that re-reads discarded buffered data if a
stream is enabled mid-stream, so track switching will seem instant.
A somewhat similar problem is when all tracks of an external files were
disabled - when enabling the first track, we have to seek to the target
position.
Handle these with the same mechanism. Pass the "current time" to the
demuxer's stream switch function, and let the demuxer figure out what to
do. The demuxer will issue a refresh seek (if possible) to update the
new stream, or will issue a "normal" seek if there was no active stream
yet.
One case that changes is when a video/audio stream is enabled on an
external file with only a subtitle stream active, and the demuxer does
not support rrefresh seeks. This is a fuzzy case, because subtitles are
sparse, and the demuxer might have skipped large amounts of data. We
used to seek (and send the subtitle decoder some subtitle packets
twice). This case is sort of obscure and insane, and the fix would be
questionable, so we simply don't care.
Should mostly fix #3392.
2016-08-06 13:47:04 +00:00
|
|
|
pts += get_track_seek_offset(mpctx, track);
|
2018-06-30 15:55:21 +00:00
|
|
|
if (track->type == STREAM_SUB)
|
|
|
|
pts -= 10.0;
|
|
|
|
}
|
2020-11-19 14:12:04 +00:00
|
|
|
if (refresh_only)
|
|
|
|
demuxer_refresh_track(track->demuxer, track->stream, pts);
|
|
|
|
else
|
|
|
|
demuxer_select_track(track->demuxer, track->stream, pts, track->selected);
|
2013-12-24 10:30:22 +00:00
|
|
|
}
|
|
|
|
|
2016-02-23 21:51:18 +00:00
|
|
|
static void enable_demux_thread(struct MPContext *mpctx, struct demuxer *demux)
|
2014-07-18 13:10:28 +00:00
|
|
|
{
|
2016-02-23 21:51:18 +00:00
|
|
|
if (mpctx->opts->demuxer_thread && !demux->fully_read) {
|
|
|
|
demux_set_wakeup_cb(demux, wakeup_demux, mpctx);
|
|
|
|
demux_start_thread(demux);
|
2014-07-18 13:10:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
static int find_new_tid(struct MPContext *mpctx, enum stream_type t)
|
|
|
|
{
|
|
|
|
int new_id = 0;
|
|
|
|
for (int i = 0; i < mpctx->num_tracks; i++) {
|
|
|
|
struct track *track = mpctx->tracks[i];
|
|
|
|
if (track->type == t)
|
|
|
|
new_id = MPMAX(new_id, track->user_tid);
|
|
|
|
}
|
|
|
|
return new_id + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct track *add_stream_track(struct MPContext *mpctx,
|
2014-07-16 20:40:21 +00:00
|
|
|
struct demuxer *demuxer,
|
2016-02-15 20:03:51 +00:00
|
|
|
struct sh_stream *stream)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
|
|
|
for (int i = 0; i < mpctx->num_tracks; i++) {
|
|
|
|
struct track *track = mpctx->tracks[i];
|
2016-02-15 20:03:51 +00:00
|
|
|
if (track->stream == stream)
|
2013-10-29 21:38:29 +00:00
|
|
|
return track;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct track *track = talloc_ptrtype(NULL, track);
|
|
|
|
*track = (struct track) {
|
|
|
|
.type = stream->type,
|
|
|
|
.user_tid = find_new_tid(mpctx, stream->type),
|
|
|
|
.demuxer_id = stream->demuxer_id,
|
2014-10-21 11:16:48 +00:00
|
|
|
.ff_index = stream->ff_index,
|
2013-10-29 21:38:29 +00:00
|
|
|
.title = stream->title,
|
|
|
|
.default_track = stream->default_track,
|
2015-06-27 20:02:24 +00:00
|
|
|
.forced_track = stream->forced_track,
|
2018-07-26 19:53:14 +00:00
|
|
|
.dependent_track = stream->dependent_track,
|
|
|
|
.visual_impaired_track = stream->visual_impaired_track,
|
|
|
|
.hearing_impaired_track = stream->hearing_impaired_track,
|
2021-10-14 14:36:57 +00:00
|
|
|
.image = stream->image,
|
2013-10-29 21:38:29 +00:00
|
|
|
.attached_picture = stream->attached_picture != NULL,
|
|
|
|
.lang = stream->lang,
|
2014-07-16 20:40:21 +00:00
|
|
|
.demuxer = demuxer,
|
2013-10-29 21:38:29 +00:00
|
|
|
.stream = stream,
|
|
|
|
};
|
|
|
|
MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
|
|
|
|
|
2021-12-02 16:19:39 +00:00
|
|
|
mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
return track;
|
|
|
|
}
|
|
|
|
|
|
|
|
void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer)
|
|
|
|
{
|
2016-02-15 20:03:51 +00:00
|
|
|
for (int n = 0; n < demux_get_num_stream(demuxer); n++)
|
|
|
|
add_stream_track(mpctx, demuxer, demux_get_stream(demuxer, n));
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Result numerically higher => better match. 0 == no match.
|
|
|
|
static int match_lang(char **langs, char *lang)
|
|
|
|
{
|
|
|
|
for (int idx = 0; langs && langs[idx]; idx++) {
|
2017-12-23 20:18:13 +00:00
|
|
|
if (lang && strcasecmp(langs[idx], lang) == 0)
|
2013-10-29 21:38:29 +00:00
|
|
|
return INT_MAX - idx;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Get the track wanted by the user.
|
|
|
|
* tid is the track ID requested by the user (-2: deselect, -1: default)
|
|
|
|
* lang is a string list, NULL is same as empty list
|
|
|
|
* Sort tracks based on the following criteria, and pick the first:
|
2020-04-13 13:42:52 +00:00
|
|
|
*0a) track matches tid (always wins)
|
|
|
|
* 0b) track is not from --external-file
|
2014-01-05 15:15:30 +00:00
|
|
|
* 1) track is external (no_default cancels this)
|
2013-10-29 21:38:29 +00:00
|
|
|
* 1b) track was passed explicitly (is not an auto-loaded subtitle)
|
|
|
|
* 2) earlier match in lang list
|
2020-06-22 20:08:42 +00:00
|
|
|
* 3a) track is marked forced and we're preferring forced tracks
|
|
|
|
* 3b) track is marked non-forced and we're preferring non-forced tracks
|
|
|
|
* 3c) track is marked default
|
2014-09-01 21:47:27 +00:00
|
|
|
* 4) attached picture, HLS bitrate
|
|
|
|
* 5) lower track number
|
|
|
|
* If select_fallback is not set, 5) is only used to determine whether a
|
2013-10-29 21:38:29 +00:00
|
|
|
* matching track is preferred over another track. Otherwise, always pick a
|
|
|
|
* track (if nothing else matches, return the track with lowest ID).
|
2020-06-22 20:08:42 +00:00
|
|
|
* Forced tracks are preferred when the user prefers not to display subtitles
|
2013-10-29 21:38:29 +00:00
|
|
|
*/
|
|
|
|
// Return whether t1 is preferred over t2
|
2014-09-01 21:47:27 +00:00
|
|
|
static bool compare_track(struct track *t1, struct track *t2, char **langs,
|
2020-06-22 20:08:42 +00:00
|
|
|
int prefer_forced, struct MPOpts *opts)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
2016-08-10 20:22:50 +00:00
|
|
|
if (!opts->autoload_files && t1->is_external != t2->is_external)
|
|
|
|
return !t1->is_external;
|
2014-01-05 15:15:30 +00:00
|
|
|
bool ext1 = t1->is_external && !t1->no_default;
|
|
|
|
bool ext2 = t2->is_external && !t2->no_default;
|
2021-07-28 15:00:38 +00:00
|
|
|
if (ext1 != ext2) {
|
|
|
|
if (t1->attached_picture && t2->attached_picture
|
|
|
|
&& opts->audio_display == 1)
|
|
|
|
return !ext1;
|
2014-01-05 15:15:30 +00:00
|
|
|
return ext1;
|
2021-07-28 15:00:38 +00:00
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
if (t1->auto_loaded != t2->auto_loaded)
|
|
|
|
return !t1->auto_loaded;
|
|
|
|
int l1 = match_lang(langs, t1->lang), l2 = match_lang(langs, t2->lang);
|
|
|
|
if (l1 != l2)
|
|
|
|
return l1 > l2;
|
2015-06-27 20:02:24 +00:00
|
|
|
if (t1->forced_track != t2->forced_track)
|
2020-06-22 20:08:42 +00:00
|
|
|
return prefer_forced ? t1->forced_track : !t1->forced_track;
|
2013-10-29 21:38:29 +00:00
|
|
|
if (t1->default_track != t2->default_track)
|
|
|
|
return t1->default_track;
|
|
|
|
if (t1->attached_picture != t2->attached_picture)
|
|
|
|
return !t1->attached_picture;
|
2015-07-13 11:34:58 +00:00
|
|
|
if (t1->stream && t2->stream && opts->hls_bitrate >= 0 &&
|
|
|
|
t1->stream->hls_bitrate != t2->stream->hls_bitrate)
|
|
|
|
{
|
|
|
|
bool t1_ok = t1->stream->hls_bitrate <= opts->hls_bitrate;
|
|
|
|
bool t2_ok = t2->stream->hls_bitrate <= opts->hls_bitrate;
|
|
|
|
if (t1_ok != t2_ok)
|
|
|
|
return t1_ok;
|
|
|
|
if (t1_ok && t2_ok)
|
|
|
|
return t1->stream->hls_bitrate > t2->stream->hls_bitrate;
|
|
|
|
return t1->stream->hls_bitrate < t2->stream->hls_bitrate;
|
2014-09-01 21:47:27 +00:00
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
return t1->user_tid <= t2->user_tid;
|
|
|
|
}
|
2019-03-01 21:33:24 +00:00
|
|
|
|
|
|
|
static bool duplicate_track(struct MPContext *mpctx, int order,
|
|
|
|
enum stream_type type, struct track *track)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < order; i++) {
|
|
|
|
if (mpctx->current_track[i][type] == track)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2015-05-22 19:00:24 +00:00
|
|
|
struct track *select_default_track(struct MPContext *mpctx, int order,
|
|
|
|
enum stream_type type)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
2015-05-22 19:00:24 +00:00
|
|
|
struct MPOpts *opts = mpctx->opts;
|
|
|
|
int tid = opts->stream_id[order][type];
|
2019-03-01 21:33:24 +00:00
|
|
|
char **langs = opts->stream_lang[type];
|
2020-06-22 20:08:42 +00:00
|
|
|
int prefer_forced = type != STREAM_SUB ||
|
|
|
|
(!opts->subs_with_matching_audio &&
|
|
|
|
mpctx->current_track[0][STREAM_AUDIO] &&
|
|
|
|
match_lang(langs, mpctx->current_track[0][STREAM_AUDIO]->lang));
|
2017-12-25 10:43:04 +00:00
|
|
|
if (tid == -2)
|
2013-10-29 21:38:29 +00:00
|
|
|
return NULL;
|
|
|
|
bool select_fallback = type == STREAM_VIDEO || type == STREAM_AUDIO;
|
|
|
|
struct track *pick = NULL;
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *track = mpctx->tracks[n];
|
|
|
|
if (track->type != type)
|
|
|
|
continue;
|
|
|
|
if (track->user_tid == tid)
|
|
|
|
return track;
|
2020-04-13 13:55:51 +00:00
|
|
|
if (tid >= 0)
|
|
|
|
continue;
|
2018-01-06 16:49:37 +00:00
|
|
|
if (track->no_auto_select)
|
|
|
|
continue;
|
2019-03-01 21:33:24 +00:00
|
|
|
if (duplicate_track(mpctx, order, type, track))
|
|
|
|
continue;
|
2020-06-22 20:08:42 +00:00
|
|
|
if (!pick || compare_track(track, pick, langs, prefer_forced, mpctx->opts))
|
2013-10-29 21:38:29 +00:00
|
|
|
pick = track;
|
|
|
|
}
|
2014-01-05 15:15:30 +00:00
|
|
|
if (pick && !select_fallback && !(pick->is_external && !pick->no_default)
|
2015-06-27 20:02:24 +00:00
|
|
|
&& !match_lang(langs, pick->lang) && !pick->default_track
|
|
|
|
&& !pick->forced_track)
|
2013-10-29 21:38:29 +00:00
|
|
|
pick = NULL;
|
|
|
|
if (pick && pick->attached_picture && !mpctx->opts->audio_display)
|
|
|
|
pick = NULL;
|
2016-08-10 20:22:50 +00:00
|
|
|
if (pick && !opts->autoload_files && pick->is_external)
|
|
|
|
pick = NULL;
|
2020-06-22 20:08:42 +00:00
|
|
|
if (pick && type == STREAM_SUB && prefer_forced && !pick->forced_track &&
|
|
|
|
opts->subs_rend->forced_subs_only == -1)
|
|
|
|
opts->subs_rend->forced_subs_only_current = 1;
|
2013-10-29 21:38:29 +00:00
|
|
|
return pick;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *track_layout_hash(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
char *h = talloc_strdup(NULL, "");
|
|
|
|
for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *track = mpctx->tracks[n];
|
|
|
|
if (track->type != type)
|
|
|
|
continue;
|
|
|
|
h = talloc_asprintf_append_buffer(h, "%d-%d-%d-%d-%s\n", type,
|
|
|
|
track->user_tid, track->default_track, track->is_external,
|
|
|
|
track->lang ? track->lang : "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return h;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normally, video/audio/sub track selection is persistent across files. This
|
|
|
|
// code resets track selection if the new file has a different track layout.
|
|
|
|
static void check_previous_track_selection(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
struct MPOpts *opts = mpctx->opts;
|
|
|
|
|
|
|
|
if (!mpctx->track_layout_hash)
|
|
|
|
return;
|
|
|
|
|
|
|
|
char *h = track_layout_hash(mpctx);
|
|
|
|
if (strcmp(h, mpctx->track_layout_hash) != 0) {
|
2015-05-22 19:00:24 +00:00
|
|
|
// Reset selection, but only if they're not "auto" or "off". The
|
|
|
|
// defaults are -1 (default selection), or -2 (off) for secondary tracks.
|
|
|
|
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
|
2020-04-15 15:03:37 +00:00
|
|
|
for (int i = 0; i < num_ptracks[t]; i++) {
|
2020-04-15 15:10:01 +00:00
|
|
|
if (opts->stream_id[i][t] >= 0)
|
|
|
|
mark_track_selection(mpctx, i, t, i == 0 ? -1 : -2);
|
2015-05-22 19:00:24 +00:00
|
|
|
}
|
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
talloc_free(mpctx->track_layout_hash);
|
|
|
|
mpctx->track_layout_hash = NULL;
|
|
|
|
}
|
|
|
|
talloc_free(h);
|
|
|
|
}
|
|
|
|
|
2020-04-15 15:10:01 +00:00
|
|
|
// Update the matching track selection user option to the given value.
|
|
|
|
void mark_track_selection(struct MPContext *mpctx, int order,
|
|
|
|
enum stream_type type, int value)
|
2020-04-13 13:42:52 +00:00
|
|
|
{
|
2020-04-15 15:03:37 +00:00
|
|
|
assert(order >= 0 && order < num_ptracks[type]);
|
2020-04-13 13:42:52 +00:00
|
|
|
mpctx->opts->stream_id[order][type] = value;
|
|
|
|
m_config_notify_change_opt_ptr(mpctx->mconfig,
|
|
|
|
&mpctx->opts->stream_id[order][type]);
|
|
|
|
}
|
|
|
|
|
2013-12-24 16:46:08 +00:00
|
|
|
void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type,
|
2015-05-26 12:01:23 +00:00
|
|
|
struct track *track, int flags)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
|
|
|
assert(!track || track->type == type);
|
2020-04-15 15:03:37 +00:00
|
|
|
assert(type >= 0 && type < STREAM_TYPE_COUNT);
|
|
|
|
assert(order >= 0 && order < num_ptracks[type]);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2015-05-26 12:01:23 +00:00
|
|
|
// Mark the current track selection as explicitly user-requested. (This is
|
|
|
|
// different from auto-selection or disabling a track due to errors.)
|
2020-04-13 13:42:52 +00:00
|
|
|
if (flags & FLAG_MARK_SELECTION)
|
|
|
|
mark_track_selection(mpctx, order, type, track ? track->user_tid : -2);
|
2015-05-26 12:01:23 +00:00
|
|
|
|
|
|
|
// No decoder should be initialized yet.
|
|
|
|
if (!mpctx->demuxer)
|
|
|
|
return;
|
|
|
|
|
2013-12-24 16:46:08 +00:00
|
|
|
struct track *current = mpctx->current_track[order][type];
|
2013-10-29 21:38:29 +00:00
|
|
|
if (track == current)
|
|
|
|
return;
|
|
|
|
|
2016-02-05 22:19:56 +00:00
|
|
|
if (current && current->sink) {
|
|
|
|
MP_ERR(mpctx, "Can't disable input to complex filter.\n");
|
2020-04-13 13:42:52 +00:00
|
|
|
goto error;
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
|
|
|
if ((type == STREAM_VIDEO && mpctx->vo_chain && !mpctx->vo_chain->track) ||
|
|
|
|
(type == STREAM_AUDIO && mpctx->ao_chain && !mpctx->ao_chain->track))
|
|
|
|
{
|
|
|
|
MP_ERR(mpctx, "Can't switch away from complex filter output.\n");
|
2020-04-13 13:42:52 +00:00
|
|
|
goto error;
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
|
|
|
|
2013-12-25 10:24:37 +00:00
|
|
|
if (track && track->selected) {
|
|
|
|
// Track has been selected in a different order parameter.
|
|
|
|
MP_ERR(mpctx, "Track %d is already selected.\n", track->user_tid);
|
2020-04-13 13:42:52 +00:00
|
|
|
goto error;
|
2013-12-25 10:24:37 +00:00
|
|
|
}
|
|
|
|
|
2013-12-24 16:46:08 +00:00
|
|
|
if (order == 0) {
|
|
|
|
if (type == STREAM_VIDEO) {
|
2014-10-03 17:57:49 +00:00
|
|
|
uninit_video_chain(mpctx);
|
2015-02-03 22:09:02 +00:00
|
|
|
if (!track)
|
2016-04-23 15:24:03 +00:00
|
|
|
handle_force_window(mpctx, true);
|
2013-12-24 16:46:08 +00:00
|
|
|
} else if (type == STREAM_AUDIO) {
|
2014-07-13 18:07:14 +00:00
|
|
|
clear_audio_output_buffers(mpctx);
|
2014-10-03 17:57:49 +00:00
|
|
|
uninit_audio_chain(mpctx);
|
2020-04-09 09:45:46 +00:00
|
|
|
if (!track)
|
|
|
|
uninit_audio_out(mpctx);
|
2013-12-24 16:46:08 +00:00
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
2016-01-17 15:56:32 +00:00
|
|
|
if (type == STREAM_SUB)
|
|
|
|
uninit_sub(mpctx, current);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2016-02-25 21:44:50 +00:00
|
|
|
if (current) {
|
2017-02-07 16:05:17 +00:00
|
|
|
if (current->remux_sink)
|
|
|
|
close_recorder_and_error(mpctx);
|
2013-12-23 19:14:54 +00:00
|
|
|
current->selected = false;
|
2020-11-19 14:12:04 +00:00
|
|
|
reselect_demux_stream(mpctx, current, false);
|
2016-02-25 21:44:50 +00:00
|
|
|
}
|
2013-12-23 19:14:54 +00:00
|
|
|
|
2013-12-24 16:46:08 +00:00
|
|
|
mpctx->current_track[order][type] = track;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2016-02-25 21:44:50 +00:00
|
|
|
if (track) {
|
2013-12-23 19:14:54 +00:00
|
|
|
track->selected = true;
|
2020-11-19 14:12:04 +00:00
|
|
|
reselect_demux_stream(mpctx, track, false);
|
2016-02-25 21:44:50 +00:00
|
|
|
}
|
2013-12-23 19:14:54 +00:00
|
|
|
|
2014-03-03 22:58:19 +00:00
|
|
|
if (type == STREAM_VIDEO && order == 0) {
|
|
|
|
reinit_video_chain(mpctx);
|
|
|
|
} else if (type == STREAM_AUDIO && order == 0) {
|
|
|
|
reinit_audio_chain(mpctx);
|
|
|
|
} else if (type == STREAM_SUB && order >= 0 && order <= 2) {
|
2016-01-17 15:56:32 +00:00
|
|
|
reinit_sub(mpctx, track);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
2021-12-02 16:19:39 +00:00
|
|
|
mp_notify(mpctx, MP_EVENT_TRACK_SWITCHED, NULL);
|
2016-09-16 12:24:15 +00:00
|
|
|
mp_wakeup_core(mpctx);
|
2014-02-03 21:00:59 +00:00
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
talloc_free(mpctx->track_layout_hash);
|
|
|
|
mpctx->track_layout_hash = talloc_steal(mpctx, track_layout_hash(mpctx));
|
2020-04-13 13:42:52 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
error:
|
|
|
|
mark_track_selection(mpctx, order, type, -1);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
2013-12-24 16:46:08 +00:00
|
|
|
void mp_switch_track(struct MPContext *mpctx, enum stream_type type,
|
2015-05-26 12:01:23 +00:00
|
|
|
struct track *track, int flags)
|
2013-12-24 16:46:08 +00:00
|
|
|
{
|
2015-05-26 12:01:23 +00:00
|
|
|
mp_switch_track_n(mpctx, 0, type, track, flags);
|
2013-12-24 16:46:08 +00:00
|
|
|
}
|
|
|
|
|
2013-12-23 19:14:54 +00:00
|
|
|
void mp_deselect_track(struct MPContext *mpctx, struct track *track)
|
|
|
|
{
|
2013-12-24 16:46:08 +00:00
|
|
|
if (track && track->selected) {
|
2020-04-15 15:03:37 +00:00
|
|
|
for (int t = 0; t < num_ptracks[track->type]; t++) {
|
2015-05-26 12:01:23 +00:00
|
|
|
mp_switch_track_n(mpctx, t, track->type, NULL, 0);
|
2020-04-13 13:42:52 +00:00
|
|
|
mark_track_selection(mpctx, t, track->type, -1); // default
|
|
|
|
}
|
2013-12-24 16:46:08 +00:00
|
|
|
}
|
2013-12-23 19:14:54 +00:00
|
|
|
}
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type,
|
|
|
|
int tid)
|
|
|
|
{
|
|
|
|
if (tid == -1)
|
2013-12-24 16:46:08 +00:00
|
|
|
return mpctx->current_track[0][type];
|
2013-10-29 21:38:29 +00:00
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *track = mpctx->tracks[n];
|
|
|
|
if (track->type == type && track->user_tid == tid)
|
|
|
|
return track;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool mp_remove_track(struct MPContext *mpctx, struct track *track)
|
|
|
|
{
|
|
|
|
if (!track->is_external)
|
|
|
|
return false;
|
|
|
|
|
2013-12-23 19:14:54 +00:00
|
|
|
mp_deselect_track(mpctx, track);
|
|
|
|
if (track->selected)
|
|
|
|
return false;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2015-02-22 18:06:21 +00:00
|
|
|
struct demuxer *d = track->demuxer;
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
int index = 0;
|
|
|
|
while (index < mpctx->num_tracks && mpctx->tracks[index] != track)
|
|
|
|
index++;
|
2015-02-17 22:43:43 +00:00
|
|
|
MP_TARRAY_REMOVE_AT(mpctx->tracks, mpctx->num_tracks, index);
|
2013-10-29 21:38:29 +00:00
|
|
|
talloc_free(track);
|
|
|
|
|
2015-02-22 18:06:21 +00:00
|
|
|
// Close the demuxer, unless there is still a track using it. These are
|
2016-02-15 20:03:51 +00:00
|
|
|
// all external tracks.
|
2015-02-22 18:06:21 +00:00
|
|
|
bool in_use = false;
|
|
|
|
for (int n = mpctx->num_tracks - 1; n >= 0 && !in_use; n--)
|
|
|
|
in_use |= mpctx->tracks[n]->demuxer == d;
|
|
|
|
|
2016-02-23 21:12:11 +00:00
|
|
|
if (!in_use)
|
2018-05-19 15:06:00 +00:00
|
|
|
demux_cancel_and_free(d);
|
2015-02-17 22:43:13 +00:00
|
|
|
|
2021-12-02 16:19:39 +00:00
|
|
|
mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-02 16:12:27 +00:00
|
|
|
// Add the given file as additional track. The filter argument controls how or
|
|
|
|
// if tracks are auto-selected at any point.
|
2018-05-07 18:36:17 +00:00
|
|
|
// To be run on a worker thread, locked (temporarily unlocks core).
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
// cancel will generally be used to abort the loading process, but on success
|
|
|
|
// the demuxer is changed to be slaved to mpctx->playback_abort instead.
|
2018-01-27 20:27:58 +00:00
|
|
|
int mp_add_external_file(struct MPContext *mpctx, char *filename,
|
2021-03-07 21:53:19 +00:00
|
|
|
enum stream_type filter, struct mp_cancel *cancel,
|
|
|
|
bool cover_art)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
|
|
|
struct MPOpts *opts = mpctx->opts;
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
if (!filename || mp_cancel_test(cancel))
|
2018-01-27 20:27:58 +00:00
|
|
|
return -1;
|
2015-02-02 20:23:12 +00:00
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
char *disp_filename = filename;
|
|
|
|
if (strncmp(disp_filename, "memory://", 9) == 0)
|
|
|
|
disp_filename = "memory://"; // avoid noise
|
2015-02-02 20:23:12 +00:00
|
|
|
|
2019-06-07 15:09:22 +00:00
|
|
|
struct demuxer_params params = {
|
|
|
|
.is_top_level = true,
|
stream, demux: redo origin policy thing
mpv has a very weak and very annoying policy that determines whether a
playlist should be used or not. For example, if you play a remote
playlist, you usually don't want it to be able to read local filesystem
entries. (Although for a media player the impact is small I guess.)
It's weak and annoying as in that it does not prevent certain cases
which could be interpreted as bad in some cases, such as allowing
playlists on the local filesystem to reference remote URLs. It probably
barely makes sense, but we just want to exclude some other "definitely
not a good idea" things, all while playlists generally just work, so
whatever.
The policy is:
- from the command line anything is played
- local playlists can reference anything except "unsafe" streams
("unsafe" means special stream inputs like libavfilter graphs)
- remote playlists can reference only remote URLs
- things like "memory://" and archives are "transparent" to this
This commit does... something. It replaces the weird stream flags with a
slightly clearer "origin" value, which is now consequently passed down
and used everywhere. It fixes some deviations from the described policy.
I wanted to force archives to reference only content within them, but
this would probably have been more complicated (or required different
abstractions), and I'm too lazy to figure it out, so archives are now
"transparent" (playlists within archives behave the same outside).
There may be a lot of bugs in this.
This is unfortunately a very noisy commit because:
- every stream open call now needs to pass the origin
- so does every demuxer open call (=> params param. gets mandatory)
- most stream were changed to provide the "origin" value
- the origin value needed to be passed along in a lot of places
- I was too lazy to split the commit
Fixes: #7274
2019-12-20 08:41:42 +00:00
|
|
|
.stream_flags = STREAM_ORIGIN_DIRECT,
|
2019-06-07 15:09:22 +00:00
|
|
|
};
|
2015-02-20 20:21:14 +00:00
|
|
|
|
2015-02-02 20:23:12 +00:00
|
|
|
switch (filter) {
|
|
|
|
case STREAM_SUB:
|
2015-02-20 20:21:14 +00:00
|
|
|
params.force_format = opts->sub_demuxer_name;
|
2015-02-02 20:23:12 +00:00
|
|
|
break;
|
|
|
|
case STREAM_AUDIO:
|
2015-02-20 20:21:14 +00:00
|
|
|
params.force_format = opts->audio_demuxer_name;
|
2015-02-02 20:23:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
mp_core_unlock(mpctx);
|
2018-05-06 19:23:28 +00:00
|
|
|
|
2015-02-20 20:56:55 +00:00
|
|
|
struct demuxer *demuxer =
|
2018-05-19 15:06:00 +00:00
|
|
|
demux_open_url(filename, ¶ms, cancel, mpctx->global);
|
2018-05-06 19:23:28 +00:00
|
|
|
if (demuxer)
|
|
|
|
enable_demux_thread(mpctx, demuxer);
|
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
mp_core_lock(mpctx);
|
|
|
|
|
|
|
|
// The command could have overlapped with playback exiting. (We don't care
|
|
|
|
// if playback has started again meanwhile - weird, but not a problem.)
|
2018-05-19 16:25:54 +00:00
|
|
|
if (mpctx->stop_play)
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
goto err_out;
|
2018-05-06 19:23:28 +00:00
|
|
|
|
2015-02-20 20:56:55 +00:00
|
|
|
if (!demuxer)
|
2013-10-29 21:38:29 +00:00
|
|
|
goto err_out;
|
2015-02-02 20:23:12 +00:00
|
|
|
|
2019-02-18 13:01:29 +00:00
|
|
|
if (filter != STREAM_SUB && opts->rebase_start_time)
|
2015-11-16 21:47:17 +00:00
|
|
|
demux_set_ts_offset(demuxer, -demuxer->start_time);
|
|
|
|
|
2017-12-07 09:32:29 +00:00
|
|
|
bool has_any = false;
|
demux: remove weird tripple-buffering for the sh_stream list
The demuxer infrastructure was originally single-threaded. To make it
suitable for multithreading (specifically, demuxing and decoding on
separate threads), some sort of tripple-buffering was introduced. There
are separate "struct demuxer" allocations. The demuxer thread sets the
state on d_thread. If anything changes, the state is copied to d_buffer
(the copy is protected by a lock), and the decoder thread is notified.
Then the decoder thread copies the state from d_buffer to d_user (again
while holding a lock). This avoids the need for locking in the
demuxer/decoder code itself (only demux.c needs an internal, "invisible"
lock.)
Remove the streams/num_streams fields from this tripple-buffering
schema. Move them to the internal struct, and protect them with the
internal lock. Use accessors for read access outside of demux.c.
Other than replacing all field accesses with accessors, this separates
allocating and adding sh_streams. This is needed to avoid race
conditions. Before this change, this was awkwardly handled by first
initializing the sh_stream, and then sending a stream change event. Now
the stream is allocated, then initialized, and then declared as
immutable and added (at which point it becomes visible to the decoder
thread immediately).
This change is useful for PR #2626. And eventually, we should probably
get entirely of the tripple buffering, and this makes a nice first step.
2015-12-23 20:44:53 +00:00
|
|
|
for (int n = 0; n < demux_get_num_stream(demuxer); n++) {
|
|
|
|
struct sh_stream *sh = demux_get_stream(demuxer, n);
|
2017-12-07 09:32:29 +00:00
|
|
|
if (sh->type == filter || filter == STREAM_TYPE_COUNT) {
|
|
|
|
has_any = true;
|
|
|
|
break;
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-07 09:32:29 +00:00
|
|
|
|
|
|
|
if (!has_any) {
|
|
|
|
char *tname = mp_tprintf(20, "%s ", stream_type_name(filter));
|
|
|
|
if (filter == STREAM_TYPE_COUNT)
|
|
|
|
tname = "";
|
|
|
|
MP_ERR(mpctx, "No %sstreams in file %s.\n", tname, disp_filename);
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
goto err_out;
|
2017-12-07 09:32:29 +00:00
|
|
|
}
|
|
|
|
|
2018-01-27 20:27:58 +00:00
|
|
|
int first_num = -1;
|
2017-12-07 09:32:29 +00:00
|
|
|
for (int n = 0; n < demux_get_num_stream(demuxer); n++) {
|
|
|
|
struct sh_stream *sh = demux_get_stream(demuxer, n);
|
|
|
|
struct track *t = add_stream_track(mpctx, demuxer, sh);
|
|
|
|
t->is_external = true;
|
2018-10-21 14:46:54 +00:00
|
|
|
if (sh->title && sh->title[0]) {
|
|
|
|
t->title = talloc_strdup(t, sh->title);
|
|
|
|
} else {
|
|
|
|
t->title = talloc_strdup(t, mp_basename(disp_filename));
|
|
|
|
}
|
2017-12-07 09:32:29 +00:00
|
|
|
t->external_filename = talloc_strdup(t, filename);
|
|
|
|
t->no_default = sh->type != filter;
|
2018-03-02 16:12:27 +00:00
|
|
|
t->no_auto_select = t->no_default;
|
2021-03-07 21:53:19 +00:00
|
|
|
// if we found video, and we are loading cover art, flag as such.
|
|
|
|
t->attached_picture = t->type == STREAM_VIDEO && cover_art;
|
2018-01-27 20:27:58 +00:00
|
|
|
if (first_num < 0 && (filter == STREAM_TYPE_COUNT || sh->type == filter))
|
|
|
|
first_num = mpctx->num_tracks - 1;
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
2015-02-02 20:23:12 +00:00
|
|
|
|
2018-05-19 15:06:00 +00:00
|
|
|
mp_cancel_set_parent(demuxer->cancel, mpctx->playback_abort);
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
|
2018-01-27 20:27:58 +00:00
|
|
|
return first_num;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
err_out:
|
2018-05-19 15:06:00 +00:00
|
|
|
demux_cancel_and_free(demuxer);
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
if (!mp_cancel_test(cancel))
|
2018-03-21 13:49:32 +00:00
|
|
|
MP_ERR(mpctx, "Can not open external file %s.\n", disp_filename);
|
2018-01-27 20:27:58 +00:00
|
|
|
return -1;
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
// to be run on a worker thread, locked (temporarily unlocks core)
|
2016-02-08 20:18:35 +00:00
|
|
|
static void open_external_files(struct MPContext *mpctx, char **files,
|
|
|
|
enum stream_type filter)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
2018-05-07 18:36:17 +00:00
|
|
|
// Need a copy, because the option value could be mutated during iteration.
|
|
|
|
void *tmp = talloc_new(NULL);
|
|
|
|
files = mp_dup_str_array(tmp, files);
|
|
|
|
|
2016-02-08 20:18:35 +00:00
|
|
|
for (int n = 0; files && files[n]; n++)
|
2021-03-07 21:53:19 +00:00
|
|
|
// when given filter is set to video, we are loading up cover art
|
|
|
|
mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort,
|
|
|
|
filter == STREAM_VIDEO);
|
2018-05-07 18:36:17 +00:00
|
|
|
|
|
|
|
talloc_free(tmp);
|
2015-02-02 20:23:12 +00:00
|
|
|
}
|
|
|
|
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
// See mp_add_external_file() for meaning of cancel parameter.
|
|
|
|
void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel)
|
2015-02-02 20:23:12 +00:00
|
|
|
{
|
2020-09-27 22:12:52 +00:00
|
|
|
struct MPOpts *opts = mpctx->opts;
|
|
|
|
|
|
|
|
if (opts->sub_auto < 0 && opts->audiofile_auto < 0 && opts->coverart_auto < 0)
|
2015-02-02 20:23:12 +00:00
|
|
|
return;
|
2020-09-27 22:12:52 +00:00
|
|
|
if (!opts->autoload_files || strcmp(mpctx->filename, "-") == 0)
|
2016-08-10 20:22:50 +00:00
|
|
|
return;
|
2015-02-02 20:23:12 +00:00
|
|
|
|
|
|
|
void *tmp = talloc_new(NULL);
|
2020-09-27 22:12:52 +00:00
|
|
|
struct subfn *list = find_external_files(mpctx->global, mpctx->filename, opts);
|
2015-02-02 20:23:12 +00:00
|
|
|
talloc_steal(tmp, list);
|
2015-02-05 21:14:17 +00:00
|
|
|
|
|
|
|
int sc[STREAM_TYPE_COUNT] = {0};
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
if (!mpctx->tracks[n]->attached_picture)
|
|
|
|
sc[mpctx->tracks[n]->type]++;
|
|
|
|
}
|
|
|
|
|
2015-02-02 20:23:12 +00:00
|
|
|
for (int i = 0; list && list[i].fname; i++) {
|
2020-09-27 22:14:54 +00:00
|
|
|
struct subfn *e = &list[i];
|
|
|
|
|
2016-02-23 21:12:11 +00:00
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *t = mpctx->tracks[n];
|
2020-09-27 22:14:54 +00:00
|
|
|
if (t->demuxer && strcmp(t->demuxer->filename, e->fname) == 0)
|
2015-02-02 20:23:12 +00:00
|
|
|
goto skip;
|
|
|
|
}
|
2020-09-27 22:14:54 +00:00
|
|
|
if (e->type == STREAM_SUB && !sc[STREAM_VIDEO] && !sc[STREAM_AUDIO])
|
2015-02-05 21:14:17 +00:00
|
|
|
goto skip;
|
2020-09-27 22:14:54 +00:00
|
|
|
if (e->type == STREAM_AUDIO && !sc[STREAM_VIDEO])
|
2015-02-05 21:14:17 +00:00
|
|
|
goto skip;
|
2020-09-27 22:14:54 +00:00
|
|
|
if (e->type == STREAM_VIDEO && (sc[STREAM_VIDEO] || !sc[STREAM_AUDIO]))
|
2020-09-27 22:12:52 +00:00
|
|
|
goto skip;
|
2021-03-07 21:53:19 +00:00
|
|
|
|
|
|
|
// when given filter is set to video, we are loading up cover art
|
|
|
|
int first = mp_add_external_file(mpctx, e->fname, e->type, cancel,
|
|
|
|
e->type == STREAM_VIDEO);
|
2018-01-27 20:27:58 +00:00
|
|
|
if (first < 0)
|
|
|
|
goto skip;
|
|
|
|
|
|
|
|
for (int n = first; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *t = mpctx->tracks[n];
|
|
|
|
t->auto_loaded = true;
|
|
|
|
if (!t->lang)
|
2020-09-27 22:14:54 +00:00
|
|
|
t->lang = talloc_strdup(t, e->lang);
|
2015-02-02 20:23:12 +00:00
|
|
|
}
|
|
|
|
skip:;
|
|
|
|
}
|
2015-02-05 21:14:17 +00:00
|
|
|
|
2015-02-02 20:23:12 +00:00
|
|
|
talloc_free(tmp);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
2014-12-29 21:08:22 +00:00
|
|
|
// Do stuff to a newly loaded playlist. This includes any processing that may
|
|
|
|
// be required after loading a playlist.
|
|
|
|
void prepare_playlist(struct MPContext *mpctx, struct playlist *pl)
|
|
|
|
{
|
|
|
|
struct MPOpts *opts = mpctx->opts;
|
|
|
|
|
2015-08-22 20:08:17 +00:00
|
|
|
pl->current = NULL;
|
|
|
|
|
|
|
|
if (opts->playlist_pos >= 0)
|
|
|
|
pl->current = playlist_entry_from_index(pl, opts->playlist_pos);
|
|
|
|
|
2014-12-29 21:08:22 +00:00
|
|
|
if (opts->shuffle)
|
|
|
|
playlist_shuffle(pl);
|
|
|
|
|
|
|
|
if (opts->merge_files)
|
|
|
|
merge_playlist_files(pl);
|
|
|
|
|
2015-08-22 20:08:17 +00:00
|
|
|
if (!pl->current)
|
|
|
|
pl->current = mp_check_playlist_resume(mpctx, pl);
|
|
|
|
|
2014-12-29 21:08:22 +00:00
|
|
|
if (!pl->current)
|
2019-12-28 20:12:02 +00:00
|
|
|
pl->current = playlist_get_first(pl);
|
2014-12-29 21:08:22 +00:00
|
|
|
}
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
// Replace the current playlist entry with playlist contents. Moves the entries
|
|
|
|
// from the given playlist pl, so the entries don't actually need to be copied.
|
2020-03-26 23:57:11 +00:00
|
|
|
static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl,
|
|
|
|
int64_t *start_id, int *num_new_entries)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
2019-12-28 20:12:02 +00:00
|
|
|
if (pl->num_entries) {
|
2014-12-29 21:08:22 +00:00
|
|
|
prepare_playlist(mpctx, pl);
|
|
|
|
struct playlist_entry *new = pl->current;
|
2016-01-06 21:40:55 +00:00
|
|
|
if (mpctx->playlist->current)
|
|
|
|
playlist_add_redirect(pl, mpctx->playlist->current->filename);
|
2020-03-26 23:57:11 +00:00
|
|
|
*num_new_entries = pl->num_entries;
|
|
|
|
*start_id = playlist_transfer_entries(mpctx->playlist, pl);
|
2013-11-03 18:22:13 +00:00
|
|
|
// current entry is replaced
|
2013-10-29 21:38:29 +00:00
|
|
|
if (mpctx->playlist->current)
|
|
|
|
playlist_remove(mpctx->playlist, mpctx->playlist->current);
|
2014-09-02 20:16:20 +00:00
|
|
|
if (new)
|
|
|
|
mpctx->playlist->current = new;
|
2013-10-29 21:38:29 +00:00
|
|
|
} else {
|
|
|
|
MP_WARN(mpctx, "Empty playlist!\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-23 19:54:04 +00:00
|
|
|
static void process_hooks(struct MPContext *mpctx, char *name)
|
command: add a mechanism to allow scripts to intercept file loads
A vague idea to get something similar what libquvi did.
Undocumented because it might change a lot, or even be removed. To give
an idea what it does, a Lua script could do the following:
-- type ID priority
mp.commandv("hook_add", "on_load", 0, 0)
mp.register_script_message("hook_run", function(param, param2)
-- param is "0", the user-chosen ID from the hook_add command
-- param2 is the magic value that has to be passed to finish
-- the hook
mp.resume_all()
-- do something, maybe set options that are reset on end:
mp.set_property("file-local-options/name", "value")
-- or change the URL that's being opened:
local url = mp.get_property("stream-open-filename")
mp.set_property("stream-open-filename", url .. ".png")
-- let the player (or the next script) continue
mp.commandv("hook_ack", param2)
end)
2014-10-15 21:09:53 +00:00
|
|
|
{
|
2018-03-23 19:54:04 +00:00
|
|
|
mp_hook_start(mpctx, name);
|
command: add a mechanism to allow scripts to intercept file loads
A vague idea to get something similar what libquvi did.
Undocumented because it might change a lot, or even be removed. To give
an idea what it does, a Lua script could do the following:
-- type ID priority
mp.commandv("hook_add", "on_load", 0, 0)
mp.register_script_message("hook_run", function(param, param2)
-- param is "0", the user-chosen ID from the hook_add command
-- param2 is the magic value that has to be passed to finish
-- the hook
mp.resume_all()
-- do something, maybe set options that are reset on end:
mp.set_property("file-local-options/name", "value")
-- or change the URL that's being opened:
local url = mp.get_property("stream-open-filename")
mp.set_property("stream-open-filename", url .. ".png")
-- let the player (or the next script) continue
mp.commandv("hook_ack", param2)
end)
2014-10-15 21:09:53 +00:00
|
|
|
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
while (!mp_hook_test_completion(mpctx, name)) {
|
2015-02-04 17:25:40 +00:00
|
|
|
mp_idle(mpctx);
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
|
client API: provide ways to finish property changes on file changes
When the current file changes (or rather, when starting/finishing
playback of a playlist entry), clients tend to have the problem that
it's hard to tell whether a property change notification (via
mpv_observe_property() and mechanisms layered on top of it) is from the
previous or new playlist entry. The previous commit probably helps, but
all the asynchronity is still a bit unhelpful.
Try to make this better by adding new hooks, that are run before/after
playback init/deinit. This is similar to the existing hooks, except
they're outside of "initialized" playback, which excludes that you might
accidentally get an overlap between the current and the previous/next
playlist entry.
That still doesn't seem quite enough, since normally, property change
notifications come after the hook event. So basically a client would
have to explicitly "drain" the event queue within the hook, and make the
hook continue only after that is done. Knowing when property
notifications are done is another asynchronous nightmare (how exactly it
works keeps changing within client.c, and an API user probably can't
tell anymore when all pending properties are truly done). So introduce
another guarantee: properties that were changed before the hook happens
will be returned before the hook event is returned. That means the
client will have received all pending property notifications from the
previous playlist entry (or whatever) before the hook is entered.
As another minor complication, we shouldn't just keep the hook pending
until _all_ property notifications are done, since the client's hook
could produce new ones. (Or just consider things like the demuxer thread
hammering the client with cache update events, while the "on_preloaded"
hook is run.) So there is some extra untested, fragile logic in client.c
to handle this (the waiting_for_hook flag).
This probably works, but was barely tested. Not sure if this helps
anyone, but I think it's fine for my own purposes. (I really hated this
aspect of the API whenever I used it myself.)
2020-03-07 01:52:10 +00:00
|
|
|
// We have no idea what blocks a hook, so just do a full abort. This
|
|
|
|
// does nothing for hooks that happen outside of playback.
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
if (mpctx->stop_play)
|
|
|
|
mp_abort_playback_async(mpctx);
|
|
|
|
}
|
2015-02-04 17:25:40 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
// to be run on a worker thread, locked (temporarily unlocks core)
|
2014-03-25 01:05:48 +00:00
|
|
|
static void load_chapters(struct MPContext *mpctx)
|
|
|
|
{
|
2016-02-15 20:03:51 +00:00
|
|
|
struct demuxer *src = mpctx->demuxer;
|
2014-11-02 15:47:23 +00:00
|
|
|
bool free_src = false;
|
|
|
|
char *chapter_file = mpctx->opts->chapter_file;
|
|
|
|
if (chapter_file && chapter_file[0]) {
|
2018-05-07 18:36:17 +00:00
|
|
|
chapter_file = talloc_strdup(NULL, chapter_file);
|
|
|
|
mp_core_unlock(mpctx);
|
stream, demux: redo origin policy thing
mpv has a very weak and very annoying policy that determines whether a
playlist should be used or not. For example, if you play a remote
playlist, you usually don't want it to be able to read local filesystem
entries. (Although for a media player the impact is small I guess.)
It's weak and annoying as in that it does not prevent certain cases
which could be interpreted as bad in some cases, such as allowing
playlists on the local filesystem to reference remote URLs. It probably
barely makes sense, but we just want to exclude some other "definitely
not a good idea" things, all while playlists generally just work, so
whatever.
The policy is:
- from the command line anything is played
- local playlists can reference anything except "unsafe" streams
("unsafe" means special stream inputs like libavfilter graphs)
- remote playlists can reference only remote URLs
- things like "memory://" and archives are "transparent" to this
This commit does... something. It replaces the weird stream flags with a
slightly clearer "origin" value, which is now consequently passed down
and used everywhere. It fixes some deviations from the described policy.
I wanted to force archives to reference only content within them, but
this would probably have been more complicated (or required different
abstractions), and I'm too lazy to figure it out, so archives are now
"transparent" (playlists within archives behave the same outside).
There may be a lot of bugs in this.
This is unfortunately a very noisy commit because:
- every stream open call now needs to pass the origin
- so does every demuxer open call (=> params param. gets mandatory)
- most stream were changed to provide the "origin" value
- the origin value needed to be passed along in a lot of places
- I was too lazy to split the commit
Fixes: #7274
2019-12-20 08:41:42 +00:00
|
|
|
struct demuxer_params p = {.stream_flags = STREAM_ORIGIN_DIRECT};
|
|
|
|
struct demuxer *demux = demux_open_url(chapter_file, &p,
|
2018-05-19 15:06:00 +00:00
|
|
|
mpctx->playback_abort,
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
mpctx->global);
|
2018-05-07 18:36:17 +00:00
|
|
|
mp_core_lock(mpctx);
|
2015-06-24 19:26:06 +00:00
|
|
|
if (demux) {
|
|
|
|
src = demux;
|
|
|
|
free_src = true;
|
2014-11-02 15:47:23 +00:00
|
|
|
}
|
|
|
|
talloc_free(mpctx->chapters);
|
|
|
|
mpctx->chapters = NULL;
|
2018-05-07 18:36:17 +00:00
|
|
|
talloc_free(chapter_file);
|
2014-11-02 15:47:23 +00:00
|
|
|
}
|
|
|
|
if (src && !mpctx->chapters) {
|
|
|
|
talloc_free(mpctx->chapters);
|
2014-11-02 16:20:04 +00:00
|
|
|
mpctx->num_chapters = src->num_chapters;
|
|
|
|
mpctx->chapters = demux_copy_chapter_data(src->chapters, src->num_chapters);
|
2015-11-16 21:47:17 +00:00
|
|
|
if (mpctx->opts->rebase_start_time) {
|
|
|
|
for (int n = 0; n < mpctx->num_chapters; n++)
|
|
|
|
mpctx->chapters[n].pts -= src->start_time;
|
|
|
|
}
|
2014-03-25 01:05:48 +00:00
|
|
|
}
|
2015-06-24 19:26:06 +00:00
|
|
|
if (free_src)
|
2018-05-19 15:06:00 +00:00
|
|
|
demux_cancel_and_free(src);
|
2014-03-25 01:05:48 +00:00
|
|
|
}
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
static void load_per_file_options(m_config_t *conf,
|
|
|
|
struct playlist_param *params,
|
|
|
|
int params_count)
|
|
|
|
{
|
|
|
|
for (int n = 0; n < params_count; n++) {
|
2017-07-02 11:00:22 +00:00
|
|
|
m_config_set_option_cli(conf, params[n].name, params[n].value,
|
2019-11-10 22:53:57 +00:00
|
|
|
M_SETOPT_BACKUP);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-18 18:02:50 +00:00
|
|
|
static void *open_demux_thread(void *ctx)
|
2014-10-06 19:20:38 +00:00
|
|
|
{
|
2017-01-18 18:02:50 +00:00
|
|
|
struct MPContext *mpctx = ctx;
|
|
|
|
|
2018-03-01 20:47:56 +00:00
|
|
|
mpthread_set_name("opener");
|
|
|
|
|
2015-08-03 23:01:09 +00:00
|
|
|
struct demuxer_params p = {
|
2017-01-18 18:02:50 +00:00
|
|
|
.force_format = mpctx->open_format,
|
|
|
|
.stream_flags = mpctx->open_url_flags,
|
2018-09-01 14:06:41 +00:00
|
|
|
.stream_record = true,
|
2019-06-07 15:09:22 +00:00
|
|
|
.is_top_level = true,
|
2015-08-03 23:01:09 +00:00
|
|
|
};
|
2019-09-20 16:13:16 +00:00
|
|
|
struct demuxer *demux =
|
2017-01-18 18:02:50 +00:00
|
|
|
demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global);
|
2019-09-20 16:13:16 +00:00
|
|
|
mpctx->open_res_demuxer = demux;
|
2017-01-18 18:02:50 +00:00
|
|
|
|
2019-09-20 16:13:16 +00:00
|
|
|
if (demux) {
|
2017-01-18 18:02:50 +00:00
|
|
|
MP_VERBOSE(mpctx, "Opening done: %s\n", mpctx->open_url);
|
2019-09-20 16:13:16 +00:00
|
|
|
|
2019-09-29 00:24:29 +00:00
|
|
|
if (mpctx->open_for_prefetch && !demux->fully_read) {
|
|
|
|
int num_streams = demux_get_num_stream(demux);
|
|
|
|
for (int n = 0; n < num_streams; n++) {
|
|
|
|
struct sh_stream *sh = demux_get_stream(demux, n);
|
|
|
|
demuxer_select_track(demux, sh, MP_NOPTS_VALUE, true);
|
|
|
|
}
|
2019-09-20 16:13:16 +00:00
|
|
|
|
2019-09-29 00:30:04 +00:00
|
|
|
demux_set_wakeup_cb(demux, wakeup_demux, mpctx);
|
|
|
|
demux_start_thread(demux);
|
2019-09-29 00:24:29 +00:00
|
|
|
demux_start_prefetch(demux);
|
|
|
|
}
|
2017-01-18 18:02:50 +00:00
|
|
|
} else {
|
|
|
|
MP_VERBOSE(mpctx, "Opening failed or was aborted: %s\n", mpctx->open_url);
|
|
|
|
|
2015-08-28 18:51:29 +00:00
|
|
|
if (p.demuxer_failed) {
|
2017-01-18 18:02:50 +00:00
|
|
|
mpctx->open_res_error = MPV_ERROR_UNKNOWN_FORMAT;
|
2015-08-28 18:51:29 +00:00
|
|
|
} else {
|
2017-01-18 18:02:50 +00:00
|
|
|
mpctx->open_res_error = MPV_ERROR_LOADING_FAILED;
|
2015-08-28 18:51:29 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-18 18:02:50 +00:00
|
|
|
|
|
|
|
atomic_store(&mpctx->open_done, true);
|
|
|
|
mp_wakeup_core(mpctx);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cancel_open(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
if (mpctx->open_cancel)
|
|
|
|
mp_cancel_trigger(mpctx->open_cancel);
|
|
|
|
|
|
|
|
if (mpctx->open_active)
|
|
|
|
pthread_join(mpctx->open_thread, NULL);
|
|
|
|
mpctx->open_active = false;
|
|
|
|
|
2018-05-19 15:06:00 +00:00
|
|
|
if (mpctx->open_res_demuxer)
|
|
|
|
demux_cancel_and_free(mpctx->open_res_demuxer);
|
|
|
|
mpctx->open_res_demuxer = NULL;
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
|
2017-01-18 18:02:50 +00:00
|
|
|
TA_FREEP(&mpctx->open_cancel);
|
|
|
|
TA_FREEP(&mpctx->open_url);
|
|
|
|
TA_FREEP(&mpctx->open_format);
|
|
|
|
|
|
|
|
atomic_store(&mpctx->open_done, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup all the field to open this url, and make sure a thread is running.
|
2019-09-29 00:24:29 +00:00
|
|
|
static void start_open(struct MPContext *mpctx, char *url, int url_flags,
|
|
|
|
bool for_prefetch)
|
2017-01-18 18:02:50 +00:00
|
|
|
{
|
|
|
|
cancel_open(mpctx);
|
|
|
|
|
|
|
|
assert(!mpctx->open_active);
|
|
|
|
assert(!mpctx->open_cancel);
|
|
|
|
assert(!mpctx->open_res_demuxer);
|
|
|
|
assert(!atomic_load(&mpctx->open_done));
|
|
|
|
|
|
|
|
mpctx->open_cancel = mp_cancel_new(NULL);
|
|
|
|
mpctx->open_url = talloc_strdup(NULL, url);
|
|
|
|
mpctx->open_format = talloc_strdup(NULL, mpctx->opts->demuxer_name);
|
|
|
|
mpctx->open_url_flags = url_flags;
|
2019-09-29 00:30:04 +00:00
|
|
|
mpctx->open_for_prefetch = for_prefetch && mpctx->opts->demuxer_thread;
|
2017-01-18 18:02:50 +00:00
|
|
|
|
|
|
|
if (pthread_create(&mpctx->open_thread, NULL, open_demux_thread, mpctx)) {
|
|
|
|
cancel_open(mpctx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
mpctx->open_active = true;
|
2014-10-06 19:20:38 +00:00
|
|
|
}
|
|
|
|
|
2015-02-20 19:28:23 +00:00
|
|
|
static void open_demux_reentrant(struct MPContext *mpctx)
|
2014-10-06 19:20:38 +00:00
|
|
|
{
|
2017-01-18 18:02:50 +00:00
|
|
|
char *url = mpctx->stream_open_filename;
|
|
|
|
|
|
|
|
if (mpctx->open_active) {
|
|
|
|
bool done = atomic_load(&mpctx->open_done);
|
|
|
|
bool failed = done && !mpctx->open_res_demuxer;
|
|
|
|
bool correct_url = strcmp(mpctx->open_url, url) == 0;
|
|
|
|
|
|
|
|
if (correct_url && !failed) {
|
|
|
|
MP_VERBOSE(mpctx, "Using prefetched/prefetching URL.\n");
|
|
|
|
} else if (correct_url && failed) {
|
|
|
|
MP_VERBOSE(mpctx, "Prefetched URL failed, retrying.\n");
|
|
|
|
cancel_open(mpctx);
|
|
|
|
} else {
|
2017-01-19 06:56:49 +00:00
|
|
|
if (done) {
|
|
|
|
MP_VERBOSE(mpctx, "Dropping finished prefetch of wrong URL.\n");
|
|
|
|
} else {
|
2017-10-23 08:53:28 +00:00
|
|
|
MP_VERBOSE(mpctx, "Aborting ongoing prefetch of wrong URL.\n");
|
2017-01-19 06:56:49 +00:00
|
|
|
}
|
2017-01-18 18:02:50 +00:00
|
|
|
cancel_open(mpctx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mpctx->open_active)
|
2019-09-29 00:24:29 +00:00
|
|
|
start_open(mpctx, url, mpctx->playing->stream_flags, false);
|
2017-01-18 18:02:50 +00:00
|
|
|
|
|
|
|
// User abort should cancel the opener now.
|
2018-05-19 15:06:00 +00:00
|
|
|
mp_cancel_set_parent(mpctx->open_cancel, mpctx->playback_abort);
|
2017-01-18 18:02:50 +00:00
|
|
|
|
|
|
|
while (!atomic_load(&mpctx->open_done)) {
|
|
|
|
mp_idle(mpctx);
|
|
|
|
|
|
|
|
if (mpctx->stop_play)
|
|
|
|
mp_abort_playback_async(mpctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mpctx->open_res_demuxer) {
|
|
|
|
mpctx->demuxer = mpctx->open_res_demuxer;
|
|
|
|
mpctx->open_res_demuxer = NULL;
|
2018-05-19 15:06:00 +00:00
|
|
|
mp_cancel_set_parent(mpctx->demuxer->cancel, mpctx->playback_abort);
|
2014-10-28 14:30:27 +00:00
|
|
|
} else {
|
2017-01-18 18:02:50 +00:00
|
|
|
mpctx->error_playing = mpctx->open_res_error;
|
2014-10-28 14:30:27 +00:00
|
|
|
}
|
2017-01-18 18:02:50 +00:00
|
|
|
|
|
|
|
cancel_open(mpctx); // cleanup
|
|
|
|
}
|
|
|
|
|
|
|
|
void prefetch_next(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
if (!mpctx->opts->prefetch_open)
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct playlist_entry *new_entry = mp_next_file(mpctx, +1, false, false);
|
|
|
|
if (new_entry && !mpctx->open_active && new_entry->filename) {
|
|
|
|
MP_VERBOSE(mpctx, "Prefetching: %s\n", new_entry->filename);
|
2019-09-29 00:24:29 +00:00
|
|
|
start_open(mpctx, new_entry->filename, new_entry->stream_flags, true);
|
2017-01-18 18:02:50 +00:00
|
|
|
}
|
2014-10-06 19:20:38 +00:00
|
|
|
}
|
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
// Destroy the complex filter, and remove the references to the filter pads.
|
|
|
|
// (Call cleanup_deassociated_complex_filters() to close decoders/VO/AO
|
|
|
|
// that are not connected anymore due to this.)
|
|
|
|
static void deassociate_complex_filters(struct MPContext *mpctx)
|
2016-02-05 22:19:56 +00:00
|
|
|
{
|
2017-08-12 21:08:48 +00:00
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++)
|
|
|
|
mpctx->tracks[n]->sink = NULL;
|
|
|
|
if (mpctx->vo_chain)
|
|
|
|
mpctx->vo_chain->filter_src = NULL;
|
|
|
|
if (mpctx->ao_chain)
|
|
|
|
mpctx->ao_chain->filter_src = NULL;
|
2018-01-26 03:36:47 +00:00
|
|
|
TA_FREEP(&mpctx->lavfi);
|
|
|
|
TA_FREEP(&mpctx->lavfi_graph);
|
2017-08-12 21:08:48 +00:00
|
|
|
}
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
// Close all decoders and sinks (AO/VO) that are not connected to either
|
|
|
|
// a track or a filter pad.
|
|
|
|
static void cleanup_deassociated_complex_filters(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *track = mpctx->tracks[n];
|
|
|
|
if (!(track->sink || track->vo_c || track->ao_c)) {
|
2018-01-29 05:18:33 +00:00
|
|
|
if (track->dec && !track->vo_c && !track->ao_c) {
|
video: make decoder wrapper a filter
Move dec_video.c to filters/f_decoder_wrapper.c. It essentially becomes
a source filter. vd.h mostly disappears, because mp_filter takes care of
the dataflow, but its remains are in struct mp_decoder_fns.
One goal is to simplify dataflow by letting the filter framework handle
it (or more accurately, using its conventions). One result is that the
decode calls disappear from video.c, because we simply connect the
decoder wrapper and the filter chain with mp_pin_connect().
Another goal is to eventually remove the code duplication between the
audio and video paths for this. This commit prepares for this by trying
to make f_decoder_wrapper.c extensible, so it can be used for audio as
well later.
Decoder framedropping changes a bit. It doesn't seem to be worse than
before, and it's an obscure feature, so I'm content with its new state.
Some special code that was apparently meant to avoid dropping too many
frames in a row is removed, though.
I'm not sure how the source code tree should be organized. For one,
video/decode/vd_lavc.c is the only file in its directory, which is a bit
annoying.
2018-01-28 09:08:45 +00:00
|
|
|
talloc_free(track->dec->f);
|
2018-02-02 16:22:05 +00:00
|
|
|
track->dec = NULL;
|
2017-08-12 21:08:48 +00:00
|
|
|
}
|
|
|
|
track->selected = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
video: make decoder wrapper a filter
Move dec_video.c to filters/f_decoder_wrapper.c. It essentially becomes
a source filter. vd.h mostly disappears, because mp_filter takes care of
the dataflow, but its remains are in struct mp_decoder_fns.
One goal is to simplify dataflow by letting the filter framework handle
it (or more accurately, using its conventions). One result is that the
decode calls disappear from video.c, because we simply connect the
decoder wrapper and the filter chain with mp_pin_connect().
Another goal is to eventually remove the code duplication between the
audio and video paths for this. This commit prepares for this by trying
to make f_decoder_wrapper.c extensible, so it can be used for audio as
well later.
Decoder framedropping changes a bit. It doesn't seem to be worse than
before, and it's an obscure feature, so I'm content with its new state.
Some special code that was apparently meant to avoid dropping too many
frames in a row is removed, though.
I'm not sure how the source code tree should be organized. For one,
video/decode/vd_lavc.c is the only file in its directory, which is a bit
annoying.
2018-01-28 09:08:45 +00:00
|
|
|
if (mpctx->vo_chain && !mpctx->vo_chain->dec_src &&
|
2017-08-12 21:08:48 +00:00
|
|
|
!mpctx->vo_chain->filter_src)
|
|
|
|
{
|
|
|
|
uninit_video_chain(mpctx);
|
|
|
|
}
|
2018-01-29 05:18:33 +00:00
|
|
|
if (mpctx->ao_chain && !mpctx->ao_chain->dec_src &&
|
2017-08-12 21:08:48 +00:00
|
|
|
!mpctx->ao_chain->filter_src)
|
|
|
|
{
|
|
|
|
uninit_audio_chain(mpctx);
|
|
|
|
}
|
|
|
|
}
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2018-02-02 16:45:01 +00:00
|
|
|
static void kill_outputs(struct MPContext *mpctx, struct track *track)
|
|
|
|
{
|
|
|
|
if (track->vo_c || track->ao_c) {
|
|
|
|
MP_VERBOSE(mpctx, "deselecting track %d for lavfi-complex option\n",
|
|
|
|
track->user_tid);
|
|
|
|
mp_switch_track(mpctx, track->type, NULL, 0);
|
|
|
|
}
|
|
|
|
assert(!(track->vo_c || track->ao_c));
|
|
|
|
}
|
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
// >0: changed, 0: no change, -1: error
|
|
|
|
static int reinit_complex_filters(struct MPContext *mpctx, bool force_uninit)
|
|
|
|
{
|
|
|
|
char *graph = mpctx->opts->lavfi_complex;
|
|
|
|
bool have_graph = graph && graph[0] && !force_uninit;
|
|
|
|
if (have_graph && mpctx->lavfi &&
|
2018-01-26 03:36:47 +00:00
|
|
|
strcmp(graph, mpctx->lavfi_graph) == 0 &&
|
|
|
|
!mp_filter_has_failed(mpctx->lavfi))
|
2017-08-12 21:08:48 +00:00
|
|
|
return 0;
|
|
|
|
if (!mpctx->lavfi && !have_graph)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
// Deassociate the old filter pads. We leave both sources (tracks) and
|
|
|
|
// sinks (AO/VO) "dangling", connected to neither track or filter pad.
|
|
|
|
// Later, we either reassociate them with new pads, or uninit them if
|
|
|
|
// they are still dangling. This avoids too interruptive actions like
|
|
|
|
// recreating the VO.
|
|
|
|
deassociate_complex_filters(mpctx);
|
|
|
|
|
|
|
|
bool success = false;
|
|
|
|
if (!have_graph) {
|
|
|
|
success = true; // normal full removal of graph
|
|
|
|
goto done;
|
|
|
|
}
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2018-01-26 03:36:47 +00:00
|
|
|
struct mp_lavfi *l =
|
|
|
|
mp_lavfi_create_graph(mpctx->filter_root, 0, false, NULL, graph);
|
|
|
|
if (!l)
|
2017-08-12 21:08:48 +00:00
|
|
|
goto done;
|
2018-01-26 03:36:47 +00:00
|
|
|
mpctx->lavfi = l->f;
|
|
|
|
mpctx->lavfi_graph = talloc_strdup(NULL, graph);
|
2016-02-10 21:08:47 +00:00
|
|
|
|
2018-01-26 03:36:47 +00:00
|
|
|
mp_filter_set_error_handler(mpctx->lavfi, mpctx->filter_root);
|
|
|
|
|
|
|
|
for (int n = 0; n < mpctx->lavfi->num_pins; n++)
|
|
|
|
mp_pin_disconnect(mpctx->lavfi->pins[n]);
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2018-01-26 03:36:47 +00:00
|
|
|
struct mp_pin *pad = mp_filter_get_named_pin(mpctx->lavfi, "vo");
|
|
|
|
if (pad && mp_pin_get_dir(pad) == MP_PIN_OUT) {
|
2018-02-02 16:45:01 +00:00
|
|
|
if (mpctx->vo_chain && mpctx->vo_chain->track)
|
|
|
|
kill_outputs(mpctx, mpctx->vo_chain->track);
|
|
|
|
if (!mpctx->vo_chain) {
|
2017-08-12 21:08:48 +00:00
|
|
|
reinit_video_chain_src(mpctx, NULL);
|
|
|
|
if (!mpctx->vo_chain)
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
struct vo_chain *vo_c = mpctx->vo_chain;
|
2018-02-02 16:45:01 +00:00
|
|
|
assert(!vo_c->track);
|
2017-08-12 21:08:48 +00:00
|
|
|
vo_c->filter_src = pad;
|
video: make decoder wrapper a filter
Move dec_video.c to filters/f_decoder_wrapper.c. It essentially becomes
a source filter. vd.h mostly disappears, because mp_filter takes care of
the dataflow, but its remains are in struct mp_decoder_fns.
One goal is to simplify dataflow by letting the filter framework handle
it (or more accurately, using its conventions). One result is that the
decode calls disappear from video.c, because we simply connect the
decoder wrapper and the filter chain with mp_pin_connect().
Another goal is to eventually remove the code duplication between the
audio and video paths for this. This commit prepares for this by trying
to make f_decoder_wrapper.c extensible, so it can be used for audio as
well later.
Decoder framedropping changes a bit. It doesn't seem to be worse than
before, and it's an obscure feature, so I'm content with its new state.
Some special code that was apparently meant to avoid dropping too many
frames in a row is removed, though.
I'm not sure how the source code tree should be organized. For one,
video/decode/vd_lavc.c is the only file in its directory, which is a bit
annoying.
2018-01-28 09:08:45 +00:00
|
|
|
mp_pin_connect(vo_c->filter->f->pins[0], vo_c->filter_src);
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
|
|
|
|
2018-01-26 03:36:47 +00:00
|
|
|
pad = mp_filter_get_named_pin(mpctx->lavfi, "ao");
|
|
|
|
if (pad && mp_pin_get_dir(pad) == MP_PIN_OUT) {
|
2018-02-02 16:45:01 +00:00
|
|
|
if (mpctx->ao_chain && mpctx->ao_chain->track)
|
|
|
|
kill_outputs(mpctx, mpctx->ao_chain->track);
|
|
|
|
if (!mpctx->ao_chain) {
|
2017-08-12 21:08:48 +00:00
|
|
|
reinit_audio_chain_src(mpctx, NULL);
|
|
|
|
if (!mpctx->ao_chain)
|
|
|
|
goto done;
|
|
|
|
}
|
2018-01-29 05:18:33 +00:00
|
|
|
struct ao_chain *ao_c = mpctx->ao_chain;
|
2018-02-02 16:45:01 +00:00
|
|
|
assert(!ao_c->track);
|
2018-01-29 05:18:33 +00:00
|
|
|
ao_c->filter_src = pad;
|
|
|
|
mp_pin_connect(ao_c->filter->f->pins[0], ao_c->filter_src);
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
2016-02-10 21:08:47 +00:00
|
|
|
|
2016-02-05 22:19:56 +00:00
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *track = mpctx->tracks[n];
|
2018-02-02 16:26:13 +00:00
|
|
|
|
|
|
|
char label[32];
|
|
|
|
char prefix;
|
|
|
|
switch (track->type) {
|
|
|
|
case STREAM_VIDEO: prefix = 'v'; break;
|
|
|
|
case STREAM_AUDIO: prefix = 'a'; break;
|
|
|
|
default: continue;
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
2018-02-02 16:26:13 +00:00
|
|
|
snprintf(label, sizeof(label), "%cid%d", prefix, track->user_tid);
|
|
|
|
|
|
|
|
pad = mp_filter_get_named_pin(mpctx->lavfi, label);
|
|
|
|
if (!pad)
|
|
|
|
continue;
|
|
|
|
if (mp_pin_get_dir(pad) != MP_PIN_IN)
|
|
|
|
continue;
|
|
|
|
assert(!mp_pin_is_connected(pad));
|
|
|
|
|
|
|
|
assert(!track->sink);
|
2018-02-02 16:45:01 +00:00
|
|
|
|
|
|
|
kill_outputs(mpctx, track);
|
|
|
|
|
2018-02-02 16:26:13 +00:00
|
|
|
track->sink = pad;
|
|
|
|
track->selected = true;
|
|
|
|
|
2018-02-02 16:45:01 +00:00
|
|
|
if (!track->dec) {
|
|
|
|
if (track->type == STREAM_VIDEO && !init_video_decoder(mpctx, track))
|
|
|
|
goto done;
|
|
|
|
if (track->type == STREAM_AUDIO && !init_audio_decoder(mpctx, track))
|
|
|
|
goto done;
|
|
|
|
}
|
2018-02-02 16:26:13 +00:00
|
|
|
|
|
|
|
mp_pin_connect(track->sink, track->dec->f->pins[0]);
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
|
|
|
|
2018-01-26 03:36:47 +00:00
|
|
|
// Don't allow unconnected pins. Libavfilter would make the data flow a
|
|
|
|
// real pain anyway.
|
|
|
|
for (int n = 0; n < mpctx->lavfi->num_pins; n++) {
|
|
|
|
struct mp_pin *pin = mpctx->lavfi->pins[n];
|
|
|
|
if (!mp_pin_is_connected(pin)) {
|
|
|
|
MP_ERR(mpctx, "Pad %s is not connected to anything.\n",
|
|
|
|
mp_pin_get_name(pin));
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
success = true;
|
|
|
|
done:
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
if (!success)
|
|
|
|
deassociate_complex_filters(mpctx);
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
cleanup_deassociated_complex_filters(mpctx);
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
if (mpctx->playback_initialized) {
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++)
|
2020-11-19 14:12:04 +00:00
|
|
|
reselect_demux_stream(mpctx, mpctx->tracks[n], false);
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
|
|
|
|
2021-12-02 16:19:39 +00:00
|
|
|
mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL);
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
return success ? 1 : -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void update_lavfi_complex(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
if (mpctx->playback_initialized) {
|
2017-08-14 12:02:13 +00:00
|
|
|
if (reinit_complex_filters(mpctx, false) != 0)
|
|
|
|
issue_refresh_seek(mpctx, MPSEEK_EXACT);
|
2017-08-12 21:08:48 +00:00
|
|
|
}
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
|
|
|
|
// Worker thread for loading external files and such. This is needed to avoid
|
|
|
|
// freezing the core when waiting for network while loading these.
|
|
|
|
static void load_external_opts_thread(void *p)
|
|
|
|
{
|
|
|
|
void **a = p;
|
|
|
|
struct MPContext *mpctx = a[0];
|
|
|
|
struct mp_waiter *waiter = a[1];
|
|
|
|
|
|
|
|
mp_core_lock(mpctx);
|
|
|
|
|
|
|
|
load_chapters(mpctx);
|
|
|
|
open_external_files(mpctx, mpctx->opts->audio_files, STREAM_AUDIO);
|
|
|
|
open_external_files(mpctx, mpctx->opts->sub_name, STREAM_SUB);
|
2020-09-27 22:12:52 +00:00
|
|
|
open_external_files(mpctx, mpctx->opts->coverart_files, STREAM_VIDEO);
|
2018-05-07 18:36:17 +00:00
|
|
|
open_external_files(mpctx, mpctx->opts->external_files, STREAM_TYPE_COUNT);
|
player: make various commands for managing external tracks abortable
Until now, they could be aborted only by ending playback, and calling
mpv_abort_async_command didn't do anything.
This requires furthering the mess how playback abort is done. The main
reason why mp_cancel exists at all is to avoid that a "frozen" demuxer
(blocked on network I/O or whatever) cannot freeze the core. The core
should always get its way. Previously, there was a single mp_cancel
handle, that could be signaled, and all demuxers would unfreeze. With
external files, we might want to abort loading of a certain external
file, which automatically means they need a separate mp_cancel. So give
every demuxer its own mp_cancel, and "slave" it to whatever parent
mp_cancel handles aborting.
Since the mpv demuxer API conflates creating the demuxer and reading the
file headers, mp_cancel strictly need to be created before the demuxer
is created (or we couldn't abort loading). Although we give every
demuxer its own mp_cancel (as "enforced" by cancel_and_free_demuxer),
it's still rather messy to create/destroy it along with the demuxer.
2018-05-18 19:38:17 +00:00
|
|
|
autoload_external_files(mpctx, mpctx->playback_abort);
|
2018-05-07 18:36:17 +00:00
|
|
|
|
|
|
|
mp_waiter_wakeup(waiter, 0);
|
|
|
|
mp_wakeup_core(mpctx);
|
|
|
|
mp_core_unlock(mpctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void load_external_opts(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
struct mp_waiter wait = MP_WAITER_INITIALIZER;
|
|
|
|
|
|
|
|
void *a[] = {mpctx, &wait};
|
|
|
|
if (!mp_thread_pool_queue(mpctx->thread_pool, load_external_opts_thread, a)) {
|
|
|
|
mpctx->stop_play = PT_ERROR;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
while (!mp_waiter_poll(&wait)) {
|
2018-05-07 18:36:17 +00:00
|
|
|
mp_idle(mpctx);
|
|
|
|
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
if (mpctx->stop_play)
|
|
|
|
mp_abort_playback_async(mpctx);
|
|
|
|
}
|
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
mp_waiter_wait(&wait);
|
|
|
|
}
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
// Start playing the current playlist entry.
|
|
|
|
// Handle initialization and deinitialization.
|
|
|
|
static void play_current_file(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
struct MPOpts *opts = mpctx->opts;
|
|
|
|
double playback_start = -1e100;
|
|
|
|
|
2018-05-19 16:25:54 +00:00
|
|
|
assert(mpctx->stop_play);
|
client API: provide ways to finish property changes on file changes
When the current file changes (or rather, when starting/finishing
playback of a playlist entry), clients tend to have the problem that
it's hard to tell whether a property change notification (via
mpv_observe_property() and mechanisms layered on top of it) is from the
previous or new playlist entry. The previous commit probably helps, but
all the asynchronity is still a bit unhelpful.
Try to make this better by adding new hooks, that are run before/after
playback init/deinit. This is similar to the existing hooks, except
they're outside of "initialized" playback, which excludes that you might
accidentally get an overlap between the current and the previous/next
playlist entry.
That still doesn't seem quite enough, since normally, property change
notifications come after the hook event. So basically a client would
have to explicitly "drain" the event queue within the hook, and make the
hook continue only after that is done. Knowing when property
notifications are done is another asynchronous nightmare (how exactly it
works keeps changing within client.c, and an API user probably can't
tell anymore when all pending properties are truly done). So introduce
another guarantee: properties that were changed before the hook happens
will be returned before the hook event is returned. That means the
client will have received all pending property notifications from the
previous playlist entry (or whatever) before the hook is entered.
As another minor complication, we shouldn't just keep the hook pending
until _all_ property notifications are done, since the client's hook
could produce new ones. (Or just consider things like the demuxer thread
hammering the client with cache update events, while the "on_preloaded"
hook is run.) So there is some extra untested, fragile logic in client.c
to handle this (the waiting_for_hook flag).
This probably works, but was barely tested. Not sure if this helps
anyone, but I think it's fine for my own purposes. (I really hated this
aspect of the API whenever I used it myself.)
2020-03-07 01:52:10 +00:00
|
|
|
mpctx->stop_play = 0;
|
|
|
|
|
|
|
|
process_hooks(mpctx, "on_before_start_file");
|
player: fix subtle idle mode differences on early program start
If the user manages to run a "loadfile x append" command before the loop
in mp_play_files() is entered, then the player could start playing
these. This isn't expected, because appending files to the playlist in
idle mode does not normally start playback. It could happen because
there is a short time window where commands are processed before the
loop is entered (such as running the command when a script is loaded).
The idle mode semantics are pretty weird: if files were provided in
advance (on the command line), then these should be played immediately.
But if idle mode was already entered, and something is appended to the
playlist using "append", i.e. without explicitly triggering playback,
then it should remain in idle mode.
Try to follow this by redefining PT_STOP to strictly mean idle mode.
Remove the playlist->current check from idle_loop(), since only the
stop_play field counts now (cf. what mp_set_playlist_entry() does).
This actually introduces the possibility that playlist->current, and
with it playlist-pos, are set to something, even though playback is not
active or being started. Previously, this was only possible during state
transitions, such as when changing playlist entries.
Very annoyingly, this means the current way MPV_EVENT_IDLE was sent
doesn't work anymore. Logically, idle mode can be "active" even if
idle_loop() was not entered yet (between the time after mp_initialize()
and before the loop in mp_play_files()). Instead of worrying about this,
redo the "idle-active" property, and deprecate the event.
See: #7543
2020-03-21 13:01:38 +00:00
|
|
|
if (mpctx->stop_play || !mpctx->playlist->current)
|
client API: provide ways to finish property changes on file changes
When the current file changes (or rather, when starting/finishing
playback of a playlist entry), clients tend to have the problem that
it's hard to tell whether a property change notification (via
mpv_observe_property() and mechanisms layered on top of it) is from the
previous or new playlist entry. The previous commit probably helps, but
all the asynchronity is still a bit unhelpful.
Try to make this better by adding new hooks, that are run before/after
playback init/deinit. This is similar to the existing hooks, except
they're outside of "initialized" playback, which excludes that you might
accidentally get an overlap between the current and the previous/next
playlist entry.
That still doesn't seem quite enough, since normally, property change
notifications come after the hook event. So basically a client would
have to explicitly "drain" the event queue within the hook, and make the
hook continue only after that is done. Knowing when property
notifications are done is another asynchronous nightmare (how exactly it
works keeps changing within client.c, and an API user probably can't
tell anymore when all pending properties are truly done). So introduce
another guarantee: properties that were changed before the hook happens
will be returned before the hook event is returned. That means the
client will have received all pending property notifications from the
previous playlist entry (or whatever) before the hook is entered.
As another minor complication, we shouldn't just keep the hook pending
until _all_ property notifications are done, since the client's hook
could produce new ones. (Or just consider things like the demuxer thread
hammering the client with cache update events, while the "on_preloaded"
hook is run.) So there is some extra untested, fragile logic in client.c
to handle this (the waiting_for_hook flag).
This probably works, but was barely tested. Not sure if this helps
anyone, but I think it's fine for my own purposes. (I really hated this
aspect of the API whenever I used it myself.)
2020-03-07 01:52:10 +00:00
|
|
|
return;
|
2018-05-19 16:25:54 +00:00
|
|
|
|
2020-03-21 16:45:30 +00:00
|
|
|
mpv_event_start_file start_event = {
|
|
|
|
.playlist_entry_id = mpctx->playlist->current->id,
|
|
|
|
};
|
2020-03-26 23:57:11 +00:00
|
|
|
mpv_event_end_file end_event = {
|
|
|
|
.playlist_entry_id = start_event.playlist_entry_id,
|
|
|
|
};
|
2020-03-21 16:45:30 +00:00
|
|
|
|
|
|
|
mp_notify(mpctx, MPV_EVENT_START_FILE, &start_event);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
stream: redo playback abort handling
This mechanism originates from MPlayer's way of dealing with blocking
network, but it's still useful. On opening and closing, mpv waits for
network synchronously, and also some obscure commands and use-cases can
lead to such blocking. In these situations, the stream is asynchronously
forced to stop by "interrupting" it.
The old design interrupting I/O was a bit broken: polling with a
callback, instead of actively interrupting it. Change the direction of
this. There is no callback anymore, and the player calls
mp_cancel_trigger() to force the stream to return.
libavformat (via stream_lavf.c) has the old broken design, and fixing it
would require fixing libavformat, which won't happen so quickly. So we
have to keep that part. But everything above the stream layer is
prepared for a better design, and more sophisticated methods than
mp_cancel_test() could be easily introduced.
There's still one problem: commands are still run in the central
playback loop, which we assume can block on I/O in the worst case.
That's not a problem yet, because we simply mark some commands as being
able to stop playback of the current file ("quit" etc.), so input.c
could abort playback as soon as such a command is queued. But there are
also commands abort playback only conditionally, and the logic for that
is in the playback core and thus "unreachable". For example,
"playlist_next" aborts playback only if there's a next file. We don't
want it to always abort playback.
As a quite ugly hack, abort playback only if at least 2 abort commands
are queued - this pretty much happens only if the core is frozen and
doesn't react to input.
2014-09-13 12:23:08 +00:00
|
|
|
mp_cancel_reset(mpctx->playback_abort);
|
|
|
|
|
2014-10-28 15:19:07 +00:00
|
|
|
mpctx->error_playing = MPV_ERROR_LOADING_FAILED;
|
2013-10-29 21:38:29 +00:00
|
|
|
mpctx->filename = NULL;
|
|
|
|
mpctx->shown_aframes = 0;
|
|
|
|
mpctx->shown_vframes = 0;
|
2014-09-01 19:02:43 +00:00
|
|
|
mpctx->last_chapter_seek = -2;
|
|
|
|
mpctx->last_chapter_pts = MP_NOPTS_VALUE;
|
|
|
|
mpctx->last_chapter = -2;
|
|
|
|
mpctx->paused = false;
|
|
|
|
mpctx->playing_msg_shown = false;
|
|
|
|
mpctx->max_frames = -1;
|
2015-08-10 16:40:16 +00:00
|
|
|
mpctx->video_speed = mpctx->audio_speed = opts->playback_speed;
|
|
|
|
mpctx->speed_factor_a = mpctx->speed_factor_v = 1.0;
|
2015-08-10 16:43:25 +00:00
|
|
|
mpctx->display_sync_error = 0.0;
|
|
|
|
mpctx->display_sync_active = false;
|
2018-05-17 11:13:00 +00:00
|
|
|
// let get_current_time() show 0 as start time (before playback_pts is set)
|
|
|
|
mpctx->last_seek_pts = 0.0;
|
2014-09-01 19:02:43 +00:00
|
|
|
mpctx->seek = (struct seek_params){ 0 };
|
video: rewrite filtering glue code
Get rid of the old vf.c code. Replace it with a generic filtering
framework, which can potentially handle more than just --vf. At least
reimplementing --af with this code is planned.
This changes some --vf semantics (including runtime behavior and the
"vf" command). The most important ones are listed in interface-changes.
vf_convert.c is renamed to f_swscale.c. It is now an internal filter
that can not be inserted by the user manually.
f_lavfi.c is a refactor of player/lavfi.c. The latter will be removed
once --lavfi-complex is reimplemented on top of f_lavfi.c. (which is
conceptually easy, but a big mess due to the data flow changes).
The existing filters are all changed heavily. The data flow of the new
filter framework is different. Especially EOF handling changes - EOF is
now a "frame" rather than a state, and must be passed through exactly
once.
Another major thing is that all filters must support dynamic format
changes. The filter reconfig() function goes away. (This sounds complex,
but since all filters need to handle EOF draining anyway, they can use
the same code, and it removes the mess with reconfig() having to predict
the output format, which completely breaks with libavfilter anyway.)
In addition, there is no automatic format negotiation or conversion.
libavfilter's primitive and insufficient API simply doesn't allow us to
do this in a reasonable way. Instead, filters can use f_autoconvert as
sub-filter, and tell it which formats they support. This filter will in
turn add actual conversion filters, such as f_swscale, to perform
necessary format changes.
vf_vapoursynth.c uses the same basic principle of operation as before,
but with worryingly different details in data flow. Still appears to
work.
The hardware deint filters (vf_vavpp.c, vf_d3d11vpp.c, vf_vdpaupp.c) are
heavily changed. Fortunately, they all used refqueue.c, which is for
sharing the data flow logic (especially for managing future/past
surfaces and such). It turns out it can be used to factor out most of
the data flow. Some of these filters accepted software input. Instead of
having ad-hoc upload code in each filter, surface upload is now
delegated to f_autoconvert, which can use f_hwupload to perform this.
Exporting VO capabilities is still a big mess (mp_stream_info stuff).
The D3D11 code drops the redundant image formats, and all code uses the
hw_subfmt (sw_format in FFmpeg) instead. Although that too seems to be a
big mess for now.
f_async_queue is unused.
2018-01-16 10:53:44 +00:00
|
|
|
mpctx->filter_root = mp_filter_create_root(mpctx->global);
|
2020-03-08 18:37:20 +00:00
|
|
|
mp_filter_graph_set_wakeup_cb(mpctx->filter_root, mp_wakeup_core_cb, mpctx);
|
2020-03-05 20:38:58 +00:00
|
|
|
mp_filter_graph_set_max_run_time(mpctx->filter_root, 0.1);
|
2014-09-01 19:02:43 +00:00
|
|
|
|
|
|
|
reset_playback_state(mpctx);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2014-09-08 22:38:38 +00:00
|
|
|
mpctx->playing = mpctx->playlist->current;
|
player: add a number of new playlist contol commands/properties
Should give a good deal more explicit control and insight over the
player state.
Some feel a bit pointless, and/or expose internal weirdness. However,
it's not like the existing weirdness didn't exist before, or can be made
go away. (In part, the weirdness is because certain in-between states
are visible. Hiding them would make things simpler, but less flexible.)
Maybe this actually gives users a better idea how the API _should_ look
like, too.
On a side note, this tries to really guarantee that mpctx->playing is
set between playback start/end. For that, the loadfile.c changes assume
that mpctx->playing is set (guaranteed by code above the change), and
that playing->filename is set (probably could never be false; was broken
before and actually would have crashed if that could ever happen; in any
case, also add an assert to playlist.c for this).
playlist_entry_to_index() now tolerates playlist_entrys that are not
part of the playlist. This is also needed for mpctx->playing.
2020-03-21 16:08:43 +00:00
|
|
|
assert(mpctx->playing);
|
|
|
|
assert(mpctx->playing->filename);
|
2014-09-08 22:38:38 +00:00
|
|
|
mpctx->playing->reserved += 1;
|
|
|
|
|
2015-12-23 14:49:20 +00:00
|
|
|
mpctx->filename = talloc_strdup(NULL, mpctx->playing->filename);
|
command: add a mechanism to allow scripts to intercept file loads
A vague idea to get something similar what libquvi did.
Undocumented because it might change a lot, or even be removed. To give
an idea what it does, a Lua script could do the following:
-- type ID priority
mp.commandv("hook_add", "on_load", 0, 0)
mp.register_script_message("hook_run", function(param, param2)
-- param is "0", the user-chosen ID from the hook_add command
-- param2 is the magic value that has to be passed to finish
-- the hook
mp.resume_all()
-- do something, maybe set options that are reset on end:
mp.set_property("file-local-options/name", "value")
-- or change the URL that's being opened:
local url = mp.get_property("stream-open-filename")
mp.set_property("stream-open-filename", url .. ".png")
-- let the player (or the next script) continue
mp.commandv("hook_ack", param2)
end)
2014-10-15 21:09:53 +00:00
|
|
|
mpctx->stream_open_filename = mpctx->filename;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2017-01-04 23:07:28 +00:00
|
|
|
mpctx->add_osd_seek_info &= OSD_SEEK_INFO_CURRENT_FILE;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
if (opts->reset_options) {
|
|
|
|
for (int n = 0; opts->reset_options[n]; n++) {
|
|
|
|
const char *opt = opts->reset_options[n];
|
|
|
|
if (opt[0]) {
|
|
|
|
if (strcmp(opt, "all") == 0) {
|
|
|
|
m_config_backup_all_opts(mpctx->mconfig);
|
|
|
|
} else {
|
|
|
|
m_config_backup_opt(mpctx->mconfig, opt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-21 19:45:19 +00:00
|
|
|
mp_load_auto_profiles(mpctx);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2015-05-09 14:21:44 +00:00
|
|
|
mp_load_playback_resume(mpctx, mpctx->filename);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2014-09-08 22:38:38 +00:00
|
|
|
load_per_file_options(mpctx->mconfig, mpctx->playing->params,
|
|
|
|
mpctx->playing->num_params);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2014-09-01 19:02:43 +00:00
|
|
|
mpctx->max_frames = opts->play_frames;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2015-09-20 15:58:02 +00:00
|
|
|
handle_force_window(mpctx, false);
|
2015-09-20 15:05:14 +00:00
|
|
|
|
2019-12-28 20:12:02 +00:00
|
|
|
if (mpctx->playlist->num_entries > 1 ||
|
2019-05-16 15:20:24 +00:00
|
|
|
mpctx->playing->num_redirects)
|
|
|
|
MP_INFO(mpctx, "Playing: %s\n", mpctx->filename);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
assert(mpctx->demuxer == NULL);
|
|
|
|
|
2018-03-23 19:54:04 +00:00
|
|
|
process_hooks(mpctx, "on_load");
|
|
|
|
if (mpctx->stop_play)
|
2014-10-25 18:07:44 +00:00
|
|
|
goto terminate_playback;
|
command: add a mechanism to allow scripts to intercept file loads
A vague idea to get something similar what libquvi did.
Undocumented because it might change a lot, or even be removed. To give
an idea what it does, a Lua script could do the following:
-- type ID priority
mp.commandv("hook_add", "on_load", 0, 0)
mp.register_script_message("hook_run", function(param, param2)
-- param is "0", the user-chosen ID from the hook_add command
-- param2 is the magic value that has to be passed to finish
-- the hook
mp.resume_all()
-- do something, maybe set options that are reset on end:
mp.set_property("file-local-options/name", "value")
-- or change the URL that's being opened:
local url = mp.get_property("stream-open-filename")
mp.set_property("stream-open-filename", url .. ".png")
-- let the player (or the next script) continue
mp.commandv("hook_ack", param2)
end)
2014-10-15 21:09:53 +00:00
|
|
|
|
2015-07-02 12:02:32 +00:00
|
|
|
if (opts->stream_dump && opts->stream_dump[0]) {
|
2016-03-26 19:14:02 +00:00
|
|
|
if (stream_dump(mpctx, mpctx->stream_open_filename) >= 0)
|
2015-07-02 12:02:32 +00:00
|
|
|
mpctx->error_playing = 1;
|
|
|
|
goto terminate_playback;
|
|
|
|
}
|
|
|
|
|
2015-02-20 19:28:23 +00:00
|
|
|
open_demux_reentrant(mpctx);
|
2018-03-23 19:54:04 +00:00
|
|
|
if (!mpctx->stop_play && !mpctx->demuxer) {
|
|
|
|
process_hooks(mpctx, "on_load_fail");
|
|
|
|
if (strcmp(mpctx->stream_open_filename, mpctx->filename) != 0 &&
|
|
|
|
!mpctx->stop_play)
|
|
|
|
{
|
|
|
|
mpctx->error_playing = MPV_ERROR_LOADING_FAILED;
|
|
|
|
open_demux_reentrant(mpctx);
|
|
|
|
}
|
2018-01-02 14:20:53 +00:00
|
|
|
}
|
2016-02-15 20:03:51 +00:00
|
|
|
if (!mpctx->demuxer || mpctx->stop_play)
|
2013-10-29 21:38:29 +00:00
|
|
|
goto terminate_playback;
|
|
|
|
|
|
|
|
if (mpctx->demuxer->playlist) {
|
2014-08-31 17:49:39 +00:00
|
|
|
struct playlist *pl = mpctx->demuxer->playlist;
|
2020-03-26 23:57:11 +00:00
|
|
|
transfer_playlist(mpctx, pl, &end_event.playlist_insert_id,
|
|
|
|
&end_event.playlist_insert_num_entries);
|
2014-11-08 22:03:04 +00:00
|
|
|
mp_notify_property(mpctx, "playlist");
|
2015-06-11 19:39:48 +00:00
|
|
|
mpctx->error_playing = 2;
|
2013-10-29 21:38:29 +00:00
|
|
|
goto terminate_playback;
|
|
|
|
}
|
|
|
|
|
2017-01-18 15:48:47 +00:00
|
|
|
if (mpctx->opts->rebase_start_time)
|
|
|
|
demux_set_ts_offset(mpctx->demuxer, -mpctx->demuxer->start_time);
|
|
|
|
enable_demux_thread(mpctx, mpctx->demuxer);
|
|
|
|
|
2016-02-15 20:03:51 +00:00
|
|
|
add_demuxer_tracks(mpctx, mpctx->demuxer);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2018-05-07 18:36:17 +00:00
|
|
|
load_external_opts(mpctx);
|
|
|
|
if (mpctx->stop_play)
|
|
|
|
goto terminate_playback;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
check_previous_track_selection(mpctx);
|
|
|
|
|
2018-03-23 19:54:04 +00:00
|
|
|
process_hooks(mpctx, "on_preloaded");
|
|
|
|
if (mpctx->stop_play)
|
2016-02-15 15:19:19 +00:00
|
|
|
goto terminate_playback;
|
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
if (reinit_complex_filters(mpctx, false) < 0)
|
2016-02-10 21:08:47 +00:00
|
|
|
goto terminate_playback;
|
2016-02-05 22:19:56 +00:00
|
|
|
|
2020-06-22 20:08:42 +00:00
|
|
|
opts->subs_rend->forced_subs_only_current = (opts->subs_rend->forced_subs_only == 1) ? 1 : 0;
|
|
|
|
|
2015-05-22 19:00:24 +00:00
|
|
|
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
|
2020-04-15 15:03:37 +00:00
|
|
|
for (int i = 0; i < num_ptracks[t]; i++) {
|
2016-02-05 22:19:56 +00:00
|
|
|
struct track *sel = NULL;
|
|
|
|
bool taken = (t == STREAM_VIDEO && mpctx->vo_chain) ||
|
|
|
|
(t == STREAM_AUDIO && mpctx->ao_chain);
|
2017-08-12 21:43:05 +00:00
|
|
|
if (!taken && opts->stream_auto_sel)
|
2016-02-05 22:19:56 +00:00
|
|
|
sel = select_default_track(mpctx, i, t);
|
|
|
|
mpctx->current_track[i][t] = sel;
|
|
|
|
}
|
2015-05-22 19:00:24 +00:00
|
|
|
}
|
2013-12-25 10:24:37 +00:00
|
|
|
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
|
2020-04-15 15:03:37 +00:00
|
|
|
for (int i = 0; i < num_ptracks[t]; i++) {
|
2020-04-13 13:42:52 +00:00
|
|
|
// One track can strictly feed at most 1 decoder
|
2013-12-25 10:24:37 +00:00
|
|
|
struct track *track = mpctx->current_track[i][t];
|
|
|
|
if (track) {
|
2020-09-03 12:13:17 +00:00
|
|
|
if (track->type != STREAM_SUB &&
|
|
|
|
mpctx->encode_lavc_ctx &&
|
|
|
|
!encode_lavc_stream_type_ok(mpctx->encode_lavc_ctx,
|
|
|
|
track->type))
|
|
|
|
{
|
|
|
|
MP_WARN(mpctx, "Disabling %s (not supported by target "
|
|
|
|
"format).\n", stream_type_name(track->type));
|
|
|
|
mpctx->current_track[i][t] = NULL;
|
|
|
|
mark_track_selection(mpctx, i, t, -2); // disable
|
|
|
|
} else if (track->selected) {
|
2013-12-25 10:24:37 +00:00
|
|
|
MP_ERR(mpctx, "Track %d can't be selected twice.\n",
|
|
|
|
track->user_tid);
|
|
|
|
mpctx->current_track[i][t] = NULL;
|
2020-04-13 13:42:52 +00:00
|
|
|
mark_track_selection(mpctx, i, t, -2); // disable
|
2013-12-25 10:24:37 +00:00
|
|
|
} else {
|
|
|
|
track->selected = true;
|
|
|
|
}
|
|
|
|
}
|
2020-04-13 13:42:52 +00:00
|
|
|
|
|
|
|
// Revert selection of unselected tracks to default. This is needed
|
|
|
|
// because track properties have inconsistent behavior.
|
|
|
|
if (!track && opts->stream_id[i][t] >= 0)
|
|
|
|
mark_track_selection(mpctx, i, t, -1); // default
|
2013-12-25 10:24:37 +00:00
|
|
|
}
|
2013-12-23 19:14:54 +00:00
|
|
|
}
|
2016-02-25 21:44:50 +00:00
|
|
|
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++)
|
2020-11-19 14:12:04 +00:00
|
|
|
reselect_demux_stream(mpctx, mpctx->tracks[n], false);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2014-07-16 20:40:21 +00:00
|
|
|
update_demuxer_properties(mpctx);
|
|
|
|
|
2018-07-05 14:12:46 +00:00
|
|
|
update_playback_speed(mpctx);
|
|
|
|
|
|
|
|
reinit_video_chain(mpctx);
|
|
|
|
reinit_audio_chain(mpctx);
|
|
|
|
reinit_sub_all(mpctx);
|
|
|
|
|
2014-03-30 17:05:59 +00:00
|
|
|
if (mpctx->encode_lavc_ctx) {
|
2018-07-05 14:12:46 +00:00
|
|
|
if (mpctx->vo_chain)
|
2018-04-29 18:03:24 +00:00
|
|
|
encode_lavc_expect_stream(mpctx->encode_lavc_ctx, STREAM_VIDEO);
|
2018-07-05 14:12:46 +00:00
|
|
|
if (mpctx->ao_chain)
|
2018-04-29 18:03:24 +00:00
|
|
|
encode_lavc_expect_stream(mpctx->encode_lavc_ctx, STREAM_AUDIO);
|
2014-03-30 17:05:59 +00:00
|
|
|
encode_lavc_set_metadata(mpctx->encode_lavc_ctx,
|
|
|
|
mpctx->demuxer->metadata);
|
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2017-08-12 21:43:05 +00:00
|
|
|
if (!mpctx->vo_chain && !mpctx->ao_chain && opts->stream_auto_sel) {
|
2016-02-01 21:28:47 +00:00
|
|
|
MP_FATAL(mpctx, "No video or audio streams selected.\n");
|
|
|
|
mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY;
|
|
|
|
goto terminate_playback;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mpctx->vo_chain && mpctx->vo_chain->is_coverart) {
|
|
|
|
MP_INFO(mpctx,
|
2021-07-28 15:00:38 +00:00
|
|
|
"Displaying cover art. Use --no-audio-display to prevent this.\n");
|
2016-02-01 21:28:47 +00:00
|
|
|
}
|
|
|
|
|
2016-02-15 14:14:11 +00:00
|
|
|
if (!mpctx->vo_chain)
|
|
|
|
handle_force_window(mpctx, true);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
MP_VERBOSE(mpctx, "Starting playback...\n");
|
|
|
|
|
2016-01-03 14:48:47 +00:00
|
|
|
mpctx->playback_initialized = true;
|
|
|
|
mp_notify(mpctx, MPV_EVENT_FILE_LOADED, NULL);
|
2016-10-02 10:33:34 +00:00
|
|
|
update_screensaver_state(mpctx);
|
2016-01-03 14:48:47 +00:00
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
if (mpctx->max_frames == 0) {
|
2015-07-08 19:31:31 +00:00
|
|
|
if (!mpctx->stop_play)
|
|
|
|
mpctx->stop_play = PT_NEXT_ENTRY;
|
2014-10-28 15:19:07 +00:00
|
|
|
mpctx->error_playing = 0;
|
2013-10-29 21:38:29 +00:00
|
|
|
goto terminate_playback;
|
|
|
|
}
|
|
|
|
|
2019-05-16 14:29:45 +00:00
|
|
|
if (opts->demuxer_cache_wait) {
|
2019-09-29 00:24:29 +00:00
|
|
|
demux_start_prefetch(mpctx->demuxer);
|
|
|
|
|
2019-05-16 14:29:45 +00:00
|
|
|
while (!mpctx->stop_play) {
|
|
|
|
struct demux_reader_state s;
|
|
|
|
demux_get_reader_state(mpctx->demuxer, &s);
|
|
|
|
if (s.idle)
|
|
|
|
break;
|
|
|
|
|
|
|
|
mp_idle(mpctx);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
player: modify/simplify AB-loop behavior
This changes the behavior of the --ab-loop-a/b options. In addition, it
makes it work with backward playback mode.
The most obvious change is that the both the A and B point need to be
set now before any looping happens. Unlike before, unset points don't
implicitly use the start or end of the file. I think the old behavior
was a feature that was explicitly added/wanted. Well, it's gone now.
This is because of 2 reasons:
1. I never liked this feature, and it always got in my way (as user).
2. It's inherently annoying with backward playback mode.
In backward playback mode, the user wants to set A/B in the wrong order.
The ab-loop command will first set A, then B, so if you use this command
during backward playback, A will be set to a higher timestamps than B.
If you switch back to forward playback mode, the loop would stop
working. I want the loop to just continue to work, and the chosen
solution conflicts with the removed feature.
The order issue above _could_ be fixed by also switching the AB-loop
user option values around on direction switch. But there are no other
instances of option changes magically affecting other options, and doing
this would probably lead to unexpected misery (dying from corner cases
and such).
Another solution is sorting the A/B points by timestamps after copying
them from the user options. Then A/B options set in backward mode will
work in forward mode. This is the chosen solution. If you sort the
points, you don't know anymore whether the unset point is supposed to
signify the end or the start of the file.
The AB-loop code is slightly better abstracted now, so it should be easy
to restore the removed feature. It would still require coming up with a
solution for backwards playback, though.
A minor change is that if one point is set and the other is unset, I'm
rendering both the chapter markers and the marker for the set point.
Why? I don't know. My test file had chapters, and I guess I decided this
looked better.
This commit also fixes some subtle and obvious issues that I already
forgot about when I wrote this commit message. It cleans up some minor
code duplication and nonsense too.
Regarding backward playback, the code uses an unsanitary mix of internal
("transformed") and user timestamps. So the play_dir variable appears
more than usual.
To mention one unfixed issue: if you set an AB-loop that is completely
past the end of the file, it will get stuck in an infinite seeking loop
once playback reaches the end of the file. Fixing this reliably seemed
annoying, so the fix is "just don't do this". It's not a hard freeze
anyway.
2019-05-26 23:24:22 +00:00
|
|
|
// (Not get_play_start_pts(), which would always trigger a seek.)
|
|
|
|
double play_start_pts = rel_time_to_abs(mpctx, opts->play_start);
|
Implement backwards playback
See manpage additions. This is a huge hack. You can bet there are shit
tons of bugs. It's literally forcing square pegs into round holes.
Hopefully, the manpage wall of text makes it clear enough that the whole
shit can easily crash and burn. (Although it shouldn't literally crash.
That would be a bug. It possibly _could_ start a fire by entering some
sort of endless loop, not a literal one, just something where it tries
to do work without making progress.)
(Some obvious bugs I simply ignored for this initial version, but
there's a number of potential bugs I can't even imagine. Normal playback
should remain completely unaffected, though.)
How this works is also described in the manpage. Basically, we demux in
reverse, then we decode in reverse, then we render in reverse.
The decoding part is the simplest: just reorder the decoder output. This
weirdly integrates with the timeline/ordered chapter code, which also
has special requirements on feeding the packets to the decoder in a
non-straightforward way (it doesn't conflict, although a bugmessmass
breaks correct slicing of segments, so EDL/ordered chapter playback is
broken in backward direction).
Backward demuxing is pretty involved. In theory, it could be much
easier: simply iterating the usual demuxer output backward. But this
just doesn't fit into our code, so there's a cthulhu nightmare of shit.
To be specific, each stream (audio, video) is reversed separately. At
least this means we can do backward playback within cached content (for
example, you could play backwards in a live stream; on that note, it
disables prefetching, which would lead to losing new live video, but
this could be avoided).
The fuckmess also meant that I didn't bother trying to support
subtitles. Subtitles are a problem because they're "sparse" streams.
They need to be "passively" demuxed: you don't try to read a subtitle
packet, you demux audio and video, and then look whether there was a
subtitle packet. This means to get subtitles for a time range, you need
to know that you demuxed video and audio over this range, which becomes
pretty messy when you demux audio and video backwards separately.
Backward display is the most weird (and potentially buggy) part. To
avoid that we need to touch a LOT of timing code, we negate all
timestamps. The basic idea is that due to the navigation, all
comparisons and subtractions of timestamps keep working, and you don't
need to touch every single of them to "reverse" them.
E.g.:
bool before = pts_a < pts_b;
would need to be:
bool before = forward
? pts_a < pts_b
: pts_a > pts_b;
or:
bool before = pts_a * dir < pts_b * dir;
or if you, as it's implemented now, just do this after decoding:
pts_a *= dir;
pts_b *= dir;
and then in the normal timing/renderer code:
bool before = pts_a < pts_b;
Consequently, we don't need many changes in the latter code. But some
assumptions inhererently true for forward playback may have been broken
anyway. What is mainly needed is fixing places where values are passed
between positive and negative "domains". For example, seeking and
timestamp user display always uses positive timestamps. The main mess is
that it's not obvious which domain a given variable should or does use.
Well, in my tests with a single file, it suddenly started to work when I
did this. I'm honestly surprised that it did, and that I didn't have to
change a single line in the timing code past decoder (just something
minor to make external/cached text subtitles display). I committed it
immediately while avoiding thinking about it. But there really likely
are subtle problems of all sorts.
As far as I'm aware, gstreamer also supports backward playback. When I
looked at this years ago, I couldn't find a way to actually try this,
and I didn't revisit it now. Back then I also read talk slides from the
person who implemented it, and I'm not sure if and which ideas I might
have taken from it. It's possible that the timestamp reversal is
inspired by it, but I didn't check. (I think it claimed that it could
avoid large changes by changing a sign?)
VapourSynth has some sort of reverse function, which provides a backward
view on a video. The function itself is trivial to implement, as
VapourSynth aims to provide random access to video by frame numbers (so
you just request decreasing frame numbers). From what I remember, it
wasn't exactly fluid, but it worked. It's implemented by creating an
index, and seeking to the target on demand, and a bunch of caching. mpv
could use it, but it would either require using VapourSynth as demuxer
and decoder for everything, or replacing the current file every time
something is supposed to be played backwards.
FFmpeg's libavfilter has reversal filters for audio and video. These
require buffering the entire media data of the file, and don't really
fit into mpv's architecture. It could be used by playing a libavfilter
graph that also demuxes, but that's like VapourSynth but worse.
2019-05-18 00:10:51 +00:00
|
|
|
|
|
|
|
// Backward playback -> start from end by default.
|
|
|
|
if (play_start_pts == MP_NOPTS_VALUE && opts->play_dir < 0)
|
2019-05-26 23:46:34 +00:00
|
|
|
play_start_pts = get_start_time(mpctx, -1);
|
Implement backwards playback
See manpage additions. This is a huge hack. You can bet there are shit
tons of bugs. It's literally forcing square pegs into round holes.
Hopefully, the manpage wall of text makes it clear enough that the whole
shit can easily crash and burn. (Although it shouldn't literally crash.
That would be a bug. It possibly _could_ start a fire by entering some
sort of endless loop, not a literal one, just something where it tries
to do work without making progress.)
(Some obvious bugs I simply ignored for this initial version, but
there's a number of potential bugs I can't even imagine. Normal playback
should remain completely unaffected, though.)
How this works is also described in the manpage. Basically, we demux in
reverse, then we decode in reverse, then we render in reverse.
The decoding part is the simplest: just reorder the decoder output. This
weirdly integrates with the timeline/ordered chapter code, which also
has special requirements on feeding the packets to the decoder in a
non-straightforward way (it doesn't conflict, although a bugmessmass
breaks correct slicing of segments, so EDL/ordered chapter playback is
broken in backward direction).
Backward demuxing is pretty involved. In theory, it could be much
easier: simply iterating the usual demuxer output backward. But this
just doesn't fit into our code, so there's a cthulhu nightmare of shit.
To be specific, each stream (audio, video) is reversed separately. At
least this means we can do backward playback within cached content (for
example, you could play backwards in a live stream; on that note, it
disables prefetching, which would lead to losing new live video, but
this could be avoided).
The fuckmess also meant that I didn't bother trying to support
subtitles. Subtitles are a problem because they're "sparse" streams.
They need to be "passively" demuxed: you don't try to read a subtitle
packet, you demux audio and video, and then look whether there was a
subtitle packet. This means to get subtitles for a time range, you need
to know that you demuxed video and audio over this range, which becomes
pretty messy when you demux audio and video backwards separately.
Backward display is the most weird (and potentially buggy) part. To
avoid that we need to touch a LOT of timing code, we negate all
timestamps. The basic idea is that due to the navigation, all
comparisons and subtractions of timestamps keep working, and you don't
need to touch every single of them to "reverse" them.
E.g.:
bool before = pts_a < pts_b;
would need to be:
bool before = forward
? pts_a < pts_b
: pts_a > pts_b;
or:
bool before = pts_a * dir < pts_b * dir;
or if you, as it's implemented now, just do this after decoding:
pts_a *= dir;
pts_b *= dir;
and then in the normal timing/renderer code:
bool before = pts_a < pts_b;
Consequently, we don't need many changes in the latter code. But some
assumptions inhererently true for forward playback may have been broken
anyway. What is mainly needed is fixing places where values are passed
between positive and negative "domains". For example, seeking and
timestamp user display always uses positive timestamps. The main mess is
that it's not obvious which domain a given variable should or does use.
Well, in my tests with a single file, it suddenly started to work when I
did this. I'm honestly surprised that it did, and that I didn't have to
change a single line in the timing code past decoder (just something
minor to make external/cached text subtitles display). I committed it
immediately while avoiding thinking about it. But there really likely
are subtle problems of all sorts.
As far as I'm aware, gstreamer also supports backward playback. When I
looked at this years ago, I couldn't find a way to actually try this,
and I didn't revisit it now. Back then I also read talk slides from the
person who implemented it, and I'm not sure if and which ideas I might
have taken from it. It's possible that the timestamp reversal is
inspired by it, but I didn't check. (I think it claimed that it could
avoid large changes by changing a sign?)
VapourSynth has some sort of reverse function, which provides a backward
view on a video. The function itself is trivial to implement, as
VapourSynth aims to provide random access to video by frame numbers (so
you just request decreasing frame numbers). From what I remember, it
wasn't exactly fluid, but it worked. It's implemented by creating an
index, and seeking to the target on demand, and a bunch of caching. mpv
could use it, but it would either require using VapourSynth as demuxer
and decoder for everything, or replacing the current file every time
something is supposed to be played backwards.
FFmpeg's libavfilter has reversal filters for audio and video. These
require buffering the entire media data of the file, and don't really
fit into mpv's architecture. It could be used by playing a libavfilter
graph that also demuxes, but that's like VapourSynth but worse.
2019-05-18 00:10:51 +00:00
|
|
|
|
2017-12-04 01:26:42 +00:00
|
|
|
if (play_start_pts != MP_NOPTS_VALUE) {
|
|
|
|
queue_seek(mpctx, MPSEEK_ABSOLUTE, play_start_pts, MPSEEK_DEFAULT, 0);
|
2013-10-29 21:38:29 +00:00
|
|
|
execute_queued_seek(mpctx);
|
|
|
|
}
|
|
|
|
|
2017-04-14 16:22:45 +00:00
|
|
|
update_internal_pause_state(mpctx);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2017-02-07 16:05:17 +00:00
|
|
|
open_recorder(mpctx, true);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
playback_start = mp_time_sec();
|
2014-10-28 15:19:07 +00:00
|
|
|
mpctx->error_playing = 0;
|
2017-04-14 17:06:13 +00:00
|
|
|
mpctx->in_playloop = true;
|
2013-10-29 21:38:29 +00:00
|
|
|
while (!mpctx->stop_play)
|
|
|
|
run_playloop(mpctx);
|
2017-04-14 17:06:13 +00:00
|
|
|
mpctx->in_playloop = false;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
MP_VERBOSE(mpctx, "EOF code: %d \n", mpctx->stop_play);
|
|
|
|
|
2014-10-28 18:48:56 +00:00
|
|
|
terminate_playback:
|
|
|
|
|
2018-05-19 16:25:54 +00:00
|
|
|
if (!mpctx->stop_play)
|
|
|
|
mpctx->stop_play = PT_ERROR;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2013-11-08 19:02:09 +00:00
|
|
|
if (mpctx->stop_play != AT_END_OF_FILE)
|
|
|
|
clear_audio_output_buffers(mpctx);
|
audio: don't let ao_lavc access frontend internals, change gapless audio
ao_lavc.c accesses ao->buffer, which I consider internal. The access was
done in ao_lavc.c/uninit(), which tried to get the left-over audio in
order to write the last (possibly partial) audio frame. The play()
function didn't accept partial frames, because the AOPLAY_FINAL_CHUNK
flag was not correctly set, and handling it otherwise would require an
internal FIFO.
Fix this by making sure that with gapless audio (used with encoding),
the AOPLAY_FINAL_CHUNK is set only once, instead when each file ends.
Basically, move the hack in ao_lavc's uninit to uninit_player.
One thing can not be entirely correctly handled: if gapless audio is
active, we don't know really whether the AO is closed because the file
ended playing (i.e. we want to send the buffered remainder of the audio
to the AO), or whether the user is quitting the player. (The stop_play
flag is overwritten, fixing that is perhaps not worth it.) Handle this
by adding additional code to drain the AO and the buffers when playback
is quit (see play_current_file() change).
Test case: mpv avdevice://lavfi:sine=441 avdevice://lavfi:sine=441 -length 0.2267 -gapless-audio
2013-11-08 19:00:58 +00:00
|
|
|
|
2018-05-19 16:25:54 +00:00
|
|
|
update_core_idle_state(mpctx);
|
|
|
|
|
player: change m_config to use new option handling mechanisms
Instead of making m_config a special-case, it more or less uses the
underlying m_config_cache/m_config_shadow APIs properly. This makes the
player core a (relatively) equivalent user of the core option API. In
particular, this means that other threads can change core options with
m_config_cache_write_opt() calls (before this commit, this merely led to
diverging option values).
An important change is that before this commit, mpctx->opts contained
the "master copy" of all option data. Now it's just another copy of the
option data, and the shadow copy is considered the master. This is why
whenever mpctx->opts is written, the change needs to be copied to the
master (thus why this commits add a bunch of m_config_notify... calls).
If another thread (e.g. a VO) changes an option, async_change_cb is now
invoked, which funnels the change notification through the player's
layers.
The new self_notification parameter on mp_option_change_callback is so
that m_config_notify... doesn't trigger recursion, and it's used in
cases where the change was already "processed". It's still needed to
trigger libmpv property updates. (I considered using an extra
m_config_cache for that, but it'd only cause problems with no
advantages.)
I think the recent changes actually forgot to send libmpv property
updates in some cases. This should fix this anyway. In some cases,
property updates are reworked, and the potential for bugs should be
lower (probably).
The primary point of this change is to allow external updates, for
example by a VO writing the fullscreen option if the window state is
changed by the window manager (rather than mpv changing it). This is not
used yet, but the following commits will.
2019-11-29 11:49:15 +00:00
|
|
|
if (mpctx->step_frames) {
|
2013-10-29 21:38:29 +00:00
|
|
|
opts->pause = 1;
|
player: change m_config to use new option handling mechanisms
Instead of making m_config a special-case, it more or less uses the
underlying m_config_cache/m_config_shadow APIs properly. This makes the
player core a (relatively) equivalent user of the core option API. In
particular, this means that other threads can change core options with
m_config_cache_write_opt() calls (before this commit, this merely led to
diverging option values).
An important change is that before this commit, mpctx->opts contained
the "master copy" of all option data. Now it's just another copy of the
option data, and the shadow copy is considered the master. This is why
whenever mpctx->opts is written, the change needs to be copied to the
master (thus why this commits add a bunch of m_config_notify... calls).
If another thread (e.g. a VO) changes an option, async_change_cb is now
invoked, which funnels the change notification through the player's
layers.
The new self_notification parameter on mp_option_change_callback is so
that m_config_notify... doesn't trigger recursion, and it's used in
cases where the change was already "processed". It's still needed to
trigger libmpv property updates. (I considered using an extra
m_config_cache for that, but it'd only cause problems with no
advantages.)
I think the recent changes actually forgot to send libmpv property
updates in some cases. This should fix this anyway. In some cases,
property updates are reworked, and the potential for bugs should be
lower (probably).
The primary point of this change is to allow external updates, for
example by a VO writing the fullscreen option if the window state is
changed by the window manager (rather than mpv changing it). This is not
used yet, but the following commits will.
2019-11-29 11:49:15 +00:00
|
|
|
m_config_notify_change_opt_ptr(mpctx->mconfig, &opts->pause);
|
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2020-03-07 11:44:48 +00:00
|
|
|
process_hooks(mpctx, "on_unload");
|
|
|
|
|
2017-02-07 16:05:17 +00:00
|
|
|
close_recorder(mpctx);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
// time to uninit all, except global stuff:
|
2017-08-12 21:08:48 +00:00
|
|
|
reinit_complex_filters(mpctx, true);
|
2014-10-03 17:57:49 +00:00
|
|
|
uninit_audio_chain(mpctx);
|
|
|
|
uninit_video_chain(mpctx);
|
|
|
|
uninit_sub_all(mpctx);
|
2014-10-10 12:49:18 +00:00
|
|
|
if (!opts->gapless_audio && !mpctx->encode_lavc_ctx)
|
2014-10-03 17:57:49 +00:00
|
|
|
uninit_audio_out(mpctx);
|
|
|
|
|
2015-07-02 12:38:03 +00:00
|
|
|
mpctx->playback_initialized = false;
|
|
|
|
|
player: make playback termination asynchronous
Until now, stopping playback aborted the demuxer and I/O layer violently
by signaling mp_cancel (bound to libavformat's AVIOInterruptCB
mechanism). Change it to try closing them gracefully.
The main purpose is to silence those libavformat errors that happen when
you request termination. Most of libavformat barely cares about the
termination mechanism (AVIOInterruptCB), and essentially it's like the
network connection is abruptly severed, or file I/O suddenly returns I/O
errors. There were issues with dumb TLS warnings, parsers complaining
about incomplete data, and some special protocols that require server
communication to gracefully disconnect.
We still want to abort it forcefully if it refuses to terminate on its
own, so a timeout is required. Users can set the timeout to 0, which
should give them the old behavior.
This also removes the old mechanism that treats certain commands (like
"quit") specially, and tries to terminate the demuxers even if the core
is currently frozen. This is for situations where the core synchronized
to the demuxer or stream layer while network is unresponsive. This in
turn can only happen due to the "program" or "cache-size" properties in
the current code (see one of the previous commits). Also, the old
mechanism doesn't fit particularly well with the new one. We wouldn't
want to abort playback immediately on a "quit" command - the new code is
all about giving it a chance to end it gracefully. We'd need some sort
of watchdog thread or something equally complicated to handle this. So
just remove it.
The change in osd.c is to prevent that it clears the status line while
waiting for termination. The normal status line code doesn't output
anything useful at this point, and the code path taken clears it, both
of which is an annoying behavior change, so just let it show the old
one.
2018-05-19 16:41:13 +00:00
|
|
|
uninit_demuxer(mpctx);
|
|
|
|
|
|
|
|
// Possibly stop ongoing async commands.
|
|
|
|
mp_abort_playback_async(mpctx);
|
|
|
|
|
2014-10-28 14:30:27 +00:00
|
|
|
m_config_restore_backups(mpctx->mconfig);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2018-03-02 23:07:06 +00:00
|
|
|
TA_FREEP(&mpctx->filter_root);
|
2014-12-29 21:51:18 +00:00
|
|
|
talloc_free(mpctx->filtered_tags);
|
|
|
|
mpctx->filtered_tags = NULL;
|
2014-12-19 22:54:21 +00:00
|
|
|
|
2021-12-02 16:19:39 +00:00
|
|
|
mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL);
|
2014-10-30 22:54:06 +00:00
|
|
|
|
2020-09-03 13:44:35 +00:00
|
|
|
if (encode_lavc_didfail(mpctx->encode_lavc_ctx))
|
|
|
|
mpctx->stop_play = PT_ERROR;
|
|
|
|
|
|
|
|
if (mpctx->stop_play == PT_ERROR && !mpctx->error_playing)
|
|
|
|
mpctx->error_playing = MPV_ERROR_GENERIC;
|
|
|
|
|
2014-10-30 22:54:06 +00:00
|
|
|
bool nothing_played = !mpctx->shown_aframes && !mpctx->shown_vframes &&
|
|
|
|
mpctx->error_playing <= 0;
|
2014-04-10 23:23:32 +00:00
|
|
|
switch (mpctx->stop_play) {
|
2014-10-28 15:19:07 +00:00
|
|
|
case PT_ERROR:
|
|
|
|
case AT_END_OF_FILE:
|
|
|
|
{
|
2015-06-11 19:39:48 +00:00
|
|
|
if (mpctx->error_playing == 0 && nothing_played)
|
2014-10-28 15:19:07 +00:00
|
|
|
mpctx->error_playing = MPV_ERROR_NOTHING_TO_PLAY;
|
2015-06-11 19:30:02 +00:00
|
|
|
if (mpctx->error_playing < 0) {
|
|
|
|
end_event.error = mpctx->error_playing;
|
2014-10-28 15:19:07 +00:00
|
|
|
end_event.reason = MPV_END_FILE_REASON_ERROR;
|
2015-06-11 19:39:48 +00:00
|
|
|
} else if (mpctx->error_playing == 2) {
|
|
|
|
end_event.reason = MPV_END_FILE_REASON_REDIRECT;
|
2014-10-28 15:19:07 +00:00
|
|
|
} else {
|
|
|
|
end_event.reason = MPV_END_FILE_REASON_EOF;
|
|
|
|
}
|
|
|
|
if (mpctx->playing) {
|
|
|
|
// Played/paused for longer than 1 second -> ok
|
|
|
|
mpctx->playing->playback_short =
|
|
|
|
playback_start < 0 || mp_time_sec() - playback_start < 1.0;
|
2014-10-30 22:54:06 +00:00
|
|
|
mpctx->playing->init_failed = nothing_played;
|
2014-10-28 15:19:07 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Note that error_playing is meaningless in these cases.
|
2014-04-10 23:23:32 +00:00
|
|
|
case PT_NEXT_ENTRY:
|
|
|
|
case PT_CURRENT_ENTRY:
|
2014-10-28 14:44:25 +00:00
|
|
|
case PT_STOP: end_event.reason = MPV_END_FILE_REASON_STOP; break;
|
|
|
|
case PT_QUIT: end_event.reason = MPV_END_FILE_REASON_QUIT; break;
|
2014-04-10 23:23:32 +00:00
|
|
|
};
|
|
|
|
mp_notify(mpctx, MPV_EVENT_END_FILE, &end_event);
|
2014-09-08 22:38:38 +00:00
|
|
|
|
2015-09-03 12:45:32 +00:00
|
|
|
MP_VERBOSE(mpctx, "finished playback, %s (reason %d)\n",
|
|
|
|
mpv_error_string(end_event.error), end_event.reason);
|
2016-11-29 16:16:22 +00:00
|
|
|
if (end_event.error == MPV_ERROR_UNKNOWN_FORMAT)
|
2015-08-03 23:01:09 +00:00
|
|
|
MP_ERR(mpctx, "Failed to recognize file format.\n");
|
|
|
|
MP_INFO(mpctx, "\n");
|
|
|
|
|
2014-09-08 22:38:38 +00:00
|
|
|
if (mpctx->playing)
|
|
|
|
playlist_entry_unref(mpctx->playing);
|
|
|
|
mpctx->playing = NULL;
|
2015-12-23 14:49:20 +00:00
|
|
|
talloc_free(mpctx->filename);
|
2014-09-08 22:38:38 +00:00
|
|
|
mpctx->filename = NULL;
|
command: add a mechanism to allow scripts to intercept file loads
A vague idea to get something similar what libquvi did.
Undocumented because it might change a lot, or even be removed. To give
an idea what it does, a Lua script could do the following:
-- type ID priority
mp.commandv("hook_add", "on_load", 0, 0)
mp.register_script_message("hook_run", function(param, param2)
-- param is "0", the user-chosen ID from the hook_add command
-- param2 is the magic value that has to be passed to finish
-- the hook
mp.resume_all()
-- do something, maybe set options that are reset on end:
mp.set_property("file-local-options/name", "value")
-- or change the URL that's being opened:
local url = mp.get_property("stream-open-filename")
mp.set_property("stream-open-filename", url .. ".png")
-- let the player (or the next script) continue
mp.commandv("hook_ack", param2)
end)
2014-10-15 21:09:53 +00:00
|
|
|
mpctx->stream_open_filename = NULL;
|
2014-09-08 22:38:38 +00:00
|
|
|
|
2014-10-30 22:54:06 +00:00
|
|
|
if (end_event.error < 0 && nothing_played) {
|
|
|
|
mpctx->files_broken++;
|
|
|
|
} else if (end_event.error < 0) {
|
|
|
|
mpctx->files_errored++;
|
|
|
|
} else {
|
|
|
|
mpctx->files_played++;
|
|
|
|
}
|
2018-05-19 16:25:54 +00:00
|
|
|
|
|
|
|
assert(mpctx->stop_play);
|
client API: provide ways to finish property changes on file changes
When the current file changes (or rather, when starting/finishing
playback of a playlist entry), clients tend to have the problem that
it's hard to tell whether a property change notification (via
mpv_observe_property() and mechanisms layered on top of it) is from the
previous or new playlist entry. The previous commit probably helps, but
all the asynchronity is still a bit unhelpful.
Try to make this better by adding new hooks, that are run before/after
playback init/deinit. This is similar to the existing hooks, except
they're outside of "initialized" playback, which excludes that you might
accidentally get an overlap between the current and the previous/next
playlist entry.
That still doesn't seem quite enough, since normally, property change
notifications come after the hook event. So basically a client would
have to explicitly "drain" the event queue within the hook, and make the
hook continue only after that is done. Knowing when property
notifications are done is another asynchronous nightmare (how exactly it
works keeps changing within client.c, and an API user probably can't
tell anymore when all pending properties are truly done). So introduce
another guarantee: properties that were changed before the hook happens
will be returned before the hook event is returned. That means the
client will have received all pending property notifications from the
previous playlist entry (or whatever) before the hook is entered.
As another minor complication, we shouldn't just keep the hook pending
until _all_ property notifications are done, since the client's hook
could produce new ones. (Or just consider things like the demuxer thread
hammering the client with cache update events, while the "on_preloaded"
hook is run.) So there is some extra untested, fragile logic in client.c
to handle this (the waiting_for_hook flag).
This probably works, but was barely tested. Not sure if this helps
anyone, but I think it's fine for my own purposes. (I really hated this
aspect of the API whenever I used it myself.)
2020-03-07 01:52:10 +00:00
|
|
|
|
|
|
|
process_hooks(mpctx, "on_after_end_file");
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the next file to play. Note that if this function returns non-NULL,
|
|
|
|
// it can have side-effects and mutate mpctx.
|
|
|
|
// direction: -1 (previous) or +1 (next)
|
|
|
|
// force: if true, don't skip playlist entries marked as failed
|
2017-01-18 18:02:50 +00:00
|
|
|
// mutate: if true, change loop counters
|
2013-10-29 21:38:29 +00:00
|
|
|
struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction,
|
2017-01-18 18:02:50 +00:00
|
|
|
bool force, bool mutate)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
|
|
|
struct playlist_entry *next = playlist_get_next(mpctx->playlist, direction);
|
|
|
|
if (next && direction < 0 && !force) {
|
|
|
|
// Don't jump to files that would immediately go to next file anyway
|
|
|
|
while (next && next->playback_short)
|
2019-12-28 20:12:02 +00:00
|
|
|
next = playlist_entry_get_rel(next, -1);
|
2013-10-29 21:38:29 +00:00
|
|
|
// Always allow jumping to first file
|
2015-02-12 21:41:45 +00:00
|
|
|
if (!next && mpctx->opts->loop_times == 1)
|
2019-12-28 20:12:02 +00:00
|
|
|
next = playlist_get_first(mpctx->playlist);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
2015-02-12 21:41:45 +00:00
|
|
|
if (!next && mpctx->opts->loop_times != 1) {
|
2013-10-29 21:38:29 +00:00
|
|
|
if (direction > 0) {
|
|
|
|
if (mpctx->opts->shuffle)
|
|
|
|
playlist_shuffle(mpctx->playlist);
|
2019-12-28 20:12:02 +00:00
|
|
|
next = playlist_get_first(mpctx->playlist);
|
player: change m_config to use new option handling mechanisms
Instead of making m_config a special-case, it more or less uses the
underlying m_config_cache/m_config_shadow APIs properly. This makes the
player core a (relatively) equivalent user of the core option API. In
particular, this means that other threads can change core options with
m_config_cache_write_opt() calls (before this commit, this merely led to
diverging option values).
An important change is that before this commit, mpctx->opts contained
the "master copy" of all option data. Now it's just another copy of the
option data, and the shadow copy is considered the master. This is why
whenever mpctx->opts is written, the change needs to be copied to the
master (thus why this commits add a bunch of m_config_notify... calls).
If another thread (e.g. a VO) changes an option, async_change_cb is now
invoked, which funnels the change notification through the player's
layers.
The new self_notification parameter on mp_option_change_callback is so
that m_config_notify... doesn't trigger recursion, and it's used in
cases where the change was already "processed". It's still needed to
trigger libmpv property updates. (I considered using an extra
m_config_cache for that, but it'd only cause problems with no
advantages.)
I think the recent changes actually forgot to send libmpv property
updates in some cases. This should fix this anyway. In some cases,
property updates are reworked, and the potential for bugs should be
lower (probably).
The primary point of this change is to allow external updates, for
example by a VO writing the fullscreen option if the window state is
changed by the window manager (rather than mpv changing it). This is not
used yet, but the following commits will.
2019-11-29 11:49:15 +00:00
|
|
|
if (next && mpctx->opts->loop_times > 1) {
|
2013-10-29 21:38:29 +00:00
|
|
|
mpctx->opts->loop_times--;
|
player: change m_config to use new option handling mechanisms
Instead of making m_config a special-case, it more or less uses the
underlying m_config_cache/m_config_shadow APIs properly. This makes the
player core a (relatively) equivalent user of the core option API. In
particular, this means that other threads can change core options with
m_config_cache_write_opt() calls (before this commit, this merely led to
diverging option values).
An important change is that before this commit, mpctx->opts contained
the "master copy" of all option data. Now it's just another copy of the
option data, and the shadow copy is considered the master. This is why
whenever mpctx->opts is written, the change needs to be copied to the
master (thus why this commits add a bunch of m_config_notify... calls).
If another thread (e.g. a VO) changes an option, async_change_cb is now
invoked, which funnels the change notification through the player's
layers.
The new self_notification parameter on mp_option_change_callback is so
that m_config_notify... doesn't trigger recursion, and it's used in
cases where the change was already "processed". It's still needed to
trigger libmpv property updates. (I considered using an extra
m_config_cache for that, but it'd only cause problems with no
advantages.)
I think the recent changes actually forgot to send libmpv property
updates in some cases. This should fix this anyway. In some cases,
property updates are reworked, and the potential for bugs should be
lower (probably).
The primary point of this change is to allow external updates, for
example by a VO writing the fullscreen option if the window state is
changed by the window manager (rather than mpv changing it). This is not
used yet, but the following commits will.
2019-11-29 11:49:15 +00:00
|
|
|
m_config_notify_change_opt_ptr(mpctx->mconfig,
|
|
|
|
&mpctx->opts->loop_times);
|
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
} else {
|
2019-12-28 20:12:02 +00:00
|
|
|
next = playlist_get_last(mpctx->playlist);
|
2013-10-29 21:38:29 +00:00
|
|
|
// Don't jump to files that would immediately go to next file anyway
|
|
|
|
while (next && next->playback_short)
|
2019-12-28 20:12:02 +00:00
|
|
|
next = playlist_entry_get_rel(next, -1);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
2015-02-12 21:41:45 +00:00
|
|
|
bool ignore_failures = mpctx->opts->loop_times == -2;
|
|
|
|
if (!force && next && next->init_failed && !ignore_failures) {
|
2013-10-29 21:38:29 +00:00
|
|
|
// Don't endless loop if no file in playlist is playable
|
|
|
|
bool all_failed = true;
|
2019-12-28 20:12:02 +00:00
|
|
|
for (int n = 0; n < mpctx->playlist->num_entries; n++) {
|
|
|
|
all_failed &= mpctx->playlist->entries[n]->init_failed;
|
2013-10-29 21:38:29 +00:00
|
|
|
if (!all_failed)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (all_failed)
|
|
|
|
next = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return next;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Play all entries on the playlist, starting from the current entry.
|
|
|
|
// Return if all done.
|
|
|
|
void mp_play_files(struct MPContext *mpctx)
|
|
|
|
{
|
2020-04-09 22:55:39 +00:00
|
|
|
stats_register_thread_cputime(mpctx->stats, "thread");
|
stats: some more performance graphs
Add an infrastructure for collecting performance-related data, use it in
some places. Add rendering of them to stats.lua.
There were two main goals: minimal impact on the normal code and normal
playback. So all these stats_* function calls either happen only during
initialization, or return immediately if no stats collection is going
on. That's why it does this lazily adding of stats entries etc. (a first
iteration made each stats entry an API thing, instead of just a single
stats_ctx, but I thought that was getting too intrusive in the "normal"
code, even if everything gets worse inside of stats.c).
You could get most of this information from various profilers (including
the extremely primitive --dump-stats thing in mpv), but this makes it
easier to see the most important information at once (at least in
theory), partially because we know best about the context of various
things.
Not very happy with this. It's all pretty primitive and dumb. At this
point I just wanted to get over with it, without necessarily having to
revisit it later, but with having my stupid statistics.
Somehow the code feels terrible. There are a lot of meh decisions in
there that could be better or worse (but mostly could be better), and it
just sucks but it's also trivial and uninteresting and does the job. I
guess I hate programming. It's so tedious and the result is always shit.
Anyway, enjoy.
2020-04-08 22:27:54 +00:00
|
|
|
|
scripting: change when/how player waits for scripts being loaded
Fundamentally, scripts are loaded asynchronously, but as a feature,
there was code to wait until a script is loaded (for a certain arbitrary
definition of "loaded"). This was done in scripting.c with the
wait_loaded() function.
This called mp_idle(), and since there are commands to load/unload
scripts, it meant the player core loop could be entered recursively. I
think this is a major complication and has some problems. For example,
if you had a script that does 'os.execute("sleep inf")', then every time
you ran a command to load an instance of the script would add a new
stack frame of mp_idle(). This would lead to some sort of reentrancy
horror that is hard to debug. Also misc/dispatch.c contains a somewhat
tricky mess to support such recursive invocations. There were also some
bugs due to this and due to unforeseen interactions with other messes.
This scripting stuff was the only thing making use of that reentrancy,
and future commands that have "logical" waiting for something should be
implemented differently. So get rid of it.
Change the code to wait only in the player initialization phase: the
only place where it really has to wait is before playback is started,
because scripts might want to set options or hooks that interact with
playback initialization. Unloading of builtin scripts (can happen with
e.g. "set osc no") is left asynchronous; the unloading wasn't too robust
anyway, and this change won't make a difference if someone is trying to
break it intentionally. Note that this is not in mp_initialize(),
because mpv_initialize() uses this by locking the core, which would have
the same problem.
In the future, commands which logically wait should use different
mechanisms. Originally I thought the current approach (that is removed
with this commit) should be used, but it's too much of a mess and can't
even be used in some cases. Examples are:
- "loadfile" should be made blocking (needs to run the normal player
code and manually unblock the thread issuing the command)
- "add-sub" should not freeze the player until the URL is opened (needs
to run opening on a separate thread)
Possibly the current scripting behavior could be restored once new
mechanisms exist, and if it turns out that anyone needs it.
With this commit there should be no further instances of recursive
playloop invocations (other than the case in the following commit),
since all mp_idle()/mp_wait_events() calls are done strictly from the
main thread (and not commands/properties or libmpv client API that
"lock" the main thread).
2018-04-15 08:14:00 +00:00
|
|
|
// Wait for all scripts to load before possibly starting playback.
|
|
|
|
if (!mp_clients_all_initialized(mpctx)) {
|
|
|
|
MP_VERBOSE(mpctx, "Waiting for scripts...\n");
|
|
|
|
while (!mp_clients_all_initialized(mpctx))
|
|
|
|
mp_idle(mpctx);
|
|
|
|
mp_wakeup_core(mpctx); // avoid lost wakeups during waiting
|
|
|
|
MP_VERBOSE(mpctx, "Done loading scripts.\n");
|
|
|
|
}
|
2019-11-17 23:44:12 +00:00
|
|
|
// After above is finished; but even if it's skipped.
|
|
|
|
mp_msg_set_early_logging(mpctx->global, false);
|
scripting: change when/how player waits for scripts being loaded
Fundamentally, scripts are loaded asynchronously, but as a feature,
there was code to wait until a script is loaded (for a certain arbitrary
definition of "loaded"). This was done in scripting.c with the
wait_loaded() function.
This called mp_idle(), and since there are commands to load/unload
scripts, it meant the player core loop could be entered recursively. I
think this is a major complication and has some problems. For example,
if you had a script that does 'os.execute("sleep inf")', then every time
you ran a command to load an instance of the script would add a new
stack frame of mp_idle(). This would lead to some sort of reentrancy
horror that is hard to debug. Also misc/dispatch.c contains a somewhat
tricky mess to support such recursive invocations. There were also some
bugs due to this and due to unforeseen interactions with other messes.
This scripting stuff was the only thing making use of that reentrancy,
and future commands that have "logical" waiting for something should be
implemented differently. So get rid of it.
Change the code to wait only in the player initialization phase: the
only place where it really has to wait is before playback is started,
because scripts might want to set options or hooks that interact with
playback initialization. Unloading of builtin scripts (can happen with
e.g. "set osc no") is left asynchronous; the unloading wasn't too robust
anyway, and this change won't make a difference if someone is trying to
break it intentionally. Note that this is not in mp_initialize(),
because mpv_initialize() uses this by locking the core, which would have
the same problem.
In the future, commands which logically wait should use different
mechanisms. Originally I thought the current approach (that is removed
with this commit) should be used, but it's too much of a mess and can't
even be used in some cases. Examples are:
- "loadfile" should be made blocking (needs to run the normal player
code and manually unblock the thread issuing the command)
- "add-sub" should not freeze the player until the URL is opened (needs
to run opening on a separate thread)
Possibly the current scripting behavior could be restored once new
mechanisms exist, and if it turns out that anyone needs it.
With this commit there should be no further instances of recursive
playloop invocations (other than the case in the following commit),
since all mp_idle()/mp_wait_events() calls are done strictly from the
main thread (and not commands/properties or libmpv client API that
"lock" the main thread).
2018-04-15 08:14:00 +00:00
|
|
|
|
2016-09-19 17:57:31 +00:00
|
|
|
prepare_playlist(mpctx, mpctx->playlist);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
for (;;) {
|
|
|
|
idle_loop(mpctx);
|
player: fix subtle idle mode differences on early program start
If the user manages to run a "loadfile x append" command before the loop
in mp_play_files() is entered, then the player could start playing
these. This isn't expected, because appending files to the playlist in
idle mode does not normally start playback. It could happen because
there is a short time window where commands are processed before the
loop is entered (such as running the command when a script is loaded).
The idle mode semantics are pretty weird: if files were provided in
advance (on the command line), then these should be played immediately.
But if idle mode was already entered, and something is appended to the
playlist using "append", i.e. without explicitly triggering playback,
then it should remain in idle mode.
Try to follow this by redefining PT_STOP to strictly mean idle mode.
Remove the playlist->current check from idle_loop(), since only the
stop_play field counts now (cf. what mp_set_playlist_entry() does).
This actually introduces the possibility that playlist->current, and
with it playlist-pos, are set to something, even though playback is not
active or being started. Previously, this was only possible during state
transitions, such as when changing playlist entries.
Very annoyingly, this means the current way MPV_EVENT_IDLE was sent
doesn't work anymore. Logically, idle mode can be "active" even if
idle_loop() was not entered yet (between the time after mp_initialize()
and before the loop in mp_play_files()). Instead of worrying about this,
redo the "idle-active" property, and deprecate the event.
See: #7543
2020-03-21 13:01:38 +00:00
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
if (mpctx->stop_play == PT_QUIT)
|
|
|
|
break;
|
|
|
|
|
player: fix subtle idle mode differences on early program start
If the user manages to run a "loadfile x append" command before the loop
in mp_play_files() is entered, then the player could start playing
these. This isn't expected, because appending files to the playlist in
idle mode does not normally start playback. It could happen because
there is a short time window where commands are processed before the
loop is entered (such as running the command when a script is loaded).
The idle mode semantics are pretty weird: if files were provided in
advance (on the command line), then these should be played immediately.
But if idle mode was already entered, and something is appended to the
playlist using "append", i.e. without explicitly triggering playback,
then it should remain in idle mode.
Try to follow this by redefining PT_STOP to strictly mean idle mode.
Remove the playlist->current check from idle_loop(), since only the
stop_play field counts now (cf. what mp_set_playlist_entry() does).
This actually introduces the possibility that playlist->current, and
with it playlist-pos, are set to something, even though playback is not
active or being started. Previously, this was only possible during state
transitions, such as when changing playlist entries.
Very annoyingly, this means the current way MPV_EVENT_IDLE was sent
doesn't work anymore. Logically, idle mode can be "active" even if
idle_loop() was not entered yet (between the time after mp_initialize()
and before the loop in mp_play_files()). Instead of worrying about this,
redo the "idle-active" property, and deprecate the event.
See: #7543
2020-03-21 13:01:38 +00:00
|
|
|
if (mpctx->playlist->current)
|
|
|
|
play_current_file(mpctx);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
if (mpctx->stop_play == PT_QUIT)
|
|
|
|
break;
|
|
|
|
|
player: fix subtle idle mode differences on early program start
If the user manages to run a "loadfile x append" command before the loop
in mp_play_files() is entered, then the player could start playing
these. This isn't expected, because appending files to the playlist in
idle mode does not normally start playback. It could happen because
there is a short time window where commands are processed before the
loop is entered (such as running the command when a script is loaded).
The idle mode semantics are pretty weird: if files were provided in
advance (on the command line), then these should be played immediately.
But if idle mode was already entered, and something is appended to the
playlist using "append", i.e. without explicitly triggering playback,
then it should remain in idle mode.
Try to follow this by redefining PT_STOP to strictly mean idle mode.
Remove the playlist->current check from idle_loop(), since only the
stop_play field counts now (cf. what mp_set_playlist_entry() does).
This actually introduces the possibility that playlist->current, and
with it playlist-pos, are set to something, even though playback is not
active or being started. Previously, this was only possible during state
transitions, such as when changing playlist entries.
Very annoyingly, this means the current way MPV_EVENT_IDLE was sent
doesn't work anymore. Logically, idle mode can be "active" even if
idle_loop() was not entered yet (between the time after mp_initialize()
and before the loop in mp_play_files()). Instead of worrying about this,
redo the "idle-active" property, and deprecate the event.
See: #7543
2020-03-21 13:01:38 +00:00
|
|
|
struct playlist_entry *new_entry = NULL;
|
2014-10-28 15:19:07 +00:00
|
|
|
if (mpctx->stop_play == PT_NEXT_ENTRY || mpctx->stop_play == PT_ERROR ||
|
player: fix subtle idle mode differences on early program start
If the user manages to run a "loadfile x append" command before the loop
in mp_play_files() is entered, then the player could start playing
these. This isn't expected, because appending files to the playlist in
idle mode does not normally start playback. It could happen because
there is a short time window where commands are processed before the
loop is entered (such as running the command when a script is loaded).
The idle mode semantics are pretty weird: if files were provided in
advance (on the command line), then these should be played immediately.
But if idle mode was already entered, and something is appended to the
playlist using "append", i.e. without explicitly triggering playback,
then it should remain in idle mode.
Try to follow this by redefining PT_STOP to strictly mean idle mode.
Remove the playlist->current check from idle_loop(), since only the
stop_play field counts now (cf. what mp_set_playlist_entry() does).
This actually introduces the possibility that playlist->current, and
with it playlist-pos, are set to something, even though playback is not
active or being started. Previously, this was only possible during state
transitions, such as when changing playlist entries.
Very annoyingly, this means the current way MPV_EVENT_IDLE was sent
doesn't work anymore. Logically, idle mode can be "active" even if
idle_loop() was not entered yet (between the time after mp_initialize()
and before the loop in mp_play_files()). Instead of worrying about this,
redo the "idle-active" property, and deprecate the event.
See: #7543
2020-03-21 13:01:38 +00:00
|
|
|
mpctx->stop_play == AT_END_OF_FILE)
|
2014-10-28 15:19:07 +00:00
|
|
|
{
|
2017-01-18 18:02:50 +00:00
|
|
|
new_entry = mp_next_file(mpctx, +1, false, true);
|
player: fix subtle idle mode differences on early program start
If the user manages to run a "loadfile x append" command before the loop
in mp_play_files() is entered, then the player could start playing
these. This isn't expected, because appending files to the playlist in
idle mode does not normally start playback. It could happen because
there is a short time window where commands are processed before the
loop is entered (such as running the command when a script is loaded).
The idle mode semantics are pretty weird: if files were provided in
advance (on the command line), then these should be played immediately.
But if idle mode was already entered, and something is appended to the
playlist using "append", i.e. without explicitly triggering playback,
then it should remain in idle mode.
Try to follow this by redefining PT_STOP to strictly mean idle mode.
Remove the playlist->current check from idle_loop(), since only the
stop_play field counts now (cf. what mp_set_playlist_entry() does).
This actually introduces the possibility that playlist->current, and
with it playlist-pos, are set to something, even though playback is not
active or being started. Previously, this was only possible during state
transitions, such as when changing playlist entries.
Very annoyingly, this means the current way MPV_EVENT_IDLE was sent
doesn't work anymore. Logically, idle mode can be "active" even if
idle_loop() was not entered yet (between the time after mp_initialize()
and before the loop in mp_play_files()). Instead of worrying about this,
redo the "idle-active" property, and deprecate the event.
See: #7543
2020-03-21 13:01:38 +00:00
|
|
|
} else if (mpctx->stop_play == PT_CURRENT_ENTRY) {
|
|
|
|
new_entry = mpctx->playlist->current;
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mpctx->playlist->current = new_entry;
|
|
|
|
mpctx->playlist->current_was_replaced = false;
|
player: fix subtle idle mode differences on early program start
If the user manages to run a "loadfile x append" command before the loop
in mp_play_files() is entered, then the player could start playing
these. This isn't expected, because appending files to the playlist in
idle mode does not normally start playback. It could happen because
there is a short time window where commands are processed before the
loop is entered (such as running the command when a script is loaded).
The idle mode semantics are pretty weird: if files were provided in
advance (on the command line), then these should be played immediately.
But if idle mode was already entered, and something is appended to the
playlist using "append", i.e. without explicitly triggering playback,
then it should remain in idle mode.
Try to follow this by redefining PT_STOP to strictly mean idle mode.
Remove the playlist->current check from idle_loop(), since only the
stop_play field counts now (cf. what mp_set_playlist_entry() does).
This actually introduces the possibility that playlist->current, and
with it playlist-pos, are set to something, even though playback is not
active or being started. Previously, this was only possible during state
transitions, such as when changing playlist entries.
Very annoyingly, this means the current way MPV_EVENT_IDLE was sent
doesn't work anymore. Logically, idle mode can be "active" even if
idle_loop() was not entered yet (between the time after mp_initialize()
and before the loop in mp_play_files()). Instead of worrying about this,
redo the "idle-active" property, and deprecate the event.
See: #7543
2020-03-21 13:01:38 +00:00
|
|
|
mpctx->stop_play = new_entry ? PT_NEXT_ENTRY : PT_STOP;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2014-12-07 11:49:07 +00:00
|
|
|
if (!mpctx->playlist->current && mpctx->opts->player_idle_mode < 2)
|
2013-10-29 21:38:29 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-01-18 18:02:50 +00:00
|
|
|
|
|
|
|
cancel_open(mpctx);
|
2018-04-22 17:40:36 +00:00
|
|
|
|
|
|
|
if (mpctx->encode_lavc_ctx) {
|
|
|
|
// Make sure all streams get finished.
|
|
|
|
uninit_audio_out(mpctx);
|
|
|
|
uninit_video_out(mpctx);
|
|
|
|
|
|
|
|
if (!encode_lavc_free(mpctx->encode_lavc_ctx))
|
2020-03-21 12:03:38 +00:00
|
|
|
mpctx->files_errored += 1;
|
2018-04-22 17:40:36 +00:00
|
|
|
|
|
|
|
mpctx->encode_lavc_ctx = NULL;
|
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Abort current playback and set the given entry to play next.
|
|
|
|
// e must be on the mpctx->playlist.
|
|
|
|
void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e)
|
|
|
|
{
|
2015-03-23 03:50:51 +00:00
|
|
|
assert(!e || playlist_entry_to_index(mpctx->playlist, e) >= 0);
|
2013-10-29 21:38:29 +00:00
|
|
|
mpctx->playlist->current = e;
|
|
|
|
mpctx->playlist->current_was_replaced = false;
|
2020-03-21 16:13:33 +00:00
|
|
|
mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL);
|
2018-05-06 19:46:40 +00:00
|
|
|
// Make it pick up the new entry.
|
2019-12-06 18:21:14 +00:00
|
|
|
if (mpctx->stop_play != PT_QUIT)
|
2020-03-21 13:44:09 +00:00
|
|
|
mpctx->stop_play = e ? PT_CURRENT_ENTRY : PT_STOP;
|
2016-09-16 12:24:15 +00:00
|
|
|
mp_wakeup_core(mpctx);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
2017-02-07 16:05:17 +00:00
|
|
|
|
|
|
|
static void set_track_recorder_sink(struct track *track,
|
|
|
|
struct mp_recorder_sink *sink)
|
|
|
|
{
|
|
|
|
if (track->d_sub)
|
|
|
|
sub_set_recorder_sink(track->d_sub, sink);
|
video: make decoder wrapper a filter
Move dec_video.c to filters/f_decoder_wrapper.c. It essentially becomes
a source filter. vd.h mostly disappears, because mp_filter takes care of
the dataflow, but its remains are in struct mp_decoder_fns.
One goal is to simplify dataflow by letting the filter framework handle
it (or more accurately, using its conventions). One result is that the
decode calls disappear from video.c, because we simply connect the
decoder wrapper and the filter chain with mp_pin_connect().
Another goal is to eventually remove the code duplication between the
audio and video paths for this. This commit prepares for this by trying
to make f_decoder_wrapper.c extensible, so it can be used for audio as
well later.
Decoder framedropping changes a bit. It doesn't seem to be worse than
before, and it's an obscure feature, so I'm content with its new state.
Some special code that was apparently meant to avoid dropping too many
frames in a row is removed, though.
I'm not sure how the source code tree should be organized. For one,
video/decode/vd_lavc.c is the only file in its directory, which is a bit
annoying.
2018-01-28 09:08:45 +00:00
|
|
|
if (track->dec)
|
|
|
|
track->dec->recorder_sink = sink;
|
2017-02-07 16:05:17 +00:00
|
|
|
track->remux_sink = sink;
|
|
|
|
}
|
|
|
|
|
|
|
|
void close_recorder(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
if (!mpctx->recorder)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++)
|
|
|
|
set_track_recorder_sink(mpctx->tracks[n], NULL);
|
|
|
|
|
|
|
|
mp_recorder_destroy(mpctx->recorder);
|
|
|
|
mpctx->recorder = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Like close_recorder(), but also unset the option. Intended for use on errors.
|
|
|
|
void close_recorder_and_error(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
close_recorder(mpctx);
|
|
|
|
talloc_free(mpctx->opts->record_file);
|
|
|
|
mpctx->opts->record_file = NULL;
|
player: change m_config to use new option handling mechanisms
Instead of making m_config a special-case, it more or less uses the
underlying m_config_cache/m_config_shadow APIs properly. This makes the
player core a (relatively) equivalent user of the core option API. In
particular, this means that other threads can change core options with
m_config_cache_write_opt() calls (before this commit, this merely led to
diverging option values).
An important change is that before this commit, mpctx->opts contained
the "master copy" of all option data. Now it's just another copy of the
option data, and the shadow copy is considered the master. This is why
whenever mpctx->opts is written, the change needs to be copied to the
master (thus why this commits add a bunch of m_config_notify... calls).
If another thread (e.g. a VO) changes an option, async_change_cb is now
invoked, which funnels the change notification through the player's
layers.
The new self_notification parameter on mp_option_change_callback is so
that m_config_notify... doesn't trigger recursion, and it's used in
cases where the change was already "processed". It's still needed to
trigger libmpv property updates. (I considered using an extra
m_config_cache for that, but it'd only cause problems with no
advantages.)
I think the recent changes actually forgot to send libmpv property
updates in some cases. This should fix this anyway. In some cases,
property updates are reworked, and the potential for bugs should be
lower (probably).
The primary point of this change is to allow external updates, for
example by a VO writing the fullscreen option if the window state is
changed by the window manager (rather than mpv changing it). This is not
used yet, but the following commits will.
2019-11-29 11:49:15 +00:00
|
|
|
m_config_notify_change_opt_ptr(mpctx->mconfig, &mpctx->opts->record_file);
|
2017-02-07 16:05:17 +00:00
|
|
|
MP_ERR(mpctx, "Disabling stream recording.\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void open_recorder(struct MPContext *mpctx, bool on_init)
|
|
|
|
{
|
|
|
|
if (!mpctx->playback_initialized)
|
|
|
|
return;
|
|
|
|
|
|
|
|
close_recorder(mpctx);
|
|
|
|
|
|
|
|
char *target = mpctx->opts->record_file;
|
|
|
|
if (!target || !target[0])
|
|
|
|
return;
|
|
|
|
|
|
|
|
struct sh_stream **streams = NULL;
|
|
|
|
int num_streams = 0;
|
|
|
|
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *track = mpctx->tracks[n];
|
2018-01-29 05:18:33 +00:00
|
|
|
if (track->stream && track->selected && (track->d_sub || track->dec))
|
2017-02-07 16:05:17 +00:00
|
|
|
MP_TARRAY_APPEND(NULL, streams, num_streams, track->stream);
|
|
|
|
}
|
|
|
|
|
2021-05-28 22:28:15 +00:00
|
|
|
struct demux_attachment **attachments = talloc_array(NULL, struct demux_attachment*, mpctx->demuxer->num_attachments);
|
|
|
|
for (int n = 0; n < mpctx->demuxer->num_attachments; n++) {
|
|
|
|
attachments[n] = &mpctx->demuxer->attachments[n];
|
|
|
|
}
|
|
|
|
|
2017-02-07 16:05:17 +00:00
|
|
|
mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file,
|
2021-05-28 22:28:15 +00:00
|
|
|
streams, num_streams,
|
|
|
|
attachments, mpctx->demuxer->num_attachments);
|
2017-02-07 16:05:17 +00:00
|
|
|
|
|
|
|
if (!mpctx->recorder) {
|
|
|
|
talloc_free(streams);
|
2021-05-28 22:28:15 +00:00
|
|
|
talloc_free(attachments);
|
2017-02-07 16:05:17 +00:00
|
|
|
close_recorder_and_error(mpctx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!on_init)
|
|
|
|
mp_recorder_mark_discontinuity(mpctx->recorder);
|
|
|
|
|
|
|
|
int n_stream = 0;
|
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *track = mpctx->tracks[n];
|
|
|
|
if (n_stream >= num_streams)
|
|
|
|
break;
|
|
|
|
// (We expect track->stream not to be reused on other tracks.)
|
|
|
|
if (track->stream == streams[n_stream]) {
|
2018-09-01 14:06:41 +00:00
|
|
|
struct mp_recorder_sink * sink =
|
recorder: don't use a magic index for mp_recorder_get_sink()
Although this was sort of elegant, it just seems to complicate things
slightly. Originally, the API meant that you cache mp_recorder_sink
yourself (which would avoid the mess of passing an index around), but
that too seems slightly roundabout.
In a later change, I want to change the set of streams passed to
mp_recorder_create(), and then I'd have to keep track of the index for
each stream, which would suck. With this commit, I can just pass the
unambiguous sh_stream to it, and it will be guaranteed to match the
correct stream.
The disadvantages are barely worth discussing. It's a new linear search
per packet, but usually only 2 to 4 streams are active at a time. Also,
in theory a user could want to write 2 streams using the same sh_stream
(same metadata, just writing different packets or so), but in practice
this is never done.
2019-09-28 23:39:17 +00:00
|
|
|
mp_recorder_get_sink(mpctx->recorder, streams[n_stream]);
|
2018-09-01 14:06:41 +00:00
|
|
|
assert(sink);
|
|
|
|
set_track_recorder_sink(track, sink);
|
2017-02-07 16:05:17 +00:00
|
|
|
n_stream++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
talloc_free(streams);
|
2021-05-28 22:28:15 +00:00
|
|
|
talloc_free(attachments);
|
2017-02-07 16:05:17 +00:00
|
|
|
}
|
|
|
|
|