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"
|
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"
|
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
|
|
|
{
|
2014-10-03 17:57:49 +00:00
|
|
|
for (int r = 0; r < NUM_PTRACKS; r++) {
|
|
|
|
for (int t = 0; t < STREAM_TYPE_COUNT; t++)
|
|
|
|
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
|
|
|
mpctx->seek_slave = NULL;
|
|
|
|
|
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, ")");
|
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;
|
2014-07-16 20:40:21 +00:00
|
|
|
mp_notify(mpctx, MPV_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.
|
|
|
|
void reselect_demux_stream(struct MPContext *mpctx, struct track *track)
|
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;
|
|
|
|
}
|
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
|
|
|
demuxer_select_track(track->demuxer, track->stream, pts, track->selected);
|
2018-01-17 06:07:15 +00:00
|
|
|
if (track == mpctx->seek_slave)
|
|
|
|
mpctx->seek_slave = NULL;
|
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,
|
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);
|
|
|
|
|
2014-02-10 20:01:35 +00:00
|
|
|
mp_notify(mpctx, MPV_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:
|
2014-10-21 11:16:48 +00:00
|
|
|
* 0a) track matches ff-index (always wins)
|
|
|
|
* 0b) track matches tid (almost always wins)
|
2018-01-06 16:49:37 +00:00
|
|
|
* 0c) 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
|
2015-06-27 20:02:24 +00:00
|
|
|
* 3a) track is marked forced
|
|
|
|
* 3b) 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).
|
|
|
|
*/
|
|
|
|
// 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,
|
|
|
|
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;
|
|
|
|
if (ext1 != ext2)
|
|
|
|
return ext1;
|
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)
|
|
|
|
return 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;
|
|
|
|
}
|
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];
|
|
|
|
char **langs = order == 0 ? opts->stream_lang[type] : NULL;
|
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;
|
2018-01-06 16:49:37 +00:00
|
|
|
if (track->no_auto_select)
|
|
|
|
continue;
|
2014-09-01 21:47:27 +00:00
|
|
|
if (!pick || compare_track(track, pick, langs, 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;
|
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++) {
|
|
|
|
for (int i = 0; i < NUM_PTRACKS; i++) {
|
|
|
|
if (opts->stream_id[i][t] >= 0)
|
|
|
|
opts->stream_id[i][t] = i == 0 ? -1 : -2;
|
|
|
|
}
|
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
talloc_free(mpctx->track_layout_hash);
|
|
|
|
mpctx->track_layout_hash = NULL;
|
|
|
|
}
|
|
|
|
talloc_free(h);
|
|
|
|
}
|
|
|
|
|
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);
|
2013-12-24 16:46:08 +00:00
|
|
|
assert(order >= 0 && order < NUM_PTRACKS);
|
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.)
|
|
|
|
if (flags & FLAG_MARK_SELECTION)
|
|
|
|
mpctx->opts->stream_id[order][type] = track ? track->user_tid : -2;
|
|
|
|
|
|
|
|
// 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");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
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");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
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;
|
2016-02-25 21:44:50 +00:00
|
|
|
reselect_demux_stream(mpctx, current);
|
|
|
|
}
|
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;
|
2016-02-25 21:44:50 +00:00
|
|
|
reselect_demux_stream(mpctx, track);
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
2014-02-10 20:01:35 +00:00
|
|
|
mp_notify(mpctx, MPV_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));
|
|
|
|
}
|
|
|
|
|
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) {
|
|
|
|
for (int t = 0; t < NUM_PTRACKS; t++)
|
2015-05-26 12:01:23 +00:00
|
|
|
mp_switch_track_n(mpctx, t, track->type, NULL, 0);
|
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;
|
|
|
|
|
2018-01-17 06:07:15 +00:00
|
|
|
if (mpctx->seek_slave == track)
|
|
|
|
mpctx->seek_slave = NULL;
|
|
|
|
|
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
|
|
|
|
2014-02-10 20:01:35 +00:00
|
|
|
mp_notify(mpctx, MPV_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,
|
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
|
|
|
enum stream_type filter, struct mp_cancel *cancel)
|
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,
|
|
|
|
};
|
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;
|
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++)
|
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
|
|
|
mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort);
|
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
|
|
|
{
|
|
|
|
if (mpctx->opts->sub_auto < 0 && mpctx->opts->audiofile_auto < 0)
|
|
|
|
return;
|
2016-08-10 20:22:50 +00:00
|
|
|
if (!mpctx->opts->autoload_files)
|
|
|
|
return;
|
2015-02-02 20:23:12 +00:00
|
|
|
|
|
|
|
void *tmp = talloc_new(NULL);
|
2018-09-07 19:41:52 +00:00
|
|
|
struct subfn *list = find_external_files(mpctx->global, mpctx->filename,
|
2018-05-21 14:25:52 +00:00
|
|
|
mpctx->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++) {
|
|
|
|
char *filename = list[i].fname;
|
|
|
|
char *lang = list[i].lang;
|
2016-02-23 21:12:11 +00:00
|
|
|
for (int n = 0; n < mpctx->num_tracks; n++) {
|
|
|
|
struct track *t = mpctx->tracks[n];
|
2016-08-26 10:31:09 +00:00
|
|
|
if (t->demuxer && strcmp(t->demuxer->filename, filename) == 0)
|
2015-02-02 20:23:12 +00:00
|
|
|
goto skip;
|
|
|
|
}
|
2015-02-05 21:14:17 +00:00
|
|
|
if (list[i].type == STREAM_SUB && !sc[STREAM_VIDEO] && !sc[STREAM_AUDIO])
|
|
|
|
goto skip;
|
|
|
|
if (list[i].type == STREAM_AUDIO && !sc[STREAM_VIDEO])
|
|
|
|
goto skip;
|
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
|
|
|
int first = mp_add_external_file(mpctx, filename, list[i].type, cancel);
|
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)
|
|
|
|
t->lang = talloc_strdup(t, 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)
|
|
|
|
pl->current = pl->first;
|
|
|
|
}
|
|
|
|
|
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.
|
|
|
|
static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl)
|
|
|
|
{
|
2013-11-03 18:22:13 +00:00
|
|
|
if (pl->first) {
|
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);
|
2013-11-03 18:22:13 +00:00
|
|
|
playlist_transfer_entries(mpctx->playlist, pl);
|
|
|
|
// 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
|
|
|
|
|
|
|
// We have no idea what blocks a hook, so just do a full abort.
|
|
|
|
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);
|
2015-06-24 19:26:06 +00:00
|
|
|
struct demuxer *demux = demux_open_url(chapter_file, NULL,
|
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,
|
2016-09-22 18:35:38 +00:00
|
|
|
M_SETOPT_RUNTIME | 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
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
static void start_open(struct MPContext *mpctx, char *url, int url_flags)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
if (mpctx->opts->load_unsafe_playlists)
|
|
|
|
mpctx->open_url_flags = 0;
|
|
|
|
|
|
|
|
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)
|
|
|
|
start_open(mpctx, url, mpctx->playing->stream_flags);
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
start_open(mpctx, new_entry->filename, new_entry->stream_flags);
|
|
|
|
}
|
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++)
|
|
|
|
reselect_demux_stream(mpctx, mpctx->tracks[n]);
|
2016-02-05 22:19:56 +00:00
|
|
|
}
|
|
|
|
|
2017-08-12 21:08:48 +00:00
|
|
|
mp_notify(mpctx, MPV_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);
|
|
|
|
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);
|
|
|
|
|
2014-02-10 20:01:35 +00:00
|
|
|
mp_notify(mpctx, MPV_EVENT_START_FILE, NULL);
|
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->stop_play = 0;
|
|
|
|
mpctx->filename = NULL;
|
|
|
|
mpctx->shown_aframes = 0;
|
|
|
|
mpctx->shown_vframes = 0;
|
2014-09-01 19:02:43 +00:00
|
|
|
mpctx->last_vo_pts = MP_NOPTS_VALUE;
|
|
|
|
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);
|
|
|
|
mp_filter_root_set_wakeup_cb(mpctx->filter_root, mp_wakeup_core_cb, mpctx);
|
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;
|
|
|
|
if (!mpctx->playing || !mpctx->playing->filename)
|
2013-10-29 21:38:29 +00:00
|
|
|
goto terminate_playback;
|
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-05-16 15:20:24 +00:00
|
|
|
if (mpctx->playlist->first != mpctx->playing ||
|
|
|
|
mpctx->playlist->last != mpctx->playing ||
|
|
|
|
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;
|
2015-03-02 18:09:36 +00:00
|
|
|
int entry_stream_flags = 0;
|
loadfile, ytdl_hook: don't reject EDL-resolved URLs through playlist
The ytdl wrapper can resolve web links to playlists. This playlist is
passed as big memory:// blob, and will contain further quite normal web
links. When playback of one of these playlist entries starts, ytdl is
called again and will resolve the web link to a media URL again.
This didn't work if playlist entries resolved to EDL URLs. Playback was
rejected with a "potentially unsafe URL from playlist" error. This was
completely weird and unexpected: using the playlist entry directly on
the command line worked fine, and there isn't a reason why it should be
different for a playlist entry (both are resolved by the ytdl wrapper
anyway). Also, if the only EDL URL was added via audio-add or sub-add,
the URL was accessed successfully.
The reason this happened is because the playlist entries were marked as
STREAM_SAFE_ONLY, and edl:// is not marked as "safe". Playlist entries
passed via command line directly are not marked, so resolving them to
EDL worked.
Fix this by making the ytdl hook set load-unsafe-playlists while the
playlist is parsed. (After the playlist is parsed, and before the first
playlist entry is played, file-local options are reset again.) Further,
extend the load-unsafe-playlists option so that the playlist entries are
not marked while the playlist is loaded.
Since playlist entries are already verified, this should change nothing
about the actual security situation.
There are now 2 locations which check load_unsafe_playlists. The old one
is a bit redundant now. In theory, the playlist loading code might not
be the only code which sets these flags, so keeping the old code is
somewhat justified (and in any case it doesn't hurt to keep it).
In general, the security concept sucks (and always did). I can for
example not answer the question whether you can "break" this mechanism
with various combinations of archives, EDL files, playlists files,
compromised sites, and so on. You probably can, and I'm fully aware that
it's probably possible, so don't blame me.
2019-01-04 12:48:27 +00:00
|
|
|
if (!pl->disable_safety && !mpctx->opts->load_unsafe_playlists) {
|
2015-03-02 18:09:36 +00:00
|
|
|
entry_stream_flags = STREAM_SAFE_ONLY;
|
2016-08-26 10:31:09 +00:00
|
|
|
if (mpctx->demuxer->is_network)
|
2015-03-02 18:09:36 +00:00
|
|
|
entry_stream_flags |= STREAM_NETWORK_ONLY;
|
|
|
|
}
|
2014-08-31 17:49:39 +00:00
|
|
|
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
2014-08-31 22:12:47 +00:00
|
|
|
e->stream_flags |= entry_stream_flags;
|
2014-08-31 17:49:39 +00:00
|
|
|
transfer_playlist(mpctx, pl);
|
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
|
|
|
|
2015-05-22 19:00:24 +00:00
|
|
|
assert(NUM_PTRACKS == 2); // opts->stream_id is hardcoded to 2
|
|
|
|
for (int t = 0; t < STREAM_TYPE_COUNT; t++) {
|
2016-02-05 22:19:56 +00:00
|
|
|
for (int i = 0; i < NUM_PTRACKS; i++) {
|
|
|
|
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++) {
|
|
|
|
for (int i = 0; i < NUM_PTRACKS; i++) {
|
|
|
|
struct track *track = mpctx->current_track[i][t];
|
|
|
|
if (track) {
|
|
|
|
if (track->selected) {
|
|
|
|
MP_ERR(mpctx, "Track %d can't be selected twice.\n",
|
|
|
|
track->user_tid);
|
|
|
|
mpctx->current_track[i][t] = NULL;
|
|
|
|
} else {
|
|
|
|
track->selected = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
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++)
|
|
|
|
reselect_demux_stream(mpctx, mpctx->tracks[n]);
|
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,
|
|
|
|
"Displaying attached picture. Use --no-audio-display to prevent this.\n");
|
|
|
|
}
|
|
|
|
|
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
|
|
|
demux_start_prefetch(mpctx->demuxer);
|
|
|
|
|
|
|
|
if (opts->demuxer_cache_wait) {
|
|
|
|
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);
|
|
|
|
|
|
|
|
process_hooks(mpctx, "on_unload");
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
if (mpctx->step_frames)
|
|
|
|
opts->pause = 1;
|
|
|
|
|
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
|
|
|
|
2014-02-10 20:01:35 +00:00
|
|
|
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
|
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
|
|
|
struct mpv_event_end_file end_event = {0};
|
|
|
|
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);
|
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)
|
|
|
|
next = next->prev;
|
|
|
|
// Always allow jumping to first file
|
2015-02-12 21:41:45 +00:00
|
|
|
if (!next && mpctx->opts->loop_times == 1)
|
2013-10-29 21:38:29 +00:00
|
|
|
next = mpctx->playlist->first;
|
|
|
|
}
|
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);
|
|
|
|
next = mpctx->playlist->first;
|
2015-02-12 21:41:45 +00:00
|
|
|
if (next && mpctx->opts->loop_times > 1)
|
2013-10-29 21:38:29 +00:00
|
|
|
mpctx->opts->loop_times--;
|
|
|
|
} else {
|
|
|
|
next = mpctx->playlist->last;
|
|
|
|
// Don't jump to files that would immediately go to next file anyway
|
|
|
|
while (next && next->playback_short)
|
|
|
|
next = next->prev;
|
|
|
|
}
|
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;
|
|
|
|
struct playlist_entry *cur;
|
|
|
|
for (cur = mpctx->playlist->first; cur; cur = cur->next) {
|
|
|
|
all_failed &= cur->init_failed;
|
|
|
|
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)
|
|
|
|
{
|
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");
|
|
|
|
}
|
|
|
|
|
2016-09-19 17:57:31 +00:00
|
|
|
prepare_playlist(mpctx, mpctx->playlist);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
for (;;) {
|
2018-05-19 16:25:54 +00:00
|
|
|
assert(mpctx->stop_play);
|
2013-10-29 21:38:29 +00:00
|
|
|
idle_loop(mpctx);
|
|
|
|
if (mpctx->stop_play == PT_QUIT)
|
|
|
|
break;
|
|
|
|
|
|
|
|
play_current_file(mpctx);
|
|
|
|
if (mpctx->stop_play == PT_QUIT)
|
|
|
|
break;
|
|
|
|
|
2014-10-28 15:19:07 +00:00
|
|
|
struct playlist_entry *new_entry = mpctx->playlist->current;
|
|
|
|
if (mpctx->stop_play == PT_NEXT_ENTRY || mpctx->stop_play == PT_ERROR ||
|
2018-05-19 16:25:54 +00:00
|
|
|
mpctx->stop_play == AT_END_OF_FILE || mpctx->stop_play == PT_STOP)
|
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);
|
2013-10-29 21:38:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
mpctx->playlist->current = new_entry;
|
|
|
|
mpctx->playlist->current_was_replaced = false;
|
2018-05-19 16:25:54 +00:00
|
|
|
mpctx->stop_play = 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))
|
|
|
|
mpctx->stop_play = PT_ERROR;
|
|
|
|
|
|
|
|
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;
|
2018-05-06 19:46:40 +00:00
|
|
|
// Make it pick up the new entry.
|
2015-07-08 19:31:31 +00:00
|
|
|
if (!mpctx->stop_play)
|
|
|
|
mpctx->stop_play = PT_CURRENT_ENTRY;
|
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;
|
|
|
|
mp_notify_property(mpctx, "record-file");
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file,
|
|
|
|
streams, num_streams);
|
|
|
|
|
|
|
|
if (!mpctx->recorder) {
|
|
|
|
talloc_free(streams);
|
|
|
|
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 =
|
|
|
|
mp_recorder_get_sink(mpctx->recorder, n_stream);
|
|
|
|
assert(sink);
|
|
|
|
set_track_recorder_sink(track, sink);
|
2017-02-07 16:05:17 +00:00
|
|
|
n_stream++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
talloc_free(streams);
|
|
|
|
}
|
|
|
|
|