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
|
|
|
|
|
|
|
#include "osdep/io.h"
|
2013-12-19 20:31:27 +00:00
|
|
|
#include "osdep/terminal.h"
|
2013-10-29 21:38:29 +00:00
|
|
|
#include "osdep/timer.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
|
|
|
|
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);
|
|
|
|
|
|
|
|
pthread_mutex_lock(&mpctx->lock);
|
|
|
|
if (mpctx->demuxer_cancel)
|
|
|
|
mp_cancel_trigger(mpctx->demuxer_cancel);
|
|
|
|
pthread_mutex_unlock(&mpctx->lock);
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
}
|
2015-02-22 18:06:21 +00:00
|
|
|
talloc_free(mpctx->chapters);
|
|
|
|
mpctx->chapters = NULL;
|
|
|
|
mpctx->num_chapters = 0;
|
|
|
|
|
|
|
|
// close demuxers for external tracks
|
|
|
|
for (int n = mpctx->num_tracks - 1; n >= 0; n--) {
|
|
|
|
mpctx->tracks[n]->selected = false;
|
|
|
|
mp_remove_track(mpctx, mpctx->tracks[n]);
|
2015-02-17 22:46:12 +00:00
|
|
|
}
|
2015-12-26 17:32:27 +00:00
|
|
|
for (int i = 0; i < mpctx->num_tracks; i++) {
|
2016-01-17 15:56:32 +00:00
|
|
|
sub_destroy(mpctx->tracks[i]->d_sub);
|
2015-02-22 18:06:21 +00:00
|
|
|
talloc_free(mpctx->tracks[i]);
|
2015-12-26 17:32:27 +00:00
|
|
|
}
|
2015-02-22 18:06:21 +00:00
|
|
|
mpctx->num_tracks = 0;
|
|
|
|
|
2016-02-15 20:03:51 +00:00
|
|
|
free_demuxer_and_stream(mpctx->demuxer);
|
|
|
|
mpctx->demuxer = NULL;
|
2017-01-18 16:13:26 +00:00
|
|
|
|
|
|
|
pthread_mutex_lock(&mpctx->lock);
|
|
|
|
talloc_free(mpctx->demuxer_cancel);
|
|
|
|
mpctx->demuxer_cancel = NULL;
|
|
|
|
pthread_mutex_unlock(&mpctx->lock);
|
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_update(demuxer);
|
|
|
|
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);
|
|
|
|
if (pts != MP_NOPTS_VALUE)
|
|
|
|
pts += get_track_seek_offset(mpctx, track);
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2014-07-18 13:10:28 +00:00
|
|
|
// Called from the demuxer thread if a new packet is available.
|
|
|
|
static void wakeup_demux(void *pctx)
|
|
|
|
{
|
|
|
|
struct MPContext *mpctx = pctx;
|
2016-09-16 12:23:54 +00:00
|
|
|
mp_wakeup_core(mpctx);
|
2014-07-18 13:10:28 +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,
|
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);
|
|
|
|
|
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, stream, MP_NOPTS_VALUE, false);
|
2013-10-29 21:38:29 +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 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;
|
|
|
|
|
2016-01-17 15:56:32 +00:00
|
|
|
sub_destroy(track->d_sub);
|
2015-12-26 17:32:27 +00:00
|
|
|
|
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)
|
2015-02-22 18:06:21 +00:00
|
|
|
free_demuxer_and_stream(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;
|
|
|
|
}
|
|
|
|
|
2016-02-08 20:18:35 +00:00
|
|
|
// Add the given file as additional track. Only tracks of type "filter" are
|
|
|
|
// included; pass STREAM_TYPE_COUNT to disable filtering.
|
2018-01-27 20:27:58 +00:00
|
|
|
int mp_add_external_file(struct MPContext *mpctx, char *filename,
|
|
|
|
enum stream_type filter)
|
2013-10-29 21:38:29 +00:00
|
|
|
{
|
|
|
|
struct MPOpts *opts = mpctx->opts;
|
|
|
|
if (!filename)
|
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
|
|
|
|
2016-01-18 16:42:34 +00:00
|
|
|
struct demuxer_params params = {0};
|
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;
|
|
|
|
}
|
|
|
|
|
2015-02-20 20:56:55 +00:00
|
|
|
struct demuxer *demuxer =
|
|
|
|
demux_open_url(filename, ¶ms, mpctx->playback_abort, mpctx->global);
|
|
|
|
if (!demuxer)
|
2013-10-29 21:38:29 +00:00
|
|
|
goto err_out;
|
2016-02-23 21:51:18 +00:00
|
|
|
enable_demux_thread(mpctx, demuxer);
|
2015-02-02 20:23:12 +00:00
|
|
|
|
2017-12-07 09:20:03 +00:00
|
|
|
if (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) {
|
2015-02-20 20:08:10 +00:00
|
|
|
free_demuxer_and_stream(demuxer);
|
2017-12-07 09:32:29 +00:00
|
|
|
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);
|
2018-01-27 20:27:58 +00:00
|
|
|
return -1;
|
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;
|
|
|
|
t->title = talloc_strdup(t, mp_basename(disp_filename));
|
|
|
|
t->external_filename = talloc_strdup(t, filename);
|
|
|
|
t->no_default = sh->type != filter;
|
2018-01-06 16:49:37 +00:00
|
|
|
t->no_auto_select = filter == STREAM_TYPE_COUNT;
|
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-01-27 20:27:58 +00:00
|
|
|
return first_num;
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
err_out:
|
2015-02-20 20:08:10 +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
|
|
|
}
|
|
|
|
|
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
|
|
|
{
|
2016-02-08 20:18:35 +00:00
|
|
|
for (int n = 0; files && files[n]; n++)
|
|
|
|
mp_add_external_file(mpctx, files[n], filter);
|
2015-02-02 20:23:12 +00:00
|
|
|
}
|
|
|
|
|
2015-02-16 21:06:41 +00:00
|
|
|
void autoload_external_files(struct MPContext *mpctx)
|
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);
|
|
|
|
char *base_filename = mpctx->filename;
|
|
|
|
char *stream_filename = NULL;
|
|
|
|
if (mpctx->demuxer) {
|
|
|
|
if (demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_BASE_FILENAME,
|
|
|
|
&stream_filename) > 0)
|
|
|
|
base_filename = talloc_steal(tmp, stream_filename);
|
|
|
|
}
|
|
|
|
struct subfn *list = find_external_files(mpctx->global, base_filename);
|
|
|
|
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;
|
2018-01-27 20:27:58 +00:00
|
|
|
int first = mp_add_external_file(mpctx, filename, list[i].type);
|
|
|
|
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-01-02 14:20:53 +00:00
|
|
|
static int process_open_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-01-02 14:20:53 +00:00
|
|
|
mp_hook_run(mpctx, NULL, 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-01-02 14:20:53 +00:00
|
|
|
while (!mp_hook_test_completion(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
|
|
|
mp_idle(mpctx);
|
|
|
|
if (mpctx->stop_play) {
|
|
|
|
// Can't exit immediately, the script would interfere with the
|
|
|
|
// next file being loaded.
|
2014-10-20 21:43:10 +00:00
|
|
|
if (mpctx->stop_play == PT_QUIT)
|
|
|
|
return -1;
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-02-15 15:19:19 +00:00
|
|
|
static int process_preloaded_hooks(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
mp_hook_run(mpctx, NULL, "on_preloaded");
|
|
|
|
|
|
|
|
while (!mp_hook_test_completion(mpctx, "on_preloaded")) {
|
|
|
|
mp_idle(mpctx);
|
|
|
|
if (mpctx->stop_play)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-04 17:25:40 +00:00
|
|
|
static void process_unload_hooks(struct MPContext *mpctx)
|
|
|
|
{
|
|
|
|
mp_hook_run(mpctx, NULL, "on_unload");
|
|
|
|
|
|
|
|
while (!mp_hook_test_completion(mpctx, "on_unload"))
|
|
|
|
mp_idle(mpctx);
|
|
|
|
}
|
|
|
|
|
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]) {
|
2015-06-24 19:26:06 +00:00
|
|
|
struct demuxer *demux = demux_open_url(chapter_file, NULL,
|
2014-11-02 15:47:23 +00:00
|
|
|
mpctx->playback_abort, mpctx->global);
|
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;
|
|
|
|
}
|
|
|
|
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)
|
|
|
|
free_demuxer_and_stream(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;
|
|
|
|
|
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,
|
2017-01-19 07:00:19 +00:00
|
|
|
.initial_readahead = true,
|
2015-08-03 23:01:09 +00:00
|
|
|
};
|
2017-01-18 18:02:50 +00:00
|
|
|
mpctx->open_res_demuxer =
|
|
|
|
demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global);
|
|
|
|
|
|
|
|
if (mpctx->open_res_demuxer) {
|
|
|
|
MP_VERBOSE(mpctx, "Opening done: %s\n", mpctx->open_url);
|
|
|
|
} 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;
|
|
|
|
|
|
|
|
TA_FREEP(&mpctx->open_cancel);
|
|
|
|
TA_FREEP(&mpctx->open_url);
|
|
|
|
TA_FREEP(&mpctx->open_format);
|
|
|
|
|
|
|
|
if (mpctx->open_res_demuxer)
|
|
|
|
free_demuxer_and_stream(mpctx->open_res_demuxer);
|
|
|
|
mpctx->open_res_demuxer = NULL;
|
|
|
|
|
|
|
|
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.
|
2017-01-18 16:13:26 +00:00
|
|
|
pthread_mutex_lock(&mpctx->lock);
|
2017-01-18 18:02:50 +00:00
|
|
|
mpctx->demuxer_cancel = mpctx->open_cancel;
|
2017-01-18 16:13:26 +00:00
|
|
|
pthread_mutex_unlock(&mpctx->lock);
|
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) {
|
|
|
|
assert(mpctx->demuxer_cancel == mpctx->open_cancel);
|
|
|
|
mpctx->demuxer = mpctx->open_res_demuxer;
|
|
|
|
mpctx->open_res_demuxer = NULL;
|
|
|
|
mpctx->open_cancel = NULL;
|
2014-10-28 14:30:27 +00:00
|
|
|
} else {
|
2017-01-18 18:02:50 +00:00
|
|
|
mpctx->error_playing = mpctx->open_res_error;
|
2017-01-18 16:13:26 +00:00
|
|
|
pthread_mutex_lock(&mpctx->lock);
|
|
|
|
mpctx->demuxer_cancel = NULL;
|
|
|
|
pthread_mutex_unlock(&mpctx->lock);
|
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
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
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;
|
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
|
|
|
|
2017-11-24 12:58:57 +00:00
|
|
|
// let get_current_time() show 0 as start time (before playback_pts is set)
|
|
|
|
mpctx->last_seek_pts = 0.0;
|
|
|
|
|
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
|
|
|
|
2014-09-01 19:02:43 +00:00
|
|
|
MP_INFO(mpctx, "Playing: %s\n", mpctx->filename);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2015-07-02 12:38:03 +00:00
|
|
|
reopen_file:
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
assert(mpctx->demuxer == NULL);
|
|
|
|
|
2018-01-02 14:20:53 +00:00
|
|
|
if (process_open_hooks(mpctx, "on_load") < 0)
|
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-01-02 14:20:53 +00:00
|
|
|
if (!mpctx->stop_play && !mpctx->demuxer &&
|
|
|
|
process_open_hooks(mpctx, "on_load_fail") >= 0 &&
|
|
|
|
strcmp(mpctx->stream_open_filename, mpctx->filename) != 0)
|
|
|
|
{
|
|
|
|
mpctx->error_playing = MPV_ERROR_LOADING_FAILED;
|
|
|
|
open_demux_reentrant(mpctx);
|
|
|
|
}
|
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;
|
|
|
|
if (!pl->disable_safety) {
|
|
|
|
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);
|
|
|
|
|
2014-03-25 01:05:48 +00:00
|
|
|
load_chapters(mpctx);
|
2016-02-15 20:03:51 +00:00
|
|
|
add_demuxer_tracks(mpctx, mpctx->demuxer);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
2016-02-08 20:18:35 +00:00
|
|
|
open_external_files(mpctx, opts->audio_files, STREAM_AUDIO);
|
|
|
|
open_external_files(mpctx, opts->sub_name, STREAM_SUB);
|
|
|
|
open_external_files(mpctx, opts->external_files, STREAM_TYPE_COUNT);
|
2015-02-02 20:23:12 +00:00
|
|
|
autoload_external_files(mpctx);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
|
|
|
check_previous_track_selection(mpctx);
|
|
|
|
|
2016-02-15 15:19:19 +00:00
|
|
|
if (process_preloaded_hooks(mpctx))
|
|
|
|
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);
|
|
|
|
|
2013-07-16 11:28:28 +00:00
|
|
|
#if HAVE_ENCODING
|
2013-12-24 16:46:08 +00:00
|
|
|
if (mpctx->encode_lavc_ctx && mpctx->current_track[0][STREAM_VIDEO])
|
2013-10-29 21:38:29 +00:00
|
|
|
encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_VIDEO);
|
2013-12-24 16:46:08 +00:00
|
|
|
if (mpctx->encode_lavc_ctx && mpctx->current_track[0][STREAM_AUDIO])
|
2013-10-29 21:38:29 +00:00
|
|
|
encode_lavc_expect_stream(mpctx->encode_lavc_ctx, AVMEDIA_TYPE_AUDIO);
|
2014-03-30 17:05:59 +00:00
|
|
|
if (mpctx->encode_lavc_ctx) {
|
|
|
|
encode_lavc_set_metadata(mpctx->encode_lavc_ctx,
|
|
|
|
mpctx->demuxer->metadata);
|
|
|
|
}
|
2013-10-29 21:38:29 +00:00
|
|
|
#endif
|
|
|
|
|
2015-12-10 16:22:20 +00:00
|
|
|
update_playback_speed(mpctx);
|
|
|
|
|
2015-01-12 12:04:21 +00:00
|
|
|
reinit_video_chain(mpctx);
|
|
|
|
reinit_audio_chain(mpctx);
|
2016-01-17 15:56:32 +00:00
|
|
|
reinit_sub_all(mpctx);
|
2015-01-12 12:04:21 +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;
|
|
|
|
}
|
|
|
|
|
2017-12-04 01:26:42 +00:00
|
|
|
double play_start_pts = get_play_start_pts(mpctx);
|
|
|
|
if (play_start_pts != MP_NOPTS_VALUE) {
|
|
|
|
/*
|
|
|
|
* get_play_start_pts returns rebased values, but
|
|
|
|
* we want an un rebased value to feed to seeker.
|
|
|
|
*/
|
|
|
|
if (!opts->rebase_start_time){
|
|
|
|
play_start_pts += mpctx->demuxer->start_time;
|
2016-10-20 18:19:43 +00:00
|
|
|
}
|
2017-12-04 01:26:42 +00:00
|
|
|
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:
|
|
|
|
|
2017-04-14 16:56:03 +00:00
|
|
|
update_core_idle_state(mpctx);
|
|
|
|
|
2015-02-04 17:25:40 +00:00
|
|
|
process_unload_hooks(mpctx);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
if (mpctx->stop_play == KEEP_PLAYING)
|
|
|
|
mpctx->stop_play = AT_END_OF_FILE;
|
|
|
|
|
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
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
if (mpctx->step_frames)
|
|
|
|
opts->pause = 1;
|
|
|
|
|
2017-01-18 16:13:26 +00:00
|
|
|
mp_abort_playback_async(mpctx);
|
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
|
|
|
|
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);
|
|
|
|
uninit_demuxer(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);
|
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
|
|
|
TA_FREEP(&mpctx->filter_root);
|
2014-10-03 17:57:49 +00:00
|
|
|
|
2015-07-02 12:38:03 +00:00
|
|
|
mpctx->playback_initialized = false;
|
|
|
|
|
|
|
|
if (mpctx->stop_play == PT_RELOAD_FILE) {
|
|
|
|
mpctx->stop_play = KEEP_PLAYING;
|
2016-01-07 08:11:15 +00:00
|
|
|
mp_cancel_reset(mpctx->playback_abort);
|
2015-07-02 12:38:03 +00:00
|
|
|
goto reopen_file;
|
|
|
|
}
|
|
|
|
|
2014-10-28 14:30:27 +00:00
|
|
|
m_config_restore_backups(mpctx->mconfig);
|
2013-10-29 21:38:29 +00:00
|
|
|
|
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++;
|
|
|
|
}
|
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)
|
|
|
|
{
|
2016-09-19 17:57:31 +00:00
|
|
|
prepare_playlist(mpctx, mpctx->playlist);
|
|
|
|
|
2013-10-29 21:38:29 +00:00
|
|
|
for (;;) {
|
|
|
|
idle_loop(mpctx);
|
|
|
|
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 ||
|
|
|
|
mpctx->stop_play == AT_END_OF_FILE || !mpctx->stop_play)
|
|
|
|
{
|
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;
|
|
|
|
mpctx->stop_play = 0;
|
|
|
|
|
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);
|
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;
|
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]) {
|
|
|
|
set_track_recorder_sink(track,
|
|
|
|
mp_recorder_get_sink(mpctx->recorder, n_stream));
|
|
|
|
n_stream++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
talloc_free(streams);
|
|
|
|
}
|
|
|
|
|