[SCM] mpv/upstream: Imported Upstream version 0.19.0

jcowgill at users.alioth.debian.org jcowgill at users.alioth.debian.org
Thu Aug 25 16:14:56 UTC 2016


The following commit has been merged in the upstream branch:
commit 36ba0fceb2c02e6d8638bf1a53ca0cf0285f8882
Author: James Cowgill <jcowgill at debian.org>
Date:   Thu Aug 25 16:13:36 2016 +0000

    Imported Upstream version 0.19.0

diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE
index 78f92ca..9edfac4 100644
--- a/.github/ISSUE_TEMPLATE
+++ b/.github/ISSUE_TEMPLATE
@@ -10,5 +10,12 @@ If you're not using git master or the latest release, update.
 
 ### Log files
 
-Make a log file made with -v or --log-file=output.txt, paste it to sprunge.us,
-and replace this text with a link to it.
+Make a log file made with -v or --log-file=output.txt, paste it to
+http://sprunge.us or a similar site, and replace this text with a link to it.
+
+Do not bother reporting a bug if you do not provide the required information.
+
+### Sample files
+
+Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
+or similar sites. (Only needed if the issue cannot be reproduced without it.)
\ No newline at end of file
diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst
index 2027ed9..17fc4bd 100644
--- a/DOCS/client-api-changes.rst
+++ b/DOCS/client-api-changes.rst
@@ -32,6 +32,8 @@ API changes
 
 ::
 
+ --- mpv 0.19.0
+ 1.22   - add stream_cb API for custom protocols
  --- mpv 0.18.1 ---
  ----   - remove "status" log level from mpv_request_log_messages() docs. This
           is 100% equivalent to "v". The behavior is still the same, thus no
diff --git a/DOCS/compile-windows.md b/DOCS/compile-windows.md
index 364a057..c043671 100644
--- a/DOCS/compile-windows.md
+++ b/DOCS/compile-windows.md
@@ -173,6 +173,24 @@ sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
 rmdir /mingw64/bin/pkgconfig
 ```
 
+Linking libmpv with MSVC programs
+---------------------------------
+
+You can build C++ programs in Visual Studio and link them with libmpv. To do
+this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
+and you need to create a import library for the mpv DLL:
+
+```bash
+lib /def:mpv.def /name:mpv-1.dll /out:mpv.lib /MACHINE:X64
+```
+
+The string in the ``/name:`` parameter must match the filename of the DLL (this
+is simply the filename the MSVC linker will use). The ``mpv.def`` can be
+retrieved from the mpv build directory, or can be produced by MingGW's
+gendef.exe helper from the mpv DLL.
+
+Static linking is not possible.
+
 Running mpv
 -----------
 
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index d2e1ee4..d7db010 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -19,11 +19,13 @@ Interface changes
 
 ::
 
+ --- mpv 0.19.0 ---
+    - deprecate "balance" option/property (no replacement)
  --- mpv 0.18.1 ---
     - deprecate --heartbeat-cmd
     - remove --softvol=no capability:
         - deprecate --softvol, it now does nothing
-        - --volume, --mute, and the corrsponding properties now always control
+        - --volume, --mute, and the corresponding properties now always control
           softvol, and behave as expected without surprises (e.g. you can set
           them normally while no audio is initialized)
         - rename --softvol-max to --volume-max (deprecated alias is added)
diff --git a/DOCS/man/af.rst b/DOCS/man/af.rst
index 8fe6025..c52c422 100644
--- a/DOCS/man/af.rst
+++ b/DOCS/man/af.rst
@@ -312,34 +312,6 @@ Available filters are:
         ``mpv '--af=format=channels=5.1' '--audio-channels=5.1'`` would always force
         remixing audio to 5.1 and output it like this.
 
-``delay[=[ch1,ch2,...]]``
-    Delays the sound to the loudspeakers such that the sound from the
-    different channels arrives at the listening position simultaneously. It is
-    only useful if you have more than 2 loudspeakers.
-
-    ``[ch1,ch2,...]``
-        The delay in ms that should be imposed on each channel (floating point
-        number between 0 and 1000).
-
-    To calculate the required delay for the different channels, do as follows:
-
-    1. Measure the distance to the loudspeakers in meters in relation to your
-       listening position, giving you the distances s1 to s5 (for a 5.1
-       system). There is no point in compensating for the subwoofer (you will
-       not hear the difference anyway).
-
-    2. Subtract the distances s1 to s5 from the maximum distance, i.e.
-       ``s[i] = max(s) - s[i]; i = 1...5``.
-
-    3. Calculate the required delays in ms as ``d[i] = 1000*s[i]/342; i =
-       1...5``.
-
-    .. admonition:: Example
-
-        ``mpv --af=delay=[10.5,10.5,0,0,7,0] media.avi``
-            Would delay front left and right by 10.5 ms, the two rear channels
-            and the subwoofer by 0 ms and the center channel by 7 ms.
-
 ``drc[=method:target]``
     Applies dynamic range compression. This maximizes the volume by compressing
     the audio signal's dynamic range. (Formerly called ``volnorm``.)
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 9c4df61..2cc41bf 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -640,7 +640,7 @@ Input Commands that are Possibly Subject to Change
     The argument is the name of the binding.
 
     It can optionally be prefixed with the name of the script, using ``/`` as
-    separator, e.g. ``script_binding scriptname/bindingname``.
+    separator, e.g. ``script-binding scriptname/bindingname``.
 
     For completeness, here is how this command works internally. The details
     could change any time. On any matching key event, ``script-message-to``
@@ -809,7 +809,7 @@ Input sections group a set of bindings, and enable or disable them at once.
 In ``input.conf``, each key binding is assigned to an input section, rather
 than actually having explicit text sections.
 
-See also: ``enable_section`` and ``disable_section`` commands.
+See also: ``enable-section`` and ``disable-section`` commands.
 
 Predefined bindings:
 
@@ -869,6 +869,12 @@ Property list
     looks better for display purposes. Use the ``path`` property to get an
     unmodified filename.)
 
+    This has a sub-property:
+
+    ``filename/no-ext``
+        Like the ``filename`` property, but if the text contains a ``.``, strip
+        all text after the last ``.``. Usually this removes the file extension.
+
 ``file-size``
     Length in bytes of the source file/stream. (This is the same as
     ``${stream-end}``. For ordered chapters and such, the
@@ -1321,6 +1327,8 @@ Property list
     It doesn't change the volumes of each channel, but instead sets up a pan
     matrix to mix the left and right channels.)
 
+    Deprecated.
+
 ``fullscreen`` (RW)
     See ``--fullscreen``.
 
@@ -1479,6 +1487,12 @@ Property list
     ``video-params/gamma``
         The gamma function in use as string. (Exact values subject to change.)
 
+    ``video-params/nom-peak``
+        The video encoding's nominal peak brightness as float.
+
+    ``video-params/sig-peak``
+        The video file's tagged signal peak as float.
+
     ``video-params/chroma-location``
         Chroma location as string. (Exact values subject to change.)
 
@@ -1505,6 +1519,9 @@ Property list
             "colormatrix"       MPV_FORMAT_STRING
             "colorlevels"       MPV_FORMAT_STRING
             "primaries"         MPV_FORMAT_STRING
+            "gamma"             MPV_FORMAT_STRING
+            "nom-peak"          MPV_FORMAT_DOUBLE
+            "sig-peak"          MPV_FORMAT_DOUBLE
             "chroma-location"   MPV_FORMAT_STRING
             "rotate"            MPV_FORMAT_INT64
             "stereo-in"         MPV_FORMAT_STRING
@@ -1585,7 +1602,7 @@ Property list
 
 ``osd-width``, ``osd-height``
     Last known OSD width (can be 0). This is needed if you want to use the
-    ``overlay_add`` command. It gives you the actual OSD size, which can be
+    ``overlay-add`` command. It gives you the actual OSD size, which can be
     different from the window size in some cases.
 
 ``osd-par``
@@ -1796,6 +1813,16 @@ Property list
     ``track-list/N/audio-channels`` (deprecated)
         Deprecated alias for ``track-list/N/demux-channel-count``.
 
+    ``track-list/N/replaygain-track-peak``, ``track-list/N/replaygain-track-gain``
+        Per-track replaygain values. Only available for audio tracks with
+        corresponding information stored in the source file.
+
+    ``track-list/N/replaygain-album-peak``, ``track-list/N/replaygain-album-gain``
+        Per-album replaygain values. If the file has per-track but no per-album
+        information, the per-album values will be copied from the per-track
+        values currently. It's possible that future mpv versions will make
+        these properties unavailable instead in this case.
+
     When querying the property with the client API using ``MPV_FORMAT_NODE``,
     or with Lua ``mp.get_property_native``, this will return a mpv_node with
     the following contents:
@@ -1825,6 +1852,10 @@ Property list
                 "demux-samplerate"  MPV_FORMAT_INT64
                 "demux-fps"         MPV_FORMAT_DOUBLE
                 "audio-channels"    MPV_FORMAT_INT64
+                "replaygain-track-peak" MPV_FORMAT_DOUBLE
+                "replaygain-track-gain" MPV_FORMAT_DOUBLE
+                "replaygain-album-peak" MPV_FORMAT_DOUBLE
+                "replaygain-album-gain" MPV_FORMAT_DOUBLE
 
 ``chapter-list``
     List of chapters, current entry marked. Currently, the raw property value
diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst
index 7a0ed69..80ba838 100644
--- a/DOCS/man/ipc.rst
+++ b/DOCS/man/ipc.rst
@@ -41,7 +41,7 @@ It's also possible to send input.conf style text-only commands:
 
 ::
 
-    > echo 'show_text ${playback-time}' | socat - /tmp/mpvsocket
+    > echo 'show-text ${playback-time}' | socat - /tmp/mpvsocket
 
 But you won't get a reply over the socket. (This particular command shows the
 playback time on the player's OSD.)
@@ -65,7 +65,7 @@ You can send commands from a command prompt:
 
 ::
 
-    echo show_text ${playback-time} >\\.\pipe\mpvsocket
+    echo show-text ${playback-time} >\\.\pipe\mpvsocket
 
 To be able to simultaneously read and write from the IPC pipe, like on Linux,
 it's necessary to write an external program that uses overlapped file I/O (or
diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst
index 99ff6ff..d8252a2 100644
--- a/DOCS/man/lua.rst
+++ b/DOCS/man/lua.rst
@@ -541,8 +541,8 @@ Example command-line::
      --script-opts=myscript-optionA=TEST,myscript-optionB=0,myscript-optionC=yes
 
 
-mp.utils options
-----------------
+mp.utils functions
+------------------
 
 This built-in module provides generic helper functions for Lua, and have
 strictly speaking nothing to do with mpv or video/audio playback. They are
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index 736ef02..5a80319 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -946,7 +946,7 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
 ``~/.config/mpv/watch_later/``
     Contains temporary config files needed for resuming playback of files with
     the watch later feature. See for example the ``Q`` key binding, or the
-    ``quit_watch_later`` input command.
+    ``quit-watch-later`` input command.
 
     Each file is a small config file which is loaded if the corresponding media
     file is loaded. It contains the playback position and some (not necessarily
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index f9c32e5..39416d2 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -196,7 +196,7 @@ Playback Control
     position and so can take some time depending on decoding performance. For
     some video formats, precise seeks are disabled. This option selects the
     default choice to use for seeks; it is possible to explicitly override that
-    default in the definition of key bindings and in slave mode commands.
+    default in the definition of key bindings and in input commands.
 
     :no:       Never use precise seeks.
     :absolute: Use precise seeks if the seek is to an absolute position in the
@@ -374,7 +374,7 @@ Program Behavior
 
 ``--idle=<no|yes|once>``
     Makes mpv wait idly instead of quitting when there is no file to play.
-    Mostly useful in slave mode, where mpv can be controlled through input
+    Mostly useful in input mode, where mpv can be controlled through input
     commands.
 
     ``once`` will only idle at start and let the player close once the
@@ -404,7 +404,7 @@ Program Behavior
 ``--no-resume-playback``
     Do not restore playback position from the ``watch_later`` configuration
     subdirectory (usually ``~/.config/mpv/watch_later/``).
-    See ``quit_watch_later`` input command.
+    See ``quit-watch-later`` input command.
 
 ``--profile=<profile1,profile2,...>``
     Use the given profile(s), ``--profile=help`` displays a list of the
@@ -586,7 +586,9 @@ Video
     :vaapi:     requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
     :vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
     :videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
+    :videotoolbox-copy: copies video back into system RAM (OS X 10.8 and up only)
     :dxva2: requires ``--vo=opengl:backend=angle`` or
+
         ``--vo=opengl:backend=dxinterop`` (Windows only)
     :dxva2-copy: copies video back to system RAM (Windows only)
     :d3d11va: requires ``--vo=opengl:backend=angle`` (Windows only)
@@ -690,8 +692,8 @@ Video
     choice of the format can influence performance considerably. On the other
     hand, there doesn't appear to be a good way to detect the best format for
     the given hardware. ``nv12``, the default, works better on modern hardware,
-    while ``uyvy422`` appears to be better for old hardware. ``rgb0`` also
-    works.
+    while ``uyvy422`` appears to be better for old hardware. ``rgb0`` and
+    ``yuv420p`` also work.
 
 ``--panscan=<0.0-1.0>``
     Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9
@@ -883,9 +885,9 @@ Video
     You can get the list of allowed codecs with ``mpv --vd=help``. Remove the
     prefix, e.g. instead of ``lavc:h264`` use ``h264``.
 
-    By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video``. Note that the
-    hardware acceleration special codecs like ``h264_vdpau`` are not relevant
-    anymore, and in fact have been removed from Libav in this form.
+    By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video,vp9``. Note that
+    the hardware acceleration special codecs like ``h264_vdpau`` are not
+    relevant anymore, and in fact have been removed from Libav in this form.
 
     This is usually only needed with broken GPUs, where a codec is reported
     as supported, but decoding causes more problems than it solves.
@@ -1077,6 +1079,11 @@ Audio
 
     Since mpv 0.18.1, this always controls the internal mixer (aka "softvol").
 
+``--balance=<value>``
+    How much left/right channels contribute to the audio.
+
+    Deprecated.
+
 ``--audio-delay=<sec>``
     Audio delay in seconds (positive or negative float value). Positive values
     delay the audio, and negative values delay the video.
@@ -1143,30 +1150,51 @@ Audio
         This and enabling passthrough via ``--ad`` are deprecated in favor of
         using ``--audio-spdif=dts-hd``.
 
-``--audio-channels=<auto|number|layout>``
-    Request a channel layout for audio output (default: stereo). This  will ask
-    the AO to open a device with the given channel layout. It's up to the AO
-    to accept this layout, or to pick a fallback or to error out if the
-    requested layout is not supported.
-
-    The ``--audio-channels`` option either takes a channel number or an explicit
-    channel layout. Channel numbers refer to default layouts, e.g. 2 channels
-    refer to stereo, 6 refers to 5.1.
+``--audio-channels=<auto-safe|auto|layouts>``
+    Control which audio channels are output (e.g. surround vs. stereo). There
+    are the following possibilities:
+
+    - ``--audio-channels=auto-safe``
+        Use the system's preferred channel layout. If there is none (such
+        as when accessing a hardware device instead of the system mixer),
+        force stereo. Some audio outputs might simply accept any layout and
+        do downmixing on their own.
+
+        This is the default.
+    - ``--audio-channels=auto``
+        Send the audio device whatever it accepts, preferring the audio's
+        original channel layout. Can cause issues with HDMI (see the warning
+        below).
+    - ``--audio-channels=layout1,layout2,...``
+        List of ``,``-separated channel layouts which should be allowed.
+        Technically, this only adjusts the filter chain output to the best
+        matching layout in the list, and passes the result to the audio API.
+        It's possible that the audio API will select a different channel
+        layout.
+
+        Using this mode is recommended for direct hardware output, especially
+        over HDMI (see HDMI warning below).
+    - ``--audio-channels=stereo``
+        Force  a plain stereo downmix. This is a special-case of the previous
+        item. (See paragraphs below for implications.)
+
+    If a list of layouts is given, each item can be either an explicit channel
+    layout name (like ``5.1``), or a channel number. Channel numbers refer to
+    default layouts, e.g. 2 channels refer to stereo, 6 refers to 5.1.
 
     See ``--audio-channels=help`` output for defined default layouts. This also
     lists speaker names, which can be used to express arbitrary channel
     layouts (e.g. ``fl-fr-lfe`` is 2.1).
 
-    ``--audio-channels=auto`` tries to play audio using the input file's
-    channel layout. There is no guarantee that the audio API handles this
-    correctly. See the HDMI warning below.
-    (``empty`` is an accepted obsolete alias for ``auto``.)
-
-    This will also request the channel layout from the decoder. If the decoder
-    does not support the layout, it will fall back to its native channel layout.
-    (You can use ``--ad-lavc-downmix=no`` to make the decoder always output
-    its native layout.) Note that only some decoders support remixing audio.
-    Some that do include AC-3, AAC or DTS audio.
+    If the list of channel layouts has only 1 item, the decoder is asked to
+    produce according output. This sometimes triggers decoder-downmix, which
+    might be different from the normal mpv downmix. (Only some decoders support
+    remixing audio, like AC-3, AAC or DTS. You can use ``--ad-lavc-downmix=no``
+    to make the decoder always output its native layout.) One consequence is
+    that ``--audio-channels=stereo`` triggers decoder downmix, while ``auto``
+    or ``auto-safe`` never will, even if they end up selecting stereo. This
+    happens because the decision whether to use decoder downmix happens long
+    before the audio device is opened.
 
     If the channel layout of the media file (i.e. the decoder) and the AO's
     channel layout don't match, mpv will attempt to insert a conversion filter.
@@ -1179,6 +1207,10 @@ Audio
         channel layout, random things can happen, such as dropping the
         additional channels, or adding noise.
 
+        You are recommended to set an explicit whitelist of the layouts you
+        want. For example, most A/V receivers connected via HDMI and that can
+        do 7.1 would  be served by: ``--audio-channels=7.1,5.1,stereo``
+
 ``--audio-normalize-downmix=<yes|no>``
     Enable/disable normalization if surround audio is downmixed to stereo
     (default: no). If this is disabled, downmix can cause clipping. If it's
@@ -1283,7 +1315,7 @@ Audio
     or to set your own application name when using libmpv.
 
 ``--volume-restore-data=<string>``
-    Used internally for use by playback resume (e.g. with ``quit_watch_later``).
+    Used internally for use by playback resume (e.g. with ``quit-watch-later``).
     Restoring value has to be done carefully, because different AOs as well as
     softvol can have different value ranges, and we don't want to restore
     volume if setting the volume changes it system wide. The normal options
@@ -1309,6 +1341,25 @@ Audio
 
     Default: 0.2 (200 ms).
 
+``--audio-stream-silence=<yes|no>``
+    Cash-grab consumer audio hardware (such as A/V receivers) often ignore
+    initial audio sent over HDMI. This can happen every time audio over HDMI
+    is stopped and resumed. In order to compensate for this, you can enable
+    this option to not to stop and restart audio on seeks, and fill the gaps
+    with silence. Likewise, when pausing playback, audio is not stopped, and
+    silence is played while paused. Note that if no audio track is selected,
+    the audio device will still be closed immediately.
+
+    Not all AOs support this.
+
+``--audio-wait-open=<secs>``
+    This makes sense for use with ``--audio-stream-silence=yes``. If this option
+    is given, the player will wait for the given amount of seconds after opening
+    the audio device before sending actual audio data to it. Useful if your
+    expensive hardware discards the first 1 or 2 seconds of audio data sent to
+    it. If ``--audio-stream-silence=yes`` is not set, this option will likely
+    just waste time.
+
 Subtitles
 ---------
 
@@ -2536,7 +2587,7 @@ Input
     automatically enabled when ``-`` is found on the command line. There are
     situations where you have to set it manually, e.g. if you open
     ``/dev/stdin`` (or the equivalent on your system), use stdin in a playlist
-    or intend to read from stdin later on via the loadfile or loadlist slave
+    or intend to read from stdin later on via the loadfile or loadlist input
     commands.
 
 ``--input-ipc-server=<filename>``
@@ -2658,7 +2709,7 @@ OSD
     (default), then the playback time, duration, and some more information is
     shown.
 
-    This is also used for the ``show_progress`` command (by default mapped to
+    This is also used for the ``show-progress`` command (by default mapped to
     ``P``), or in some non-default cases when seeking.
 
     ``--osd-status-msg`` is a legacy equivalent (but with a minor difference).
@@ -2666,7 +2717,7 @@ OSD
 ``--osd-status-msg=<string>``
     Show a custom string during playback instead of the standard status text.
     This overrides the status text used for ``--osd-level=3``, when using the
-    ``show_progress`` command (by default mapped to ``P``), or in some
+    ``show-progress`` command (by default mapped to ``P``), or in some
     non-default cases when seeking. Expands properties. See
     `Property Expansion`_.
 
@@ -2927,7 +2978,7 @@ Screenshot
         insert the number of the current month as number. You have to use
         multiple ``%tX`` specifiers to build a full date/time string.
     ``%{prop[:fallback text]}``
-        Insert the value of the slave property 'prop'. E.g. ``%{filename}`` is
+        Insert the value of the input property 'prop'. E.g. ``%{filename}`` is
         the same as ``%f``. If the property does not exist or is not available,
         an error text is inserted, unless a fallback is specified.
     ``%%``
@@ -3176,7 +3227,7 @@ TV
         If <chan> is an integer greater than 1000, it will be treated as
         frequency (in kHz) rather than channel name from frequency table.
         Use _ for spaces in names (or play with quoting ;-) ). The channel
-        names will then be written using OSD, and the slave commands
+        names will then be written using OSD, and the input commands
         ``tv_step_channel``, ``tv_set_channel`` and ``tv_last_channel``
         will be usable for a remote control. Not compatible with
         the ``frequency`` parameter.
@@ -3625,7 +3676,7 @@ Miscellaneous
 ``--stream-capture=<filename>``
     Allows capturing the primary stream (not additional audio tracks or other
     kind of streams) into the given file. Capturing can also be started and
-    stopped by changing the filename with the ``stream-capture`` slave property.
+    stopped by changing the filename with the ``stream-capture`` property.
     Generally this will not produce usable results for anything else than MPEG
     or raw streams, unless capturing includes the file headers and is not
     interrupted. Note that, due to cache latencies, captured data may begin and
@@ -3669,6 +3720,16 @@ Miscellaneous
     ``--audio-file``, this includes all tracks, and does not cause default
     stream selection over the "proper" file.
 
+``--autoload-files=<yes|no>``
+    Automatically load/select external files (default: yes).
+
+    If set to ``no``, then do not automatically load external files as specified
+    by ``--sub-auto`` and ``--audio-file-auto``. If external files are forcibly
+    added (like with ``--sub-file``), they will not be auto-selected.
+
+    This does not affect playlist expansion, redirection, or other loading of
+    referenced files like with ordered chapters.
+
 ``--lavfi-complex=<string>``
     Set a "complex" libavfilter filter, which means a single filter graph can
     take input from multiple source audio and video tracks. The graph can result
diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst
index 6a5c44f..10f30c6 100644
--- a/DOCS/man/vf.rst
+++ b/DOCS/man/vf.rst
@@ -821,6 +821,12 @@ Available filters are:
         Whether deinterlacing is enabled (default: no).
     ``interlaced-only=<yes|no>``
         If ``yes`` (default), only deinterlace frames marked as interlaced.
+    ``mode=<blend|bob|adaptive|mocomp|ivctc|none>``
+        Tries to select a video processor with the given processing capability.
+        If a video processor supports multiple capabilities, it is not clear
+        which algorithm is actually selected. ``none`` always falls back. On
+        most if not all hardware, this option will probably do nothing, because
+        a video processor usually supports all modes or none.
 
 ``buffer=<num>``
     Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index b80244c..0beaee5 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -362,6 +362,9 @@ Available video output drivers are:
             exchange for adding some blur. This filter is good at temporal
             interpolation, and also known as "smoothmotion" (see ``tscale``).
 
+        ``linear``
+            A ``tscale`` filter.
+
         ``custom``
             A user-defined custom shader (see ``scale-shader``).
 
@@ -1095,8 +1098,7 @@ Available video output drivers are:
 
     ``3dlut-size=<r>x<g>x<b>``
         Size of the 3D LUT generated from the ICC profile in each dimension.
-        Default is 128x256x64.
-        Sizes must be a power of two, and 512 at most.
+        Default is 64x64x64. Sizes may range from 2 to 512.
 
     ``icc-contrast=<0-100000>``
         Specifies an upper limit on the target device's contrast ratio.
@@ -1300,7 +1302,7 @@ Available video output drivers are:
     This also supports many of the suboptions the ``opengl`` VO has. Run
     ``mpv --vo=opengl-cb:help`` for a list.
 
-    This also supports the ``vo_cmdline`` command.
+    This also supports the ``vo-cmdline`` command.
 
 ``rpi`` (Raspberry Pi)
     Native video output on the Raspberry Pi using the MMAL API.
diff --git a/DOCS/mplayer-changes.rst b/DOCS/mplayer-changes.rst
index 0699021..0eb5236 100644
--- a/DOCS/mplayer-changes.rst
+++ b/DOCS/mplayer-changes.rst
@@ -30,7 +30,7 @@ Player
 * Slave mode compatibility broken (see below).
 * Re-enable screensaver while the player is paused.
 * Allow resuming playback at a later point with ``Shift+q``, also see the
-  ``quit_watch_later`` input command.
+  ``quit-watch-later`` input command.
 * ``--keep-open`` option to stop the player from closing the window and
   exiting after playback ends.
 * A client API, that allows embedding **mpv** into applications
@@ -312,11 +312,11 @@ input.conf and Slave Commands
     +--------------------------------+----------------------------------------+
     | Old                            | New                                    |
     +================================+========================================+
-    | ``pt_step 1 [0|1]``            | ``playlist_next [weak|force]``         |
+    | ``pt_step 1 [0|1]``            | ``playlist-next [weak|force]``         |
     |                                | (translation layer cannot deal with    |
     |                                | whitespace)                            |
     +--------------------------------+----------------------------------------+
-    | ``pt_step -1 [0|1]``           | ``playlist_prev [weak|force] (same)``  |
+    | ``pt_step -1 [0|1]``           | ``playlist-prev [weak|force] (same)``  |
     +--------------------------------+----------------------------------------+
     | ``switch_ratio [<ratio>]``     | ``set video-aspect <ratio>``           |
     |                                |                                        |
@@ -331,7 +331,7 @@ input.conf and Slave Commands
     | ``<step> <dir>``               | ``no-osd``: ``no-osd cycle <prop>``    |
     |                                | ``<step>``                             |
     +--------------------------------+----------------------------------------+
-    | ``osd_show_property_text``     | ``show_text <text>``                   |
+    | ``osd_show_property_text``     | ``show-text <text>``                   |
     | ``<text>``                     | The property expansion format string   |
     |                                | syntax slightly changed.               |
     +--------------------------------+----------------------------------------+
@@ -340,9 +340,9 @@ input.conf and Slave Commands
     |                                | ``raw`` prefix to disable property     |
     |                                | expansion.                             |
     +--------------------------------+----------------------------------------+
-    | ``show_tracks``                | ``show_text ${track-list}``            |
+    | ``show_tracks``                | ``show-text ${track-list}``            |
     +--------------------------------+----------------------------------------+
-    | ``show_chapters``              | ``show_text ${chapter-list}``          |
+    | ``show_chapters``              | ``show-text ${chapter-list}``          |
     +--------------------------------+----------------------------------------+
     | ``af_switch``, ``af_add``, ... | ``af set|add|...``                     |
     +--------------------------------+----------------------------------------+
diff --git a/DOCS/release-policy.md b/DOCS/release-policy.md
index 5a1f961..e68bc35 100644
--- a/DOCS/release-policy.md
+++ b/DOCS/release-policy.md
@@ -25,7 +25,8 @@ Release procedure
 
 - Create and/or update the `VERSION` file.
 
-- Update `DOCS/client-api-changes.rst` (on major releases).
+- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
+  (in particular, update the last version numbers if necessary)
 
 - Create tag v0.X.Y.
 
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 6973ace..a9f7d2a 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,3 +1,112 @@
+Release 0.19.0
+==============
+
+Build System Changes
+--------------------
+
+- build: add --htmldir option
+- build: always require atomics
+- wscript: add proper unversioned SONAME for Android
+
+
+Features
+--------
+
+New
+~~~
+
+- client API: add stream_cb API for user-defined stream implementations (bumps client API version to 1.22)
+- vf_d3d11vpp: add video processor selection
+- videotoolbox: add --hwdec=videotoolbox-copy for h/w accelerated decoding with video filters
+- vo_opengl: add a tscale=linear direct implementation
+
+Removed
+~~~~~~~
+
+- audio/filter: remove delay audio filter
+
+
+Options and Commands
+--------------------
+
+Added
+~~~~~
+
+- command: add filename/no-ext sub-property that returns filename without extension (#3404)
+- command: add properties for HDR metadata
+- command: add replaygain information properties to track-list
+- options: add vp9 to --hwdec-codecs
+- player: add --audio-stream-silence
+- player: add --audio-wait-open
+- player: add --no-autoload-files
+- videotoolbox: add yuv420p to --videotoolbox-format
+
+Changed
+~~~~~~~
+
+- options: un-restrict --audio-delay
+- use - as command-name separator everywhere
+- vo_opengl: reduce default 3dlut-size to 64x64x64 (since accuracy is improved)
+
+
+Deprecated
+~~~~~~~~~~
+
+- deprecate "balance" option/property (no replacement)
+
+
+
+Fixes and Minor Enhancements
+----------------------------
+
+- Windows: don't wait for GUI thread when polling for events (#3393)
+- af_lavcac3enc: error out properly if encoding fails
+- af_volume: don't let softvol overwrite af_volume volumedb sub-option
+- ao_pulse: fix some volume control rounding issues
+- ao_wasapi: in exclusive mode, do not output multichannel by default
+- audio: add heuristic to move auto-downmixing before other filters
+- audio: show an osd bar when changing ao-volume
+- demux: make ALBUM replaygain tags optional (#3405)
+- demux_raw: fix small typo to add s16be support
+- demux_timeline: restore mkv edition switching
+- libarchive: sanitize non-UTF8 archive entries
+- macOS/vo_opengl: fix crash when glctx is NULL during init (#3360)
+- player: disable display-sync with spdif transcoding
+- player: do not cut off terminal status line if it contains newlines (#3340)
+- player: fix display-sync timing if audio resumes slowly
+- player: improve instant track switching (#3392)
+- player: improve non-hr seeking with external audio tracks
+- player: offset demuxer on start/seek properly with audio/sub delay
+- player: sync audio as well when enabling it mid-stream
+- stream/stream_bluray: display list of available titles in verbose mode
+- sub: don't potentially discard too many subtitles on seek
+- video: respect --deinterlace=auto
+- vo_direct3d: add missing header (fixes Cygwin build)
+- vo_opengl: angle: try D3D9 when D3D11 fails eglInitialize
+- vo_opengl: angle: use WARP if there are no hw adapters (makes it work on Windows 7 without hardware-accelerated graphics)
+- vo_opengl: increase 3DLUT accuracy at smaller LUT sizes
+- vo_opengl: remove the 3dlut-size npot2 restriction
+- vo_wayland: fix high CPU usage due to busy polling
+- wayland_common: clip window size to the display output size
+- wayland_common: fix crashes when switching to fullscreen before the video output is fully initialized
+- wayland_common: fix fullscreen image switching bug
+- wayland_common: prevent black bars on most non-native aspect ratios
+- wayland_common: remove untested/unusable wayland dnd code
+- win32: mpv.rc: re-add version info
+- x11: skip ICC update on every window move
+- ytdl: Error out with http_dash_segments (unsupported for now)
+
+
+This listing is not complete. Check DOCS/client-api-changes.rst for a history
+of changes to the client API, and DOCS/interface-changes.rst for a history
+of changes to other user-visible interfaces.
+
+A complete changelog can be seen by running `git log v0.18.1..v0.19.0`
+in the git repository or by visiting either
+https://github.com/mpv-player/mpv/compare/v0.18.1...v0.19.0 or
+http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.1..v0.19.0
+
+
 Release 0.18.1
 ==============
 
diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua
index 1f22bb4..fa56a3d 100644
--- a/TOOLS/lua/autoload.lua
+++ b/TOOLS/lua/autoload.lua
@@ -25,7 +25,7 @@ function add_files_at(index, files)
     local oldcount = mp.get_property_number("playlist-count", 1)
     for i = 1, #files do
         mp.commandv("loadfile", files[i], "append")
-        mp.commandv("playlist_move", oldcount + i - 1, index + i - 1)
+        mp.commandv("playlist-move", oldcount + i - 1, index + i - 1)
     end
 end
 
diff --git a/VERSION b/VERSION
index 249afd5..1cf0537 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.18.1
+0.19.0
diff --git a/audio/audio.c b/audio/audio.c
index 306401b..502bbf2 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -138,6 +138,9 @@ static void mp_audio_destructor(void *ptr)
  * available on every plane. The previous data is kept (for the smallest
  * common number of samples before/after resize).
  *
+ * This also makes sure the resulting buffer is writable (even in the case
+ * the buffer has the correct size).
+ *
  * mpa->samples is not set or used.
  *
  * This function is flexible enough to handle format and channel layout
@@ -153,6 +156,12 @@ void mp_audio_realloc(struct mp_audio *mpa, int samples)
     int size = get_plane_size(mpa, samples);
     if (size < 0)
         abort(); // oom or invalid parameters
+    if (!mp_audio_is_writeable(mpa)) {
+        for (int n = 0; n < MP_NUM_CHANNELS; n++) {
+            av_buffer_unref(&mpa->allocated[n]);
+            mpa->planes[n] = NULL;
+        }
+    }
     for (int n = 0; n < mpa->num_planes; n++) {
         if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
             if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
@@ -171,7 +180,7 @@ void mp_audio_realloc(struct mp_audio *mpa, int samples)
 // If the buffer is reallocated, also preallocate.
 void mp_audio_realloc_min(struct mp_audio *mpa, int samples)
 {
-    if (samples > mp_audio_get_allocated_size(mpa)) {
+    if (samples > mp_audio_get_allocated_size(mpa) || !mp_audio_is_writeable(mpa)) {
         size_t alloc = ta_calc_prealloc_elems(samples);
         if (alloc > INT_MAX)
             abort(); // oom
@@ -347,9 +356,9 @@ struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
     mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
 
 #if LIBAVUTIL_VERSION_MICRO >= 100
-    // FFmpeg being special again
-    if (lavc_chmap.num != avframe->channels)
-        mp_chmap_from_channels(&lavc_chmap, avframe->channels);
+    // FFmpeg being stupid POS again
+    if (lavc_chmap.num != av_frame_get_channels(avframe))
+        mp_chmap_from_channels(&lavc_chmap, av_frame_get_channels(avframe));
 #endif
 
     new->rate = avframe->sample_rate;
@@ -394,12 +403,9 @@ fail:
     return NULL;
 }
 
-// Returns NULL on failure. The input is always unreffed.
-struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
+int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
 {
-    struct AVFrame *avframe = av_frame_alloc();
-    if (!avframe)
-        goto fail;
+    av_frame_unref(avframe);
 
     avframe->nb_samples = frame->samples;
     avframe->format = af_to_avformat(frame->format);
@@ -410,8 +416,8 @@ struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
     if (!avframe->channel_layout)
         goto fail;
 #if LIBAVUTIL_VERSION_MICRO >= 100
-    // FFmpeg being a stupid POS (but I respect it)
-    avframe->channels = frame->channels.num;
+    // FFmpeg being a stupid POS again
+    av_frame_set_channels(avframe, frame->channels.num);
 #endif
     avframe->sample_rate = frame->rate;
 
@@ -457,6 +463,23 @@ struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
         avframe = tmp;
     }
 
+    return 0;
+
+fail:
+    av_frame_unref(avframe);
+    return -1;
+}
+
+// Returns NULL on failure. The input is always unreffed.
+struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
+{
+    struct AVFrame *avframe = av_frame_alloc();
+    if (!avframe)
+        goto fail;
+
+    if (mp_audio_to_avframe(frame, avframe) < 0)
+        goto fail;
+
     talloc_free(frame);
     return avframe;
 
diff --git a/audio/audio.h b/audio/audio.h
index e126e93..0f32f08 100644
--- a/audio/audio.h
+++ b/audio/audio.h
@@ -81,6 +81,7 @@ int mp_audio_make_writeable(struct mp_audio *data);
 struct AVFrame;
 struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
 struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
+int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
 
 struct mp_audio_pool;
 struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
diff --git a/audio/chmap.c b/audio/chmap.c
index 1d4970d..bbd3a17 100644
--- a/audio/chmap.c
+++ b/audio/chmap.c
@@ -230,44 +230,6 @@ void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels)
     }
 }
 
-// Return channel index of the given speaker, or -1.
-static int mp_chmap_find_speaker(const struct mp_chmap *map, int speaker)
-{
-    for (int n = 0; n < map->num; n++) {
-        if (map->speaker[n] == speaker)
-            return n;
-    }
-    return -1;
-}
-
-static void mp_chmap_remove_speaker(struct mp_chmap *map, int speaker)
-{
-    int index = mp_chmap_find_speaker(map, speaker);
-    if (index >= 0) {
-        for (int n = index; n < map->num - 1; n++)
-            map->speaker[n] = map->speaker[n + 1];
-        map->num--;
-    }
-}
-
-// Some decoders output additional, redundant channels, which are usually
-// useless and will mess up proper audio output channel handling.
-// map: channel map from which the channels should be removed
-// requested: if not NULL, and if it contains any of the "useless" channels,
-//            don't remove them (this is for convenience)
-void mp_chmap_remove_useless_channels(struct mp_chmap *map,
-                                      const struct mp_chmap *requested)
-{
-    if (requested &&
-        mp_chmap_find_speaker(requested, MP_SPEAKER_ID_DL) >= 0)
-        return;
-
-    if (map->num > 2) {
-        mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DL);
-        mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DR);
-    }
-}
-
 // Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>.
 // Speakers not representable by ffmpeg/libav are dropped.
 // Warning: this ignores the order of the channels, and will return a channel
diff --git a/audio/chmap.h b/audio/chmap.h
index aa9b1c5..dff6933 100644
--- a/audio/chmap.h
+++ b/audio/chmap.h
@@ -109,9 +109,6 @@ void mp_chmap_fill_na(struct mp_chmap *map, int num);
 void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels);
 void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels);
 
-void mp_chmap_remove_useless_channels(struct mp_chmap *map,
-                                      const struct mp_chmap *requested);
-
 uint64_t mp_chmap_to_lavc(const struct mp_chmap *src);
 uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src);
 void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src);
diff --git a/audio/chmap_sel.c b/audio/chmap_sel.c
index 45b696c..4fb7544 100644
--- a/audio/chmap_sel.c
+++ b/audio/chmap_sel.c
@@ -374,3 +374,16 @@ void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev)
     if (s->allow_any)
         mp_msg(log, lev, " - anything\n");
 }
+
+// Select a channel map from the given list that fits best to c. Don't change
+// *c if there's no match, or the list is empty.
+void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps)
+{
+    // This is a separate function to keep messing with mp_chmap_sel internals
+    // within this source file.
+    struct mp_chmap_sel sel = {
+        .chmaps = maps,
+        .num_chmaps = num_maps,
+    };
+    mp_chmap_sel_fallback(&sel, c);
+}
diff --git a/audio/chmap_sel.h b/audio/chmap_sel.h
index 5bd8783..4b11557 100644
--- a/audio/chmap_sel.h
+++ b/audio/chmap_sel.h
@@ -47,4 +47,6 @@ bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
 struct mp_log;
 void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev);
 
+void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps);
+
 #endif
diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c
index 0316f6b..c785c62 100644
--- a/audio/decode/ad_lavc.c
+++ b/audio/decode/ad_lavc.c
@@ -104,9 +104,9 @@ static int init(struct dec_audio *da, const char *decoder)
     lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
     lavc_context->codec_id = lavc_codec->id;
 
-    if (opts->downmix) {
+    if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
         lavc_context->request_channel_layout =
-            mp_chmap_to_lavc(&mpopts->audio_output_channels);
+            mp_chmap_to_lavc(&mpopts->audio_output_channels.chmaps[0]);
     }
 
     // Always try to set - option only exists for AC3 at the moment
diff --git a/audio/filter/af.c b/audio/filter/af.c
index 21b0982..a132965 100644
--- a/audio/filter/af.c
+++ b/audio/filter/af.c
@@ -31,7 +31,6 @@
 #include "af.h"
 
 // Static list of filters
-extern const struct af_info af_info_delay;
 extern const struct af_info af_info_channels;
 extern const struct af_info af_info_format;
 extern const struct af_info af_info_volume;
@@ -46,7 +45,6 @@ extern const struct af_info af_info_lavfi;
 extern const struct af_info af_info_rubberband;
 
 static const struct af_info *const filter_list[] = {
-    &af_info_delay,
     &af_info_channels,
     &af_info_format,
     &af_info_volume,
@@ -256,6 +254,8 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
             mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
         if (af->data)
             mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
+        if (af->auto_inserted)
+            mp_snprintf_cat(b, sizeof(b), " [a]");
         if (af == at)
             mp_snprintf_cat(b, sizeof(b), " <-");
         MP_MSG(s, msg_level, "%s\n", b);
@@ -266,164 +266,156 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
     MP_MSG(s, msg_level, "  [ao] %s\n", mp_audio_config_to_str(&s->output));
 }
 
-// in is what af can take as input - insert a conversion filter if the actual
-// input format doesn't match what af expects.
-// Returns:
-//   AF_OK: must call af_reinit() or equivalent, format matches (or is closer)
-//   AF_FALSE: nothing was changed, format matches
-//   else: error
-static int af_fix_format_conversion(struct af_stream *s,
-                                    struct af_instance **p_af,
-                                    struct mp_audio in)
+static void reset_formats(struct af_stream *s)
 {
-    int rv;
-    struct af_instance *af = *p_af;
-    struct af_instance *prev = af->prev;
-    struct mp_audio actual = *prev->data;
-    if (actual.format == in.format)
-        return AF_FALSE;
-    int dstfmt = in.format;
-    char *filter = "lavrresample";
-    if (!af_lavrresample_test_conversion(actual.format, dstfmt))
-        return AF_ERROR;
-    if (strcmp(filter, prev->info->name) == 0) {
-        if (prev->control(prev, AF_CONTROL_SET_FORMAT, &dstfmt) == AF_OK) {
-            *p_af = prev;
-            return AF_OK;
-        }
-        return AF_ERROR;
-    }
-    struct af_instance *new = af_prepend(s, af, filter, NULL);
-    if (new == NULL)
-        return AF_ERROR;
-    new->auto_inserted = true;
-    if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt))) {
-        af_remove(s, new);
-        return rv;
+    struct mp_audio none = {0};
+    for (struct af_instance *af = s->first; af; af = af->next) {
+        if (af != s->first && af != s->last)
+            mp_audio_copy_config(af->data, &none);
     }
-    *p_af = new;
-    return AF_OK;
 }
 
-// same as af_fix_format_conversion - only wrt. channels
-static int af_fix_channels(struct af_stream *s, struct af_instance **p_af,
-                           struct mp_audio in)
+static int filter_reinit(struct af_instance *af)
 {
-    int rv;
-    struct af_instance *af = *p_af;
     struct af_instance *prev = af->prev;
-    struct mp_audio actual = *prev->data;
-    if (mp_chmap_equals(&actual.channels, &in.channels))
-        return AF_FALSE;
-    if (prev->control(prev, AF_CONTROL_SET_CHANNELS, &in.channels) == AF_OK) {
-        *p_af = prev;
-        return AF_OK;
-    }
-    char *filter = "lavrresample";
-    struct af_instance *new = af_prepend(s, af, filter, NULL);
-    if (new == NULL)
+    assert(prev);
+
+    // Check if this is the first filter
+    struct mp_audio in = *prev->data;
+    // Reset just in case...
+    mp_audio_set_null_data(&in);
+
+    if (!mp_audio_config_valid(&in))
         return AF_ERROR;
-    new->auto_inserted = true;
-    if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_CHANNELS, &in.channels)))
-        return rv;
-    *p_af = new;
-    return AF_OK;
+
+    af->fmt_in = in;
+    int rv = af->control(af, AF_CONTROL_REINIT, &in);
+    if (rv == AF_OK && !mp_audio_config_equals(&in, prev->data))
+        rv = AF_FALSE; // conversion filter needed
+    if (rv == AF_FALSE)
+        af->fmt_in = in;
+
+    if (rv == AF_OK) {
+        if (!mp_audio_config_valid(af->data))
+            return AF_ERROR;
+        af->fmt_out = *af->data;
+    }
+
+    return rv;
 }
 
-static int af_fix_rate(struct af_stream *s, struct af_instance **p_af,
-                       struct mp_audio in)
+static int filter_reinit_with_conversion(struct af_stream *s, struct af_instance *af)
 {
-    int rv;
-    struct af_instance *af = *p_af;
-    struct af_instance *prev = af->prev;
-    struct mp_audio actual = *prev->data;
-    if (actual.rate == in.rate)
-        return AF_FALSE;
-    if (prev->control(prev, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate) == AF_OK) {
-        *p_af = prev;
-        return AF_OK;
+    int rv = filter_reinit(af);
+
+    // Conversion filter is needed
+    if (rv == AF_FALSE) {
+        // First try if we can change the output format of the previous
+        // filter to the input format the current filter is expecting.
+        struct mp_audio in = af->fmt_in;
+        if (af->prev != s->first && !mp_audio_config_equals(af->prev->data, &in)) {
+            // This should have been successful (because it succeeded
+            // before), even if just reverting to the old output format.
+            mp_audio_copy_config(af->prev->data, &in);
+            rv = filter_reinit(af->prev);
+            if (rv != AF_OK)
+                return rv;
+        }
+        if (!mp_audio_config_equals(af->prev->data, &in)) {
+            // Retry with conversion filter added.
+            struct af_instance *new =
+                af_prepend(s, af, "lavrresample", NULL);
+            if (!new)
+                return AF_ERROR;
+            new->auto_inserted = true;
+            mp_audio_copy_config(new->data, &in);
+            rv = filter_reinit(new);
+            if (rv != AF_OK)
+                af_remove(s, new);
+        }
+        if (rv == AF_OK)
+            rv = filter_reinit(af);
     }
-    char *filter = "lavrresample";
-    struct af_instance *new = af_prepend(s, af, filter, NULL);
-    if (new == NULL)
-        return AF_ERROR;
-    new->auto_inserted = true;
-    if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate)))
-        return rv;
-    *p_af = new;
-    return AF_OK;
+
+    return rv;
 }
 
-static void reset_formats(struct af_stream *s)
+static int af_find_output_conversion(struct af_stream *s, struct mp_audio *cfg)
 {
-    for (struct af_instance *af = s->first; af; af = af->next) {
-        af->control(af, AF_CONTROL_SET_RESAMPLE_RATE, &(int){0});
-        af->control(af, AF_CONTROL_SET_CHANNELS, &(struct mp_chmap){0});
-        af->control(af, AF_CONTROL_SET_FORMAT, &(int){0});
+    assert(mp_audio_config_valid(&s->output));
+    assert(s->initialized > 0);
+
+    if (mp_chmap_equals_reordered(&s->input.channels, &s->output.channels))
+        return AF_ERROR;
+
+    // Heuristic to detect point of conversion. If it looks like something
+    // more complicated is going on, better bail out.
+    // We expect that the last filter converts channels.
+    struct af_instance *conv = s->last->prev;
+    if (!conv->auto_inserted)
+        return AF_ERROR;
+    if (!(mp_chmap_equals_reordered(&conv->fmt_in.channels, &s->input.channels) &&
+          mp_chmap_equals_reordered(&conv->fmt_out.channels, &s->output.channels)))
+        return AF_ERROR;
+    // Also, should be the only one which does auto conversion.
+    for (struct af_instance *af = s->first->next; af != s->last; af = af->next)
+    {
+        if (af != conv && af->auto_inserted &&
+            !mp_chmap_equals_reordered(&af->fmt_in.channels, &af->fmt_out.channels))
+            return AF_ERROR;
     }
+    // And not if it's the only filter.
+    if (conv->prev == s->first && conv->next == s->last)
+        return AF_ERROR;
+
+    *cfg = s->output;
+    return AF_OK;
 }
 
 // Return AF_OK on success or AF_ERROR on failure.
-// Warning:
-// A failed af_reinit() leaves the audio chain behind in a useless, broken
-// state (for example, format filters that were tentatively inserted stay
-// inserted).
-// In that case, you should always rebuild the filter chain, or abort.
-static int af_reinit(struct af_stream *s)
-{
+static int af_do_reinit(struct af_stream *s, bool second_pass)
+{
+    struct mp_audio convert_early = {0};
+    if (second_pass) {
+        // If a channel conversion happens, and it is done by an auto-inserted
+        // filter, then insert a filter to convert it early. Otherwise, do
+        // nothing and return immediately.
+        if (af_find_output_conversion(s, &convert_early) != AF_OK)
+            return AF_OK;
+    }
+
     remove_auto_inserted_filters(s);
     af_chain_forget_frames(s);
     reset_formats(s);
     s->first->fmt_in = s->first->fmt_out = s->input;
+
+    if (mp_audio_config_valid(&convert_early)) {
+        struct af_instance *new = af_prepend(s, s->first, "lavrresample", NULL);
+        if (!new)
+            return AF_ERROR;
+        new->auto_inserted = true;
+        mp_audio_copy_config(new->data, &convert_early);
+        int rv = filter_reinit(new);
+        if (rv != AF_DETACH && rv != AF_OK)
+            return AF_ERROR;
+        MP_VERBOSE(s, "Moving up output conversion.\n");
+    }
+
     // Start with the second filter, as the first filter is the special input
     // filter which needs no initialization.
     struct af_instance *af = s->first->next;
-    // Up to 4 retries per filter (channel, rate, format conversions)
-    int max_retry = 4;
-    int retry = 0;
     while (af) {
-        if (retry >= max_retry)
-            goto negotiate_error;
-
-        // Check if this is the first filter
-        struct mp_audio in = *af->prev->data;
-        // Reset just in case...
-        mp_audio_set_null_data(&in);
-
-        if (!mp_audio_config_valid(&in))
-            goto error;
+        int rv = filter_reinit_with_conversion(s, af);
 
-        af->fmt_in = in;
-        int rv = af->control(af, AF_CONTROL_REINIT, &in);
-        if (rv == AF_OK && !mp_audio_config_equals(&in, af->prev->data))
-            rv = AF_FALSE; // conversion filter needed
         switch (rv) {
         case AF_OK:
-            if (!mp_audio_config_valid(af->data))
-                goto error;
-            af->fmt_out = *af->data;
             af = af->next;
             break;
-        case AF_FALSE: { // Configuration filter is needed
-            if (af_fix_channels(s, &af, in) == AF_OK) {
-                retry++;
-                continue;
-            }
-            if (af_fix_rate(s, &af, in) == AF_OK) {
-                retry++;
-                continue;
-            }
-            // Do this last, to prevent "format->lavrresample" being added to
-            // the filter chain when output formats not supported by
-            // af_lavrresample are in use.
-            if (af_fix_format_conversion(s, &af, in) == AF_OK) {
-                retry++;
-                continue;
-            }
+        case AF_FALSE: {
             // If the format conversion is (probably) caused by spdif, then
             // (as a feature) drop the filter, instead of failing hard.
             int fmt_in1 = af->prev->data->format;
-            int fmt_in2 = in.format;
+            int fmt_in2 = af->fmt_in.format;
             if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
                 bool spd1 = af_fmt_is_spdif(fmt_in1);
                 bool spd2 = af_fmt_is_spdif(fmt_in2);
@@ -434,7 +426,6 @@ static int af_reinit(struct af_stream *s)
                     struct af_instance *aft = af->prev;
                     af_remove(s, af);
                     af = aft->next;
-                    retry++;
                     continue;
                 }
             }
@@ -452,8 +443,6 @@ static int af_reinit(struct af_stream *s)
                    af->info->name, rv);
             goto error;
         }
-        if (af && !af->auto_inserted)
-            retry = 0;
     }
 
     /* Set previously unset fields in s->output to those of the filter chain
@@ -477,6 +466,19 @@ error:
     return AF_ERROR;
 }
 
+static int af_reinit(struct af_stream *s)
+{
+    int r = af_do_reinit(s, false);
+    if (r == AF_OK && mp_audio_config_valid(&s->output)) {
+        r = af_do_reinit(s, true);
+        if (r != AF_OK) {
+            MP_ERR(s, "Failed second pass filter negotiation.\n");
+            r = af_do_reinit(s, false);
+        }
+    }
+    return r;
+}
+
 // Uninit and remove all filters
 void af_uninit(struct af_stream *s)
 {
diff --git a/audio/filter/af.h b/audio/filter/af.h
index 9c49081..697024b 100644
--- a/audio/filter/af.h
+++ b/audio/filter/af.h
@@ -112,9 +112,6 @@ struct af_stream {
 enum af_control {
     AF_CONTROL_REINIT = 1,
     AF_CONTROL_RESET,
-    AF_CONTROL_SET_RESAMPLE_RATE,
-    AF_CONTROL_SET_FORMAT,
-    AF_CONTROL_SET_CHANNELS,
     AF_CONTROL_SET_VOLUME,
     AF_CONTROL_GET_VOLUME,
     AF_CONTROL_SET_PAN_LEVEL,
@@ -160,6 +157,4 @@ int af_test_output(struct af_instance *af, struct mp_audio *out);
 int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
 float af_softclip(float a);
 
-bool af_lavrresample_test_conversion(int src_format, int dst_format);
-
 #endif /* MPLAYER_AF_H */
diff --git a/audio/filter/af_delay.c b/audio/filter/af_delay.c
deleted file mode 100644
index 8d1cca8..0000000
--- a/audio/filter/af_delay.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * This audio filter delays the output signal for the different
- * channels and can be used for simple position panning.
- * An extension for this filter would be a reverb.
- *
- * Original author: Anders
- *
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <inttypes.h>
-
-#include "common/common.h"
-#include "af.h"
-
-#define L 65536
-
-#define UPDATEQI(qi) qi=(qi+1)&(L-1)
-
-// Data for specific instances of this filter
-typedef struct af_delay_s
-{
-  void* q[AF_NCH];      // Circular queues used for delaying audio signal
-  int   wi[AF_NCH];     // Write index
-  int   ri;             // Read index
-  float d[AF_NCH];      // Delay [ms]
-  char *delaystr;
-}af_delay_t;
-
-// Initialization and runtime control
-static int control(struct af_instance* af, int cmd, void* arg)
-{
-  af_delay_t* s = af->priv;
-  switch(cmd){
-  case AF_CONTROL_REINIT:{
-    int i;
-    struct mp_audio *in = arg;
-
-    if (in->bps != 1 && in->bps != 2 && in->bps != 4) {
-      MP_FATAL(af, "Sample format not supported\n");
-      return AF_ERROR;
-    }
-
-    // Free prevous delay queues
-    for(i=0;i<af->data->nch;i++)
-      free(s->q[i]);
-
-    mp_audio_force_interleaved_format(in);
-    mp_audio_copy_config(af->data, in);
-
-    // Allocate new delay queues
-    for(i=0;i<af->data->nch;i++){
-      s->q[i] = calloc(L,af->data->bps);
-      if(NULL == s->q[i])
-        MP_FATAL(af, "Out of memory\n");
-    }
-
-    if(AF_OK != af_from_ms(AF_NCH, s->d, s->wi, af->data->rate, 0.0, 1000.0))
-      return AF_ERROR;
-    s->ri = 0;
-    for(i=0;i<AF_NCH;i++){
-      MP_DBG(af, "Channel %i delayed by %0.3fms\n",
-             i,MPCLAMP(s->d[i],0.0,1000.0));
-      MP_TRACE(af, "Channel %i delayed by %i samples\n",
-             i,s->wi[i]);
-    }
-    return AF_OK;
-  }
-  }
-  return AF_UNKNOWN;
-}
-
-// Deallocate memory
-static void uninit(struct af_instance* af)
-{
-  int i;
-
-  for(i=0;i<AF_NCH;i++)
-      free(((af_delay_t*)(af->priv))->q[i]);
-}
-
-static int filter_frame(struct af_instance *af, struct mp_audio *c)
-{
-  if (!c)
-    return 0;
-  af_delay_t*   s   = af->priv; // Setup for this instance
-  int           nch = c->nch;    // Number of channels
-  int           len = mp_audio_psize(c)/c->bps; // Number of sample in data chunk
-  int           ri  = 0;
-  int           ch,i;
-  if (af_make_writeable(af, c) < 0) {
-    talloc_free(c);
-    return -1;
-  }
-  for(ch=0;ch<nch;ch++){
-    switch(c->bps){
-    case 1:{
-      int8_t* a = c->planes[0];
-      int8_t* q = s->q[ch];
-      int wi = s->wi[ch];
-      ri = s->ri;
-      for(i=ch;i<len;i+=nch){
-        q[wi] = a[i];
-        a[i]  = q[ri];
-        UPDATEQI(wi);
-        UPDATEQI(ri);
-      }
-      s->wi[ch] = wi;
-      break;
-    }
-    case 2:{
-      int16_t* a = c->planes[0];
-      int16_t* q = s->q[ch];
-      int wi = s->wi[ch];
-      ri = s->ri;
-      for(i=ch;i<len;i+=nch){
-        q[wi] = a[i];
-        a[i]  = q[ri];
-        UPDATEQI(wi);
-        UPDATEQI(ri);
-      }
-      s->wi[ch] = wi;
-      break;
-    }
-    case 4:{
-      int32_t* a = c->planes[0];
-      int32_t* q = s->q[ch];
-      int wi = s->wi[ch];
-      ri = s->ri;
-      for(i=ch;i<len;i+=nch){
-        q[wi] = a[i];
-        a[i]  = q[ri];
-        UPDATEQI(wi);
-        UPDATEQI(ri);
-      }
-      s->wi[ch] = wi;
-      break;
-    }
-    }
-  }
-  s->ri = ri;
-  af_add_output_frame(af, c);
-  return 0;
-}
-
-// Allocate memory and set function pointers
-static int af_open(struct af_instance* af){
-    af->control=control;
-    af->uninit=uninit;
-    af->filter_frame = filter_frame;
-    af_delay_t *s = af->priv;
-    int n = 1;
-    int i = 0;
-    char* cl = s->delaystr;
-    while(cl && n && i < AF_NCH ){
-      sscanf(cl,"%f%n",&s->d[i],&n);
-      if(n==0 || cl[n-1] == '\0')
-        break;
-      cl=&cl[n];
-      if (*cl != ',')
-          break;
-      cl++;
-      i++;
-    }
-    return AF_OK;
-}
-
-#define OPT_BASE_STRUCT af_delay_t
-const struct af_info af_info_delay = {
-    .info = "Delay audio filter",
-    .name = "delay",
-    .open = af_open,
-    .priv_size = sizeof(af_delay_t),
-    .options = (const struct m_option[]) {
-        OPT_STRING("delays", delaystr, 0),
-        {0}
-    },
-};
diff --git a/audio/filter/af_format.c b/audio/filter/af_format.c
index c0fe354..748c5cb 100644
--- a/audio/filter/af_format.c
+++ b/audio/filter/af_format.c
@@ -29,10 +29,10 @@ struct priv {
 
     int in_format;
     int in_srate;
-    struct mp_chmap in_channels;
+    struct m_channels in_channels;
     int out_format;
     int out_srate;
-    struct mp_chmap out_channels;
+    struct m_channels out_channels;
 
     int fail;
 };
@@ -44,8 +44,8 @@ static void force_in_params(struct af_instance *af, struct mp_audio *in)
     if (priv->in_format != AF_FORMAT_UNKNOWN)
         mp_audio_set_format(in, priv->in_format);
 
-    if (priv->in_channels.num)
-        mp_audio_set_channels(in, &priv->in_channels);
+    if (priv->in_channels.num_chmaps > 0)
+        mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
 
     if (priv->in_srate)
         in->rate = priv->in_srate;
@@ -58,8 +58,8 @@ static void force_out_params(struct af_instance *af, struct mp_audio *out)
     if (priv->out_format != AF_FORMAT_UNKNOWN)
         mp_audio_set_format(out, priv->out_format);
 
-    if (priv->out_channels.num)
-        mp_audio_set_channels(out, &priv->out_channels);
+    if (priv->out_channels.num_chmaps > 0)
+        mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
 
     if (priv->out_srate)
         out->rate = priv->out_srate;
@@ -124,10 +124,10 @@ const struct af_info af_info_format = {
     .options = (const struct m_option[]) {
         OPT_AUDIOFORMAT("format", in_format, 0),
         OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
-        OPT_CHMAP("channels", in_channels, CONF_MIN, .min = 0),
+        OPT_CHANNELS("channels", in_channels, 0, .min = 1),
         OPT_AUDIOFORMAT("out-format", out_format, 0),
         OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
-        OPT_CHMAP("out-channels", out_channels, CONF_MIN, .min = 0),
+        OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
         OPT_FLAG("fail", fail, 0),
         {0}
     },
diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c
index 26c9cbf..0a7c5d4 100644
--- a/audio/filter/af_lavcac3enc.c
+++ b/audio/filter/af_lavcac3enc.c
@@ -33,6 +33,7 @@
 
 #include "config.h"
 
+#include "common/av_common.h"
 #include "common/common.h"
 #include "af.h"
 #include "audio/audio_buffer.h"
@@ -57,11 +58,13 @@ typedef struct af_ac3enc_s {
     struct mp_audio *pending;   // unconsumed input data
     int in_samples;     // samples of input per AC3 frame
     int out_samples;    // upper bound on encoded output per AC3 frame
+    int64_t encoder_buffered;
 
     int cfg_add_iec61937_header;
     int cfg_bit_rate;
     int cfg_min_channel_num;
     char *cfg_encoder;
+    char **cfg_avopts;
 } af_ac3enc_t;
 
 // fmt carries the input format. Change it to the best next-possible format
@@ -168,6 +171,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
         s->in_samples = s->lavc_actx->frame_size;
         mp_audio_realloc(s->input, s->in_samples);
         s->input->samples = 0;
+        s->encoder_buffered = 0;
         return AF_OK;
     }
     case AF_CONTROL_RESET:
@@ -176,6 +180,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
         talloc_free(s->pending);
         s->pending = NULL;
         s->input->samples = 0;
+        s->encoder_buffered = 0;
         return AF_OK;
     }
     return AF_UNKNOWN;
@@ -198,8 +203,8 @@ static void uninit(struct af_instance* af)
 static void update_delay(struct af_instance *af)
 {
     af_ac3enc_t *s = af->priv;
-    af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples) /
-                (double)s->input->rate;
+    af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
+                  s->encoder_buffered) / (double)s->input->rate;
 }
 
 static int filter_frame(struct af_instance *af, struct mp_audio *audio)
@@ -232,6 +237,9 @@ static bool fill_buffer(struct af_instance *af)
     af->delay = 0;
 
     if (s->pending) {
+        if (!mp_audio_is_writeable(s->input))
+            assert(s->input->samples == 0); // we can't have sent a partial frame
+        mp_audio_realloc_min(s->input, s->in_samples);
         int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
         s->input->samples += copy;
         mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
@@ -249,17 +257,8 @@ static int read_input_frame(struct af_instance *af, AVFrame *frame)
     if (!fill_buffer(af))
         return 0; // need more input
 
-    frame->nb_samples = s->in_samples;
-    frame->format = s->lavc_actx->sample_fmt;
-    frame->channel_layout = s->lavc_actx->channel_layout;
-#if LIBAVUTIL_VERSION_MICRO >= 100
-    frame->channels = s->lavc_actx->channels;
-#endif
-    assert(s->input->num_planes <= AV_NUM_DATA_POINTERS);
-    frame->extended_data = frame->data;
-    for (int n = 0; n < s->input->num_planes; n++)
-        frame->data[n] = s->input->planes[n];
-    frame->linesize[0] = s->input->samples * s->input->sstride;
+    if (mp_audio_to_avframe(s->input, frame) < 0)
+        return -1;
 
     return 1;
 }
@@ -268,6 +267,9 @@ static int filter_out(struct af_instance *af)
 {
     af_ac3enc_t *s = af->priv;
 
+    if (!s->pending)
+        return 0;
+
     AVFrame *frame = av_frame_alloc();
     if (!frame) {
         MP_FATAL(af, "Could not allocate memory \n");
@@ -295,6 +297,7 @@ static int filter_out(struct af_instance *af)
             MP_FATAL(af, "Encode failed.\n");
             goto done;
         }
+        s->encoder_buffered += s->input->samples;
         s->input->samples = 0;
     }
     int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
@@ -303,6 +306,10 @@ static int filter_out(struct af_instance *af)
         err = 0;
         goto done;
     }
+    if (lavc_ret < 0) {
+        MP_FATAL(af, "Encode failed.\n");
+        goto done;
+    }
 #else
     err = read_input_frame(af, frame);
     if (err < 0)
@@ -312,7 +319,6 @@ static int filter_out(struct af_instance *af)
     err = -1;
     int ok;
     int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &pkt, frame, &ok);
-    av_frame_free(&frame);
     s->input->samples = 0;
     if (lavc_ret < 0 || !ok) {
         MP_FATAL(af, "Encode failed.\n");
@@ -321,7 +327,9 @@ static int filter_out(struct af_instance *af)
 #endif
 
     MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
-           pkt.size, s->pending->samples);
+           pkt.size, s->pending->samples + s->input->samples);
+
+    s->encoder_buffered -= AC3_FRAME_SIZE;
 
     struct mp_audio *out =
         mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
@@ -358,11 +366,12 @@ static int filter_out(struct af_instance *af)
     swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
     out->samples = frame_size / out->sstride;
     af_add_output_frame(af, out);
-    update_delay(af);
 
     err = 0;
 done:
     av_packet_unref(&pkt);
+    av_frame_free(&frame);
+    update_delay(af);
     return err;
 }
 
@@ -385,6 +394,10 @@ static int af_open(struct af_instance* af){
         MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
         return AF_ERROR;
     }
+
+    if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
+        return AF_ERROR;
+
     // For this one, we require the decoder to expert lists of all supported
     // parameters. (Not all decoders do that, but the ones we're interested
     // in do.)
@@ -434,6 +447,7 @@ const struct af_info af_info_lavcac3enc = {
                           ({"auto", 0}, {"default", 0})),
         OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
         OPT_STRING("encoder", cfg_encoder, 0),
+        OPT_KEYVALUELIST("o", cfg_avopts, 0),
         {0}
     },
 };
diff --git a/audio/filter/af_lavrresample.c b/audio/filter/af_lavrresample.c
index 6fbb445..fdef69a 100644
--- a/audio/filter/af_lavrresample.c
+++ b/audio/filter/af_lavrresample.c
@@ -173,12 +173,6 @@ static int check_output_conversion(int mp_format)
     return af_to_avformat(mp_format);
 }
 
-bool af_lavrresample_test_conversion(int src_format, int dst_format)
-{
-    return af_to_avformat(src_format) != AV_SAMPLE_FMT_NONE &&
-           check_output_conversion(dst_format) != AV_SAMPLE_FMT_NONE;
-}
-
 static struct mp_chmap fudge_pairs[][2] = {
     {MP_CHMAP2(BL,  BR),  MP_CHMAP2(SL,  SR)},
     {MP_CHMAP2(SL,  SR),  MP_CHMAP2(BL,  BR)},
@@ -407,21 +401,6 @@ static int control(struct af_instance *af, int cmd, void *arg)
             r = configure_lavrr(af, in, out, true);
         return r;
     }
-    case AF_CONTROL_SET_FORMAT: {
-        int format = *(int *)arg;
-        if (format && check_output_conversion(format) == AV_SAMPLE_FMT_NONE)
-            return AF_FALSE;
-
-        mp_audio_set_format(af->data, format);
-        return AF_OK;
-    }
-    case AF_CONTROL_SET_CHANNELS: {
-        mp_audio_set_channels(af->data, (struct mp_chmap *)arg);
-        return AF_OK;
-    }
-    case AF_CONTROL_SET_RESAMPLE_RATE:
-        af->data->rate = *(int *)arg;
-        return AF_OK;
     case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
         s->playback_speed = *(double *)arg;
         return AF_OK;
diff --git a/audio/filter/af_volume.c b/audio/filter/af_volume.c
index 7bd7edd..e1d5d45 100644
--- a/audio/filter/af_volume.c
+++ b/audio/filter/af_volume.c
@@ -71,15 +71,19 @@ static int control(struct af_instance *af, int cmd, void *arg)
         if (af_fmt_is_planar(in->format))
             mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
         s->rgain = 1.0;
-        if ((s->rgain_track || s->rgain_album) && af->replaygain_data) {
-            float gain, peak;
+        struct replaygain_data *rg = af->replaygain_data;
+        if ((s->rgain_track || s->rgain_album) && rg) {
+            MP_VERBOSE(af, "Replaygain: Track=%f/%f Album=%f/%f\n",
+                       rg->track_gain, rg->track_peak,
+                       rg->album_gain, rg->album_peak);
 
+            float gain, peak;
             if (s->rgain_track) {
-                gain = af->replaygain_data->track_gain;
-                peak = af->replaygain_data->track_peak;
+                gain = rg->track_gain;
+                peak = rg->track_peak;
             } else {
-                gain = af->replaygain_data->album_gain;
-                peak = af->replaygain_data->album_peak;
+                gain = rg->album_gain;
+                peak = rg->album_peak;
             }
 
             gain += s->rgain_preamp;
@@ -115,7 +119,7 @@ static void filter_plane(struct af_instance *af, struct mp_audio *data, int p)
 {
     struct priv *s = af->priv;
 
-    float level = s->level * s->rgain;
+    float level = s->level * s->rgain * from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
     int num_samples = data->samples * data->spf;
 
     if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
@@ -158,7 +162,7 @@ static int af_open(struct af_instance *af)
     struct priv *s = af->priv;
     af->control = control;
     af->filter_frame = filter;
-    s->level = from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
+    s->level = 1.0;
     return AF_OK;
 }
 
diff --git a/audio/mixer.c b/audio/mixer.c
deleted file mode 100644
index a58a814..0000000
--- a/audio/mixer.c
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <string.h>
-#include <stdio.h>
-#include <math.h>
-#include <assert.h>
-
-#include <libavutil/common.h>
-
-#include "config.h"
-#include "audio/out/ao.h"
-#include "audio/filter/af.h"
-#include "common/global.h"
-#include "common/msg.h"
-#include "mpv_talloc.h"
-#include "mixer.h"
-
-struct mixer {
-    struct mp_log *log;
-    struct MPOpts *opts;
-    struct ao *ao;
-    struct af_stream *af;
-    // Other stuff
-    float balance;
-};
-
-struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global)
-{
-    struct mixer *mixer = talloc_ptrtype(talloc_ctx, mixer);
-    *mixer = (struct mixer) {
-        .log = mp_log_new(mixer, global->log, "mixer"),
-        .opts = global->opts,
-    };
-    return mixer;
-}
-
-bool mixer_audio_initialized(struct mixer *mixer)
-{
-    return !!mixer->af;
-}
-
-// Called when opts->softvol_volume or opts->softvol_mute were changed.
-void mixer_update_volume(struct mixer *mixer)
-{
-    if (!mixer->af)
-        return;
-
-    float gain = MPMAX(mixer->opts->softvol_volume / 100.0, 0);
-    if (mixer->opts->softvol_mute == 1)
-        gain = 0.0;
-
-    if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
-        if (gain == 1.0)
-            return;
-        MP_VERBOSE(mixer, "Inserting volume filter.\n");
-        if (!(af_add(mixer->af, "volume", "softvol", NULL)
-              && af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)))
-            MP_ERR(mixer, "No volume control available.\n");
-    }
-}
-
-void mixer_getbalance(struct mixer *mixer, float *val)
-{
-    if (mixer->af)
-        af_control_any_rev(mixer->af, AF_CONTROL_GET_PAN_BALANCE, &mixer->balance);
-    *val = mixer->balance;
-}
-
-/* NOTE: Currently the balance code is seriously buggy: it always changes
- * the af_pan mapping between the first two input channels and first two
- * output channels to particular values. These values make sense for an
- * af_pan instance that was automatically inserted for balance control
- * only and is otherwise an identity transform, but if the filter was
- * there for another reason, then ignoring and overriding the original
- * values is completely wrong.
- */
-
-void mixer_setbalance(struct mixer *mixer, float val)
-{
-    struct af_instance *af_pan_balance;
-
-    mixer->balance = val;
-
-    if (!mixer->af)
-        return;
-
-    if (af_control_any_rev(mixer->af, AF_CONTROL_SET_PAN_BALANCE, &val))
-        return;
-
-    if (val == 0)
-        return;
-
-    if (!(af_pan_balance = af_add(mixer->af, "pan", "autopan", NULL))) {
-        MP_ERR(mixer, "No balance control available.\n");
-        return;
-    }
-
-    /* make all other channels pass through since by default pan blocks all */
-    for (int i = 2; i < AF_NCH; i++) {
-        float level[AF_NCH] = {0};
-        level[i] = 1.f;
-        af_control_ext_t arg_ext = { .ch = i, .arg = level };
-        af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_LEVEL,
-                                &arg_ext);
-    }
-
-    af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
-}
-
-// Called after the audio filter chain is built or rebuilt.
-// (Can be called multiple times, even without mixer_uninit() in-between.)
-void mixer_reinit_audio(struct mixer *mixer, struct af_stream *af)
-{
-    mixer->af = af;
-    if (!af)
-        return;
-
-    if (mixer->opts->softvol == SOFTVOL_NO)
-        MP_ERR(mixer, "--softvol=no is not supported anymore.\n");
-
-    mixer_update_volume(mixer);
-
-    if (mixer->balance != 0)
-        mixer_setbalance(mixer, mixer->balance);
-}
-
-/* Called before uninitializing the audio filter chain. The main purpose is to
- * turn off mute, in case it's a global/persistent setting which might
- * otherwise be left enabled even after this player instance exits.
- */
-void mixer_uninit_audio(struct mixer *mixer)
-{
-    if (!mixer->ao)
-        return;
-
-    mixer->af = NULL;
-}
diff --git a/audio/mixer.h b/audio/mixer.h
deleted file mode 100644
index b475c12..0000000
--- a/audio/mixer.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * This file is part of mpv.
- *
- * mpv is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * mpv is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with mpv.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef MPLAYER_MIXER_H
-#define MPLAYER_MIXER_H
-
-#include <stdbool.h>
-
-// Values of MPOpts.softvol
-enum {
-    SOFTVOL_NO = 0,
-    SOFTVOL_YES = 1,
-    SOFTVOL_AUTO = 2,
-};
-
-struct mpv_global;
-struct ao;
-struct af_stream;
-struct mixer;
-
-struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global);
-void mixer_reinit_audio(struct mixer *mixer, struct af_stream *af);
-void mixer_uninit_audio(struct mixer *mixer);
-bool mixer_audio_initialized(struct mixer *mixer);
-void mixer_update_volume(struct mixer *mixer);
-void mixer_getbalance(struct mixer *mixer, float *bal);
-void mixer_setbalance(struct mixer *mixer, float bal);
-
-#endif /* MPLAYER_MIXER_H */
diff --git a/audio/out/ao.c b/audio/out/ao.c
index c9d8f42..cf66e0c 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -158,7 +158,7 @@ error:
 
 static struct ao *ao_init(bool probing, struct mpv_global *global,
                           struct input_ctx *input_ctx,
-                          struct encode_lavc_context *encode_lavc_ctx,
+                          struct encode_lavc_context *encode_lavc_ctx, int flags,
                           int samplerate, int format, struct mp_chmap channels,
                           char *dev, char *name, char **args)
 {
@@ -169,6 +169,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
     ao->channels = channels;
     ao->format = format;
     ao->encode_lavc_ctx = encode_lavc_ctx;
+    ao->init_flags = flags;
     if (ao->driver->encode != !!ao->encode_lavc_ctx)
         goto fail;
 
@@ -182,6 +183,8 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
     ao->api_priv = talloc_zero_size(ao, ao->api->priv_size);
     assert(!ao->api->priv_defaults && !ao->api->options);
 
+    ao->stream_silence = flags & AO_INIT_STREAM_SILENCE;
+
     int r = ao->driver->init(ao);
     if (r < 0) {
         // Silly exception for coreaudio spdif redirection
@@ -190,7 +193,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
             snprintf(redirect, sizeof(redirect), "%s", ao->redirect);
             snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
             talloc_free(ao);
-            return ao_init(probing, global, input_ctx, encode_lavc_ctx,
+            return ao_init(probing, global, input_ctx, encode_lavc_ctx, flags,
                            samplerate, format, channels, rdevice, redirect, NULL);
         }
         goto fail;
@@ -240,7 +243,7 @@ static void split_ao_device(void *tmp, char *opt, char **out_ao, char **out_dev)
 }
 
 struct ao *ao_init_best(struct mpv_global *global,
-                        bool ao_null_fallback,
+                        int init_flags,
                         struct input_ctx *input_ctx,
                         struct encode_lavc_context *encode_lavc_ctx,
                         int samplerate, int format, struct mp_chmap channels)
@@ -283,7 +286,7 @@ struct ao *ao_init_best(struct mpv_global *global,
         }
     }
 
-    if (ao_null_fallback) {
+    if (init_flags & AO_INIT_NULL_FALLBACK) {
         MP_TARRAY_APPEND(tmp, ao_list, ao_num,
             (struct m_obj_settings){.name = "null"});
     }
@@ -297,7 +300,7 @@ struct ao *ao_init_best(struct mpv_global *global,
             dev = pref_dev;
             mp_verbose(log, "Using preferred device '%s'\n", dev);
         }
-        ao = ao_init(probing, global, input_ctx, encode_lavc_ctx,
+        ao = ao_init(probing, global, input_ctx, encode_lavc_ctx, init_flags,
                      samplerate, format, channels, dev,
                      entry->name, entry->attribs);
         if (ao)
@@ -429,6 +432,29 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
     return r;
 }
 
+// safe_multichannel=true behaves like ao_chmap_sel_adjust.
+// safe_multichannel=false is a helper for callers which do not support safe
+// handling of arbitrary channel layouts. If the multichannel layouts are not
+// considered "always safe" (e.g. HDMI), then allow only stereo or mono, if
+// they are part of the list in *s.
+bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
+                          struct mp_chmap *map, bool safe_multichannel)
+{
+    if (!safe_multichannel && (ao->init_flags & AO_INIT_SAFE_MULTICHANNEL_ONLY)) {
+        struct mp_chmap res = *map;
+        if (mp_chmap_sel_adjust(s, &res)) {
+            if (!mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_MONO) &&
+                !mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_STEREO))
+            {
+                MP_WARN(ao, "Disabling multichannel output.\n");
+                *map = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
+            }
+        }
+    }
+
+    return ao_chmap_sel_adjust(ao, s, map);
+}
+
 bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
                           struct mp_chmap *map, int num)
 {
diff --git a/audio/out/ao.h b/audio/out/ao.h
index e8e64e3..3b187e7 100644
--- a/audio/out/ao.h
+++ b/audio/out/ao.h
@@ -50,6 +50,16 @@ enum {
     AO_EVENT_HOTPLUG = 2,
 };
 
+enum {
+    // Allow falling back to ao_null if nothing else works.
+    AO_INIT_NULL_FALLBACK = 1 << 0,
+    // Only accept multichannel configurations that are guaranteed to work
+    // (i.e. not sending arbitrary layouts over HDMI).
+    AO_INIT_SAFE_MULTICHANNEL_ONLY = 1 << 1,
+    // Stream silence as long as no audio is playing.
+    AO_INIT_STREAM_SILENCE = 1 << 2,
+};
+
 typedef struct ao_control_vol {
     float left;
     float right;
@@ -72,7 +82,7 @@ struct encode_lavc_context;
 struct mp_audio;
 
 struct ao *ao_init_best(struct mpv_global *global,
-                        bool ao_null_fallback,
+                        int init_flags,
                         struct input_ctx *input_ctx,
                         struct encode_lavc_context *encode_lavc_ctx,
                         int samplerate, int format, struct mp_chmap channels);
diff --git a/audio/out/ao_alsa.c b/audio/out/ao_alsa.c
index d09f5fc..bbe15b4 100644
--- a/audio/out/ao_alsa.c
+++ b/audio/out/ao_alsa.c
@@ -53,6 +53,7 @@ struct priv {
     bool device_lost;
     snd_pcm_format_t alsa_fmt;
     bool can_pause;
+    bool paused;
     snd_pcm_sframes_t prepause_frames;
     double delay_before_pause;
     snd_pcm_uframes_t buffersize;
@@ -360,7 +361,7 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
 
     snd_pcm_free_chmaps(maps);
 
-    return ao_chmap_sel_adjust(ao, &chmap_sel, chmap);
+    return ao_chmap_sel_adjust2(ao, &chmap_sel, chmap, false);
 }
 
 // Map back our selected channel layout to an ALSA one. This is done this way so
@@ -785,6 +786,14 @@ static int init_device(struct ao *ao, int mode)
     MP_VERBOSE(ao, "buffersize: %d samples\n", (int)p->buffersize);
     MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst);
 
+    ao->device_buffer = p->buffersize;
+
+    // ao_alsa implements this by relying on underrun behavior (no data means
+    // underrun, during which silence is played). Trigger by playing some
+    // initial silence.
+    if (ao->stream_silence)
+        ao_play_silence(ao, p->outburst);
+
     return 0;
 
 alsa_error:
@@ -874,7 +883,7 @@ static double get_delay(struct ao *ao)
     struct priv *p = ao->priv;
     snd_pcm_sframes_t delay;
 
-    if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
+    if (p->paused)
         return p->delay_before_pause;
 
     if (snd_pcm_delay(p->alsa, &delay) < 0)
@@ -888,27 +897,47 @@ static double get_delay(struct ao *ao)
     return delay / (double)ao->samplerate;
 }
 
+// For stream-silence mode: replace remaining buffer with silence.
+// Tries to cause an instant buffer underrun.
+static void soft_reset(struct ao *ao)
+{
+    struct priv *p = ao->priv;
+    snd_pcm_sframes_t frames = snd_pcm_rewindable(p->alsa);
+    if (frames > 0 && snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
+        frames = snd_pcm_rewind(p->alsa, frames);
+        if (frames < 0) {
+            int err = frames;
+            CHECK_ALSA_WARN("pcm rewind error");
+        }
+    }
+}
+
 static void audio_pause(struct ao *ao)
 {
     struct priv *p = ao->priv;
     int err;
 
-    if (p->can_pause) {
+    if (p->paused)
+        return;
+
+    p->delay_before_pause = get_delay(ao);
+    p->prepause_frames = p->delay_before_pause * ao->samplerate;
+
+    if (ao->stream_silence) {
+        soft_reset(ao);
+    } else if (p->can_pause) {
         if (snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
-            p->delay_before_pause = get_delay(ao);
             err = snd_pcm_pause(p->alsa, 1);
             CHECK_ALSA_ERROR("pcm pause error");
+            p->prepause_frames = 0;
         }
     } else {
-        if (snd_pcm_delay(p->alsa, &p->prepause_frames) < 0
-            || p->prepause_frames < 0)
-            p->prepause_frames = 0;
-        p->delay_before_pause = p->prepause_frames / (double)ao->samplerate;
-
         err = snd_pcm_drop(p->alsa);
         CHECK_ALSA_ERROR("pcm drop error");
     }
 
+    p->paused = true;
+
 alsa_error: ;
 }
 
@@ -930,9 +959,15 @@ static void audio_resume(struct ao *ao)
     struct priv *p = ao->priv;
     int err;
 
+    if (!p->paused)
+        return;
+
     resume_device(ao);
 
-    if (p->can_pause) {
+    if (ao->stream_silence) {
+        p->paused = false;
+        get_delay(ao); // recovers from underrun (as a side-effect)
+    } else if (p->can_pause) {
         if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) {
             err = snd_pcm_pause(p->alsa, 0);
             CHECK_ALSA_ERROR("pcm resume error");
@@ -941,11 +976,13 @@ static void audio_resume(struct ao *ao)
         MP_VERBOSE(ao, "resume not supported by hardware\n");
         err = snd_pcm_prepare(p->alsa);
         CHECK_ALSA_ERROR("pcm prepare error");
-        if (p->prepause_frames)
-            ao_play_silence(ao, p->prepause_frames);
     }
 
+    if (p->prepause_frames)
+        ao_play_silence(ao, p->prepause_frames);
+
 alsa_error: ;
+    p->paused = false;
 }
 
 static void reset(struct ao *ao)
@@ -953,12 +990,18 @@ static void reset(struct ao *ao)
     struct priv *p = ao->priv;
     int err;
 
+    p->paused = false;
     p->prepause_frames = 0;
     p->delay_before_pause = 0;
-    err = snd_pcm_drop(p->alsa);
-    CHECK_ALSA_ERROR("pcm prepare error");
-    err = snd_pcm_prepare(p->alsa);
-    CHECK_ALSA_ERROR("pcm prepare error");
+
+    if (ao->stream_silence) {
+        soft_reset(ao);
+    } else {
+        err = snd_pcm_drop(p->alsa);
+        CHECK_ALSA_ERROR("pcm prepare error");
+        err = snd_pcm_prepare(p->alsa);
+        CHECK_ALSA_ERROR("pcm prepare error");
+    }
 
 alsa_error: ;
 }
@@ -997,6 +1040,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
         }
     } while (res == 0);
 
+    p->paused = false;
+
     return res < 0 ? -1 : res;
 
 alsa_error:
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index b0a5dc0..fbbacde 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -419,7 +419,7 @@ const struct ao_driver audio_out_coreaudio = {
     .uninit         = uninit,
     .init           = init,
     .control        = control,
-    .pause          = stop,
+    .reset          = stop,
     .resume         = start,
     .hotplug_init   = hotplug_init,
     .hotplug_uninit = hotplug_uninit,
diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c
index 510fa3a..49f921c 100644
--- a/audio/out/ao_coreaudio_exclusive.c
+++ b/audio/out/ao_coreaudio_exclusive.c
@@ -399,7 +399,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
     .name      = "coreaudio_exclusive",
     .uninit    = uninit,
     .init      = init,
-    .pause     = audio_pause,
+    .reset     = audio_pause,
     .resume    = audio_resume,
     .list_devs = ca_get_device_list,
     .priv_size = sizeof(struct priv),
diff --git a/audio/out/ao_coreaudio_properties.c b/audio/out/ao_coreaudio_properties.c
index b74cf07..a66c9dc 100644
--- a/audio/out/ao_coreaudio_properties.c
+++ b/audio/out/ao_coreaudio_properties.c
@@ -70,7 +70,7 @@ OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector,
 
     return err;
 coreaudio_error_free:
-    free(*data);
+    talloc_free(*data);
 coreaudio_error:
     return err;
 }
diff --git a/audio/out/ao_coreaudio_utils.c b/audio/out/ao_coreaudio_utils.c
index 0bcc0d6..76f5402 100644
--- a/audio/out/ao_coreaudio_utils.c
+++ b/audio/out/ao_coreaudio_utils.c
@@ -147,7 +147,7 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
 {
     if (code == noErr) return true;
 
-    mp_msg(ao->log, level, "%s (%s)\n", message, mp_tag_str(code));
+    mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code);
 
     return false;
 }
diff --git a/audio/out/ao_lavc.c b/audio/out/ao_lavc.c
index 6b4279c..8ae1317 100644
--- a/audio/out/ao_lavc.c
+++ b/audio/out/ao_lavc.c
@@ -123,7 +123,7 @@ static int init(struct ao *ao)
 
     struct mp_chmap_sel sel = {0};
     mp_chmap_sel_add_any(&sel);
-    if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
+    if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false))
         goto fail;
     mp_chmap_reorder_to_lavc(&ao->channels);
     ac->codec->channels = ao->channels.num;
diff --git a/audio/out/ao_null.c b/audio/out/ao_null.c
index 7d45795..ac3f834 100644
--- a/audio/out/ao_null.c
+++ b/audio/out/ao_null.c
@@ -57,7 +57,7 @@ struct priv {
     // (This is not needed by the AO API, but many AOs behave this way.)
     int outburst;       // samples
 
-    char **channel_layouts;
+    struct m_channels channel_layouts;
 };
 
 static void drain(struct ao *ao)
@@ -91,15 +91,9 @@ static int init(struct ao *ao)
     ao->untimed = priv->untimed;
 
     struct mp_chmap_sel sel = {.tmp = ao};
-    if (priv->channel_layouts) {
-        for (int n = 0; priv->channel_layouts[n]; n++) {
-            struct mp_chmap map = {0};
-            if (!mp_chmap_from_str(&map, bstr0(priv->channel_layouts[n]))) {
-                MP_FATAL(ao, "Invalid channel map in option.\n");
-                return -1;
-            }
-            mp_chmap_sel_add_map(&sel, &map);
-        }
+    if (priv->channel_layouts.num_chmaps) {
+        for (int n = 0; n < priv->channel_layouts.num_chmaps; n++)
+            mp_chmap_sel_add_map(&sel, &priv->channel_layouts.chmaps[n]);
     } else {
         mp_chmap_sel_add_any(&sel);
     }
@@ -244,7 +238,7 @@ const struct ao_driver audio_out_null = {
         OPT_FLOATRANGE("latency", latency_sec, 0, 0, 100),
         OPT_FLAG("broken-eof", broken_eof, 0),
         OPT_FLAG("broken-delay", broken_delay, 0),
-        OPT_STRINGLIST("channel-layouts", channel_layouts, 0),
+        OPT_CHANNELS("channel-layouts", channel_layouts, 0),
         {0}
     },
 };
diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c
index d553b67..fc0dd0d 100644
--- a/audio/out/ao_pulse.c
+++ b/audio/out/ao_pulse.c
@@ -23,6 +23,7 @@
 #include <stdbool.h>
 #include <string.h>
 #include <stdint.h>
+#include <math.h>
 #include <pthread.h>
 
 #include <pulse/pulseaudio.h>
@@ -34,8 +35,8 @@
 #include "ao.h"
 #include "internal.h"
 
-#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_NORM)
-#define VOL_MP2PA(v) ((v) * PA_VOLUME_NORM / 100)
+#define VOL_PA2MP(v) ((v) * 100.0 / PA_VOLUME_NORM)
+#define VOL_MP2PA(v) lrint((v) * PA_VOLUME_NORM / 100)
 
 struct priv {
     // PulseAudio playback stream object
diff --git a/audio/out/ao_wasapi_utils.c b/audio/out/ao_wasapi_utils.c
index 0e7dec8..6218153 100644
--- a/audio/out/ao_wasapi_utils.c
+++ b/audio/out/ao_wasapi_utils.c
@@ -407,7 +407,7 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
     }
 
     entry = ao->channels;
-    if (ao_chmap_sel_adjust(ao, &chmap_sel, &entry)){
+    if (ao_chmap_sel_adjust2(ao, &chmap_sel, &entry, !state->opt_exclusive)){
         change_waveformat_channels(wformat, &entry);
         return true;
     }
diff --git a/audio/out/internal.h b/audio/out/internal.h
index 49131ba..518661c 100644
--- a/audio/out/internal.h
+++ b/audio/out/internal.h
@@ -43,6 +43,8 @@ struct ao {
     struct encode_lavc_context *encode_lavc_ctx;
     struct input_ctx *input_ctx;
     struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
+    int init_flags; // AO_INIT_* flags
+    bool stream_silence;        // if audio inactive, just play silence
 
     // The device as selected by the user, usually using ao_device_desc.name
     // from an entry from the list returned by driver->list_devices. If the
@@ -191,6 +193,8 @@ void ao_wakeup_poll(struct ao *ao);
 
 bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
                          struct mp_chmap *map);
+bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
+                          struct mp_chmap *map, bool safe_multichannel);
 bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
                           struct mp_chmap *map, int num);
 
diff --git a/audio/out/pull.c b/audio/out/pull.c
index 8980580..2175a58 100644
--- a/audio/out/pull.c
+++ b/audio/out/pull.c
@@ -185,7 +185,7 @@ static double get_delay(struct ao *ao)
 static void reset(struct ao *ao)
 {
     struct ao_pull_state *p = ao->api_priv;
-    if (ao->driver->reset)
+    if (!ao->stream_silence && ao->driver->reset)
         ao->driver->reset(ao); // assumes the audio callback thread is stopped
     set_state(ao, AO_STATE_NONE);
     for (int n = 0; n < ao->num_planes; n++)
@@ -195,7 +195,7 @@ static void reset(struct ao *ao)
 
 static void pause(struct ao *ao)
 {
-    if (ao->driver->reset)
+    if (!ao->stream_silence && ao->driver->reset)
         ao->driver->reset(ao);
     set_state(ao, AO_STATE_NONE);
 }
@@ -244,6 +244,10 @@ static int init(struct ao *ao)
         p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride);
     atomic_store(&p->state, AO_STATE_NONE);
     assert(ao->driver->resume);
+
+    if (ao->stream_silence)
+        ao->driver->resume(ao);
+
     return 0;
 }
 
diff --git a/audio/out/push.c b/audio/out/push.c
index ac87c62..bf5dde4 100644
--- a/audio/out/push.c
+++ b/audio/out/push.c
@@ -497,10 +497,8 @@ int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
     bool wakeup = false;
     if (p_fds[num_fds].revents & POLLIN) {
         wakeup = true;
-        // flush the wakeup pipe contents - might "drown" some wakeups, but
-        // that's ok for our use-case
-        char buf[100];
-        (void)read(p->wakeup_pipe[0], buf, sizeof(buf));
+        // might "drown" some wakeups, but that's ok for our use-case
+        mp_flush_wakeup_pipe(p->wakeup_pipe[0]);
     }
     return (r >= 0 || r == -EINTR) ? wakeup : -1;
 }
diff --git a/demux/demux.c b/demux/demux.c
index 6038d24..648e629 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -119,16 +119,20 @@ struct demux_internal {
     int max_packs;
     int max_bytes;
 
+    // Set if we know that we are at the start of the file. This is used to
+    // avoid a redundant initial seek after enabling streams. We could just
+    // allow it, but to avoid buggy seeking affecting normal playback, we don't.
+    bool initial_state;
+
     bool tracks_switched;       // thread needs to inform demuxer of this
 
     bool seeking;               // there's a seek queued
     int seek_flags;             // flags for next seek (if seeking==true)
     double seek_pts;
 
-    bool refresh_seeks_enabled;
-    bool start_refresh_seek;
+    double ref_pts;             // assumed player position (only for track switches)
 
-    double ts_offset;           // timestamp offset to apply everything
+    double ts_offset;           // timestamp offset to apply to everything
 
     void (*run_fn)(void *);     // if non-NULL, function queued to be run on
     void *run_fn_arg;           // the thread as run_fn(run_fn_arg)
@@ -152,7 +156,10 @@ struct demux_stream {
                             // if false, this stream is disabled, or passively
                             // read (like subtitles)
     bool eof;               // end of demuxed stream? (true if all buffer empty)
+    bool need_refresh;      // enabled mid-stream
     bool refreshing;
+    bool correct_dts;       // packet DTS is strictly monotonically increasing
+    bool correct_pos;       // packet pos is strictly monotonically increasing
     size_t packs;           // number of packets in buffer
     size_t bytes;           // total bytes of packets in buffer
     double base_ts;         // timestamp of the last packet returned to decoder
@@ -161,12 +168,12 @@ struct demux_stream {
     size_t last_br_bytes;   // summed packet sizes since last bitrate calculation
     double bitrate;
     int64_t last_pos;
+    double last_dts;
     struct demux_packet *head;
     struct demux_packet *tail;
 
     // for closed captions (demuxer_feed_caption)
     struct sh_stream *cc;
-
 };
 
 // Return "a", or if that is NOPTS, return "def".
@@ -199,7 +206,10 @@ static void ds_flush(struct demux_stream *ds)
     ds->eof = false;
     ds->active = false;
     ds->refreshing = false;
+    ds->need_refresh = false;
     ds->last_pos = -1;
+    ds->last_dts = MP_NOPTS_VALUE;
+    ds->correct_dts = ds->correct_pos = true;
 }
 
 void demux_set_ts_offset(struct demuxer *demuxer, double offset)
@@ -222,6 +232,7 @@ struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
         .ff_index = -1,     // may be overwritten by demuxer
         .demuxer_id = -1,   // ... same
         .codec = talloc_zero(sh, struct mp_codec_params),
+        .tags = talloc_zero(sh, struct mp_tags),
     };
     sh->codec->type = type;
     return sh;
@@ -266,6 +277,33 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
     pthread_mutex_unlock(&in->lock);
 }
 
+// Update sh->tags (lazily). This must be called by demuxers which update
+// stream tags after init. (sh->tags can be accessed by the playback thread,
+// which means the demuxer thread cannot write or read it directly.)
+// Before init is finished, sh->tags can still be accessed freely.
+// Ownership of tags goes to the function.
+void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
+                           struct mp_tags *tags)
+{
+    struct demux_internal *in = demuxer->in;
+    assert(demuxer == in->d_thread);
+
+    if (sh->ds) {
+        while (demuxer->num_update_stream_tags <= sh->index) {
+            MP_TARRAY_APPEND(demuxer, demuxer->update_stream_tags,
+                             demuxer->num_update_stream_tags, NULL);
+        }
+        talloc_free(demuxer->update_stream_tags[sh->index]);
+        demuxer->update_stream_tags[sh->index] = talloc_steal(demuxer, tags);
+
+        demux_changed(demuxer, DEMUX_EVENT_METADATA);
+    } else {
+        // not added yet
+        talloc_free(sh->tags);
+        sh->tags = talloc_steal(sh, tags);
+    }
+}
+
 // Return a stream with the given index. Since streams can only be added during
 // the lifetime of the demuxer, it is guaranteed that an index within the valid
 // range [0, demux_get_num_stream()) always returns a valid sh_stream pointer,
@@ -389,6 +427,59 @@ void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp)
     demux_add_packet(sh, dp);
 }
 
+// An obscure mechanism to get stream switching to be executed faster.
+// On a switch, it seeks back, and then grabs all packets that were
+// "missing" from the packet queue of the newly selected stream.
+// Returns MP_NOPTS_VALUE if no seek should happen.
+static double get_refresh_seek_pts(struct demux_internal *in)
+{
+    struct demuxer *demux = in->d_thread;
+
+    double start_ts = in->ref_pts;
+    bool needed = false;
+    bool normal_seek = true;
+    bool refresh_possible = true;
+    for (int n = 0; n < in->num_streams; n++) {
+        struct demux_stream *ds = in->streams[n]->ds;
+
+        if (!ds->selected)
+            continue;
+
+        if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
+            start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
+
+        needed |= ds->need_refresh;
+        // If there were no other streams selected, we can use a normal seek.
+        normal_seek &= ds->need_refresh;
+        ds->need_refresh = false;
+
+        refresh_possible &= ds->correct_dts || ds->correct_pos;
+    }
+
+    if (!needed || start_ts == MP_NOPTS_VALUE || !demux->desc->seek ||
+        !demux->seekable || demux->partially_seekable)
+        return MP_NOPTS_VALUE;
+
+    if (normal_seek)
+        return start_ts;
+
+    if (!refresh_possible) {
+        MP_VERBOSE(in, "can't issue refresh seek\n");
+        return MP_NOPTS_VALUE;
+    }
+
+    for (int n = 0; n < in->num_streams; n++) {
+        struct demux_stream *ds = in->streams[n]->ds;
+        // Streams which didn't have any packets yet will return all packets,
+        // other streams return packets only starting from the last position.
+        if (ds->last_pos != -1 || ds->last_dts != MP_NOPTS_VALUE)
+            ds->refreshing = true;
+    }
+
+    // Seek back to player's current position, with a small offset added.
+    return start_ts - 1.0;
+}
+
 void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
 {
     struct demux_stream *ds = stream ? stream->ds : NULL;
@@ -399,25 +490,34 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
     struct demux_internal *in = ds->in;
     pthread_mutex_lock(&in->lock);
 
-    bool drop = false;
+    bool drop = ds->refreshing;
     if (ds->refreshing) {
         // Resume reading once the old position was reached (i.e. we start
         // returning packets where we left off before the refresh).
-        drop = dp->pos <= ds->last_pos;
-        if (dp->pos >= ds->last_pos)
-            ds->refreshing = false;
+        // If it's the same position, drop, but continue normally next time.
+        if (ds->correct_dts) {
+            ds->refreshing = dp->dts < ds->last_dts;
+        } else if (ds->correct_pos) {
+            ds->refreshing = dp->pos < ds->last_pos;
+        } else {
+            ds->refreshing = false; // should not happen
+        }
     }
 
-    if (!ds->selected || in->seeking || drop) {
+    if (!ds->selected || ds->need_refresh || in->seeking || drop) {
         pthread_mutex_unlock(&in->lock);
         talloc_free(dp);
         return;
     }
 
+    ds->correct_pos &= dp->pos >= 0 && dp->pos > ds->last_pos;
+    ds->correct_dts &= dp->dts != MP_NOPTS_VALUE && dp->dts > ds->last_dts;
+    ds->last_pos = dp->pos;
+    ds->last_dts = dp->dts;
+
     dp->stream = stream->index;
     dp->next = NULL;
 
-    ds->last_pos = dp->pos;
     ds->packs++;
     ds->bytes += dp->len;
     if (ds->tail) {
@@ -505,27 +605,40 @@ static bool read_packet(struct demux_internal *in)
     if (!read_more)
         return false;
 
+    double seek_pts = get_refresh_seek_pts(in);
+
     // Actually read a packet. Drop the lock while doing so, because waiting
     // for disk or network I/O can take time.
     in->idle = false;
+    in->initial_state = false;
     pthread_mutex_unlock(&in->lock);
+
     struct demuxer *demux = in->d_thread;
+
+    if (seek_pts != MP_NOPTS_VALUE) {
+        MP_VERBOSE(in, "refresh seek to %f\n", seek_pts);
+        demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
+    }
+
     bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
     update_cache(in);
+
     pthread_mutex_lock(&in->lock);
 
-    if (eof) {
-        for (int n = 0; n < in->num_streams; n++)
-            in->streams[n]->ds->eof = true;
-        // If we had EOF previously, then don't wakeup (avoids wakeup loop)
-        if (!in->last_eof) {
-            if (in->wakeup_cb)
-                in->wakeup_cb(in->wakeup_cb_ctx);
-            pthread_cond_signal(&in->wakeup);
-            MP_VERBOSE(in, "EOF reached.\n");
+    if (!in->seeking) {
+        if (eof) {
+            for (int n = 0; n < in->num_streams; n++)
+                in->streams[n]->ds->eof = true;
+            // If we had EOF previously, then don't wakeup (avoids wakeup loop)
+            if (!in->last_eof) {
+                if (in->wakeup_cb)
+                    in->wakeup_cb(in->wakeup_cb_ctx);
+                pthread_cond_signal(&in->wakeup);
+                MP_VERBOSE(in, "EOF reached.\n");
+            }
         }
+        in->eof = in->last_eof = eof;
     }
-    in->eof = in->last_eof = eof;
     return true;
 }
 
@@ -550,42 +663,6 @@ static void ds_get_packets(struct demux_stream *ds)
     }
 }
 
-// An obscure mechanism to get stream switching to be executed faster.
-// On a switch, it seeks back, and then grabs all packets that were
-// "missing" from the packet queue of the newly selected stream.
-static void start_refreshing(struct demux_internal *in)
-{
-    struct demuxer *demux = in->d_thread;
-
-    in->start_refresh_seek = false;
-
-    double start_ts = MP_NOPTS_VALUE;
-    for (int n = 0; n < in->num_streams; n++) {
-        struct demux_stream *ds = in->streams[n]->ds;
-        if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
-            start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
-    }
-
-    if (start_ts == MP_NOPTS_VALUE || !demux->desc->seek || !demux->seekable ||
-        demux->partially_seekable || !demux->allow_refresh_seeks)
-        return;
-
-    for (int n = 0; n < in->num_streams; n++) {
-        struct demux_stream *ds = in->streams[n]->ds;
-        // Streams which didn't read any packets yet can return all packets,
-        // or they'd be stuck forever; affects newly selected streams too.
-        if (ds->last_pos != -1)
-            ds->refreshing = true;
-    }
-
-    pthread_mutex_unlock(&in->lock);
-
-    // Seek back to player's current position, with a small offset added.
-    in->d_thread->desc->seek(in->d_thread, start_ts - 1.0, SEEK_BACKWARD | SEEK_HR);
-
-    pthread_mutex_lock(&in->lock);
-}
-
 static void execute_trackswitch(struct demux_internal *in)
 {
     in->tracks_switched = false;
@@ -603,9 +680,6 @@ static void execute_trackswitch(struct demux_internal *in)
                    &(int){any_selected});
 
     pthread_mutex_lock(&in->lock);
-
-    if (in->start_refresh_seek)
-        start_refreshing(in);
 }
 
 static void execute_seek(struct demux_internal *in)
@@ -613,6 +687,7 @@ static void execute_seek(struct demux_internal *in)
     int flags = in->seek_flags;
     double pts = in->seek_pts;
     in->seeking = false;
+    in->initial_state = false;
 
     pthread_mutex_unlock(&in->lock);
 
@@ -853,17 +928,18 @@ static int decode_float(char *str, float *out)
     return 0;
 }
 
-static int decode_gain(demuxer_t *demuxer, const char *tag, float *out)
+static int decode_gain(struct mp_log *log, struct mp_tags *tags,
+                       const char *tag, float *out)
 {
     char *tag_val = NULL;
     float dec_val;
 
-    tag_val = mp_tags_get_str(demuxer->metadata, tag);
+    tag_val = mp_tags_get_str(tags, tag);
     if (!tag_val)
         return -1;
 
-    if (decode_float(tag_val, &dec_val)) {
-        mp_msg(demuxer->log, MSGL_ERR, "Invalid replaygain value\n");
+    if (decode_float(tag_val, &dec_val) < 0) {
+        mp_msg(log, MSGL_ERR, "Invalid replaygain value\n");
         return -1;
     }
 
@@ -871,59 +947,65 @@ static int decode_gain(demuxer_t *demuxer, const char *tag, float *out)
     return 0;
 }
 
-static int decode_peak(demuxer_t *demuxer, const char *tag, float *out)
+static int decode_peak(struct mp_log *log, struct mp_tags *tags,
+                       const char *tag, float *out)
 {
     char *tag_val = NULL;
     float dec_val;
 
     *out = 1.0;
 
-    tag_val = mp_tags_get_str(demuxer->metadata, tag);
+    tag_val = mp_tags_get_str(tags, tag);
     if (!tag_val)
         return 0;
 
-    if (decode_float(tag_val, &dec_val))
-        return 0;
-
-    if (dec_val == 0.0)
-        return 0;
+    if (decode_float(tag_val, &dec_val) < 0 || dec_val <= 0.0)
+        return -1;
 
     *out = dec_val;
     return 0;
 }
 
-static void apply_replaygain(demuxer_t *demuxer, struct replaygain_data *rg)
-{
-    struct demux_internal *in = demuxer->in;
-    for (int n = 0; n < in->num_streams; n++) {
-        struct sh_stream *sh = in->streams[n];
-        if (sh->type == STREAM_AUDIO && !sh->codec->replaygain_data) {
-            MP_VERBOSE(demuxer, "Replaygain: Track=%f/%f Album=%f/%f\n",
-                       rg->track_gain, rg->track_peak,
-                       rg->album_gain, rg->album_peak);
-            sh->codec->replaygain_data = talloc_memdup(in, rg, sizeof(*rg));
-        }
-    }
-}
-
-static void demux_export_replaygain(demuxer_t *demuxer)
+static struct replaygain_data *decode_rgain(struct mp_log *log,
+                                            struct mp_tags *tags)
 {
     struct replaygain_data rg = {0};
 
-    if (!decode_gain(demuxer, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) &&
-        !decode_peak(demuxer, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) &&
-        !decode_gain(demuxer, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) &&
-        !decode_peak(demuxer, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak))
+    if (decode_gain(log, tags, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) >= 0 &&
+        decode_peak(log, tags, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) >= 0)
     {
-        apply_replaygain(demuxer, &rg);
+        if (decode_gain(log, tags, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) < 0 ||
+            decode_peak(log, tags, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak) < 0)
+        {
+            rg.album_gain = rg.track_gain;
+            rg.album_peak = rg.track_peak;
+        }
+        return talloc_memdup(NULL, &rg, sizeof(rg));
     }
 
-    if (!decode_gain(demuxer, "REPLAYGAIN_GAIN", &rg.track_gain) &&
-        !decode_peak(demuxer, "REPLAYGAIN_PEAK", &rg.track_peak))
+    if (decode_gain(log, tags, "REPLAYGAIN_GAIN", &rg.track_gain) >= 0 &&
+        decode_peak(log, tags, "REPLAYGAIN_PEAK", &rg.track_peak) >= 0)
     {
         rg.album_gain = rg.track_gain;
         rg.album_peak = rg.track_peak;
-        apply_replaygain(demuxer, &rg);
+        return talloc_memdup(NULL, &rg, sizeof(rg));
+    }
+
+    return NULL;
+}
+
+static void demux_update_replaygain(demuxer_t *demuxer)
+{
+    struct demux_internal *in = demuxer->in;
+    for (int n = 0; n < in->num_streams; n++) {
+        struct sh_stream *sh = in->streams[n];
+        if (sh->type == STREAM_AUDIO && !sh->codec->replaygain_data) {
+            struct replaygain_data *rg = decode_rgain(demuxer->log, sh->tags);
+            if (!rg)
+                rg = decode_rgain(demuxer->log, demuxer->metadata);
+            if (rg)
+                sh->codec->replaygain_data = talloc_steal(in, rg);
+        }
     }
 }
 
@@ -947,15 +1029,29 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
         dst->partially_seekable = src->partially_seekable;
         dst->filetype = src->filetype;
         dst->ts_resets_possible = src->ts_resets_possible;
-        dst->allow_refresh_seeks = src->allow_refresh_seeks;
         dst->fully_read = src->fully_read;
         dst->start_time = src->start_time;
         dst->priv = src->priv;
     }
+
     if (src->events & DEMUX_EVENT_METADATA) {
         talloc_free(dst->metadata);
         dst->metadata = mp_tags_dup(dst, src->metadata);
+
+        if (dst->num_update_stream_tags != src->num_update_stream_tags) {
+            talloc_free(dst->update_stream_tags);
+            dst->update_stream_tags =
+                talloc_zero_array(dst, struct mp_tags *, dst->num_update_stream_tags);
+            dst->num_update_stream_tags = src->num_update_stream_tags;
+        }
+        for (int n = 0; n < dst->num_update_stream_tags; n++) {
+            talloc_free(dst->update_stream_tags[n]);
+            dst->update_stream_tags[n] =
+                talloc_steal(dst->update_stream_tags, src->update_stream_tags[n]);
+            src->update_stream_tags[n] = NULL;
+        }
     }
+
     dst->events |= src->events;
     src->events = 0;
 }
@@ -977,8 +1073,6 @@ void demux_changed(demuxer_t *demuxer, int events)
 
     if (demuxer->events & DEMUX_EVENT_INIT)
         demuxer_sort_chapters(demuxer);
-    if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
-        demux_export_replaygain(demuxer);
 
     demux_copy(in->d_buffer, demuxer);
 
@@ -1001,8 +1095,28 @@ void demux_update(demuxer_t *demuxer)
     demux_copy(demuxer, in->d_buffer);
     demuxer->events |= in->events;
     in->events = 0;
-    if (in->stream_metadata && (demuxer->events & DEMUX_EVENT_METADATA))
-        mp_tags_merge(demuxer->metadata, in->stream_metadata);
+    if (demuxer->events & DEMUX_EVENT_METADATA) {
+        int num_streams = MPMIN(in->num_streams, demuxer->num_update_stream_tags);
+        for (int n = 0; n < num_streams; n++) {
+            struct mp_tags *tags = demuxer->update_stream_tags[n];
+            demuxer->update_stream_tags[n] = NULL;
+            if (tags) {
+                struct sh_stream *sh = in->streams[n];
+                talloc_free(sh->tags);
+                sh->tags = talloc_steal(sh, tags);
+            }
+        }
+
+        // Often useful audio-only files, which have metadata in the audio track
+        // metadata instead of the main metadata (especially OGG).
+        if (in->num_streams == 1)
+            mp_tags_merge(demuxer->metadata, in->streams[0]->tags);
+
+        if (in->stream_metadata)
+            mp_tags_merge(demuxer->metadata, in->stream_metadata);
+    }
+    if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
+        demux_update_replaygain(demuxer);
     pthread_mutex_unlock(&in->lock);
 }
 
@@ -1074,6 +1188,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
         .min_secs = demuxer->opts->demuxer_min_secs,
         .max_packs = demuxer->opts->demuxer_max_packs,
         .max_bytes = demuxer->opts->demuxer_max_bytes,
+        .initial_state = true,
     };
     pthread_mutex_init(&in->lock, NULL);
     pthread_cond_init(&in->wakeup, NULL);
@@ -1277,18 +1392,6 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
     return 1;
 }
 
-// Enable doing a "refresh seek" on the next stream switch.
-// Note that this by design does not disable ongoing refresh seeks, and
-// does not affect previous stream switch commands (even if they were
-// asynchronous).
-void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled)
-{
-    struct demux_internal *in = demuxer->in;
-    pthread_mutex_lock(&in->lock);
-    in->refresh_seeks_enabled = enabled;
-    pthread_mutex_unlock(&in->lock);
-}
-
 struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
                                                enum stream_type t, int id)
 {
@@ -1301,19 +1404,22 @@ struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
     return NULL;
 }
 
+// Set whether the given stream should return packets.
+// ref_pts is used only if the stream is enabled. Then it serves as approximate
+// start pts for this stream (in the worst case it is ignored).
 void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
-                          bool selected)
+                          double ref_pts, bool selected)
 {
     struct demux_internal *in = demuxer->in;
     pthread_mutex_lock(&in->lock);
     // don't flush buffers if stream is already selected / unselected
     if (stream->ds->selected != selected) {
         stream->ds->selected = selected;
-        stream->ds->active = false;
         ds_flush(stream->ds);
         in->tracks_switched = true;
-        if (selected && in->refresh_seeks_enabled)
-            in->start_refresh_seek = true;
+        stream->ds->need_refresh = selected && !in->initial_state;
+        if (stream->ds->need_refresh)
+            in->ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
         if (in->threading) {
             pthread_cond_signal(&in->wakeup);
         } else {
diff --git a/demux/demux.h b/demux/demux.h
index a26dada..07803d2 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -178,12 +178,6 @@ typedef struct demuxer {
     double start_time;
     // File format allows PTS resets (even if the current file is without)
     bool ts_resets_possible;
-    // Enable fast track switching hacks. This requires from the demuxer:
-    // - seeking is somewhat reliable; packet contents must not change
-    // - packet position (demux_packet.pos) is set, not negative, unique, and
-    //   monotonically increasing
-    // - seeking leaves packet positions invariant
-    bool allow_refresh_seeks;
     // The file data was fully read, and there is no need to keep the stream
     // open, keep the cache active, or to run the demuxer thread. Generating
     // packets is not slow either (unlike e.g. libavdevice pseudo-demuxers).
@@ -216,7 +210,10 @@ typedef struct demuxer {
     struct mp_log *log, *glog;
     struct demuxer_params *params;
 
-    struct demux_internal *in; // internal to demux.c
+    // internal to demux.c
+    struct demux_internal *in;
+    struct mp_tags **update_stream_tags;
+    int num_update_stream_tags;
 
     // Since the demuxer can run in its own thread, and the stream is not
     // thread-safe, only the demuxer is allowed to access the stream directly.
@@ -265,13 +262,12 @@ bool demux_cancel_test(struct demuxer *demuxer);
 
 void demux_flush(struct demuxer *demuxer);
 int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
-void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled);
 void demux_set_ts_offset(struct demuxer *demuxer, double offset);
 
 int demux_control(struct demuxer *demuxer, int cmd, void *arg);
 
 void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
-                          bool selected);
+                          double ref_pts, bool selected);
 void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect);
 
 void demuxer_help(struct mp_log *log);
@@ -280,6 +276,8 @@ int demuxer_add_attachment(struct demuxer *demuxer, char *name,
                            char *type, void *data, size_t data_size);
 int demuxer_add_chapter(demuxer_t *demuxer, char *name,
                         double pts, uint64_t demuxer_id);
+void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
+                           struct mp_tags *tags);
 
 double demuxer_get_time_length(struct demuxer *demuxer);
 
diff --git a/demux/demux_disc.c b/demux/demux_disc.c
index 91b87a4..805ba4c 100644
--- a/demux/demux_disc.c
+++ b/demux/demux_disc.c
@@ -57,7 +57,7 @@ static void reselect_streams(demuxer_t *demuxer)
     for (int n = 0; n < MPMIN(num_slave, p->num_streams); n++) {
         if (p->streams[n]) {
             demuxer_select_track(p->slave, demux_get_stream(p->slave, n),
-                demux_stream_is_selected(p->streams[n]));
+                MP_NOPTS_VALUE, demux_stream_is_selected(p->streams[n]));
         }
     }
 }
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index a6f51dc..e28ebd0 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -168,7 +168,6 @@ typedef struct lavf_priv {
     int num_streams;
     int cur_program;
     char *mime_type;
-    bool merge_track_metadata;
     double seek_delay;
 } lavf_priv_t;
 
@@ -502,7 +501,7 @@ static void select_tracks(struct demuxer *demuxer, int start)
     }
 }
 
-static void export_replaygain(demuxer_t *demuxer, struct mp_codec_params *c,
+static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh,
                               AVStream *st)
 {
     for (int i = 0; i < st->nb_side_data; i++) {
@@ -528,7 +527,10 @@ static void export_replaygain(demuxer_t *demuxer, struct mp_codec_params *c,
         rgain->album_peak = (av_rgain->album_peak != 0.0) ?
             av_rgain->album_peak / 100000.0f : 1.0;
 
-        c->replaygain_data = rgain;
+        // This must be run only before the stream was added, otherwise there
+        // will be race conditions with accesses from the user thread.
+        assert(!sh->ds);
+        sh->codec->replaygain_data = rgain;
     }
 }
 
@@ -575,7 +577,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
             delay = lavc_delay / (double)codec->sample_rate;
         priv->seek_delay = MPMAX(priv->seek_delay, delay);
 
-        export_replaygain(demuxer, sh->codec, st);
+        export_replaygain(demuxer, sh, st);
 
         break;
     }
@@ -681,6 +683,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
         if (!sh->title && sh->hls_bitrate > 0)
             sh->title = talloc_asprintf(sh, "bitrate %d", sh->hls_bitrate);
         sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
+        mp_tags_copy_from_av_dictionary(sh->tags, st->metadata);
         demux_add_sh_stream(demuxer, sh);
     }
 
@@ -703,14 +706,14 @@ static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
         priv->avfc->event_flags = 0;
         demux_changed(demuxer, DEMUX_EVENT_METADATA);
     }
-    if (priv->merge_track_metadata) {
-        for (int n = 0; n < priv->num_streams; n++) {
-            AVStream *st = priv->streams[n] ? priv->avfc->streams[n] : NULL;
-            if (st && st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
-                mp_tags_copy_from_av_dictionary(demuxer->metadata, st->metadata);
-                st->event_flags = 0;
-                demux_changed(demuxer, DEMUX_EVENT_METADATA);
-            }
+
+    for (int n = 0; n < priv->num_streams; n++) {
+        AVStream *st = priv->streams[n] ? priv->avfc->streams[n] : NULL;
+        if (st && st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
+            st->event_flags = 0;
+            struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
+            mp_tags_copy_from_av_dictionary(tags, st->metadata);
+            demux_set_stream_tags(demuxer, priv->streams[n], tags);
         }
     }
 }
@@ -846,16 +849,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
 
     add_new_streams(demuxer);
 
-    // Often useful with OGG audio-only files, which have metadata in the audio
-    // track metadata instead of the main metadata.
-    if (demux_get_num_stream(demuxer) == 1) {
-        priv->merge_track_metadata = true;
-        for (int n = 0; n < priv->num_streams; n++) {
-            if (priv->streams[n])
-                mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->streams[n]->metadata);
-        }
-    }
-
     mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->metadata);
     update_metadata(demuxer, NULL);
 
@@ -865,7 +858,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
     demuxer->start_time = priv->avfc->start_time == AV_NOPTS_VALUE ?
                           0 : (double)priv->avfc->start_time / AV_TIME_BASE;
 
-    demuxer->allow_refresh_seeks = matches_avinputformat_name(priv, "mp4");
     demuxer->fully_read = priv->format_hack.fully_read;
 
     return 0;
@@ -953,30 +945,15 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
         seek_pts_av = seek_pts * AV_TIME_BASE;
     }
 
-    int r;
-    if (!priv->avfc->iformat->read_seek2) {
-        // Normal seeking.
+    int r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
+    if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
+        // When seeking before the beginning of the file, and seeking fails,
+        // try again without the backwards flag to make it seek to the
+        // beginning.
+        avsflags &= ~AVSEEK_FLAG_BACKWARD;
         r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
-        if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
-            // When seeking before the beginning of the file, and seeking fails,
-            // try again without the backwards flag to make it seek to the
-            // beginning.
-            avsflags &= ~AVSEEK_FLAG_BACKWARD;
-            r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
-        }
-    } else {
-        // av_seek_frame() won't work. Use "new" seeking API. We don't use this
-        // API by default, because there are some major issues.
-        // Set max_ts==ts, so that demuxing starts from an earlier position in
-        // the worst case.
-        r = avformat_seek_file(priv->avfc, -1, INT64_MIN,
-                               seek_pts_av, seek_pts_av, avsflags);
-        // Similar issue as in the normal seeking codepath.
-        if (r < 0) {
-            r = avformat_seek_file(priv->avfc, -1, INT64_MIN,
-                                   seek_pts_av, INT64_MAX, avsflags);
-        }
     }
+
     if (r < 0) {
         char buf[180];
         av_strerror(r, buf, sizeof(buf));
diff --git a/demux/demux_libarchive.c b/demux/demux_libarchive.c
index 3f1a655..dcdbe65 100644
--- a/demux/demux_libarchive.c
+++ b/demux/demux_libarchive.c
@@ -65,23 +65,10 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
     char **files = NULL;
     int num_files = 0;
 
-    for (;;) {
-        struct archive_entry *entry;
-        int r = archive_read_next_header(mpa->arch, &entry);
-        if (r == ARCHIVE_EOF)
-            break;
-        if (r < ARCHIVE_OK)
-            MP_ERR(demuxer, "%s\n", archive_error_string(mpa->arch));
-        if (r < ARCHIVE_WARN)
-            break;
-        if (archive_entry_filetype(entry) != AE_IFREG)
-            continue;
-        const char *fn = archive_entry_pathname(entry);
-        // Some archives may have no filenames.
-        if (!fn)
-            fn = talloc_asprintf(mpa, "mpv_unknown#%d\n", num_files);
+    while (mp_archive_next_entry(mpa)) {
         // stream_libarchive.c does the real work
-        char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix, fn);
+        char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix,
+                                  mpa->entry_filename);
         MP_TARRAY_APPEND(mpa, files, num_files, f);
     }
 
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index f454f5f..da15c0f 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -93,6 +93,7 @@ typedef struct mkv_content_encoding {
 
 typedef struct mkv_track {
     int tnum;
+    uint64_t uid;
     char *name;
     struct sh_stream *stream;
 
@@ -595,6 +596,7 @@ static void parse_trackentry(struct demuxer *demuxer,
     } else {
         MP_ERR(demuxer, "Missing track number!\n");
     }
+    track->uid = entry->track_uid;
 
     if (entry->name) {
         track->name = talloc_strdup(track, entry->name);
@@ -986,9 +988,6 @@ static void process_tags(demuxer_t *demuxer)
 
     for (int i = 0; i < tags->n_tag; i++) {
         struct ebml_tag tag = tags->tag[i];
-        if (tag.targets.target_track_uid  || tag.targets.target_attachment_uid)
-            continue;
-
         struct mp_tags *dst = NULL;
 
         if (tag.targets.target_chapter_uid) {
@@ -1009,6 +1008,19 @@ static void process_tags(demuxer_t *demuxer)
                     break;
                 }
             }
+        } else if (tag.targets.target_track_uid) {
+            for (int n = 0; n < mkv_d->num_tracks; n++) {
+                if (mkv_d->tracks[n]->uid ==
+                    tag.targets.target_track_uid)
+                {
+                    struct sh_stream *sh = mkv_d->tracks[n]->stream;
+                    if (sh)
+                        dst = sh->tags;
+                    break;
+                }
+            }
+        } else if (tag.targets.target_attachment_uid) {
+            /* ignore */
         } else {
             dst = demuxer->metadata;
         }
@@ -1924,10 +1936,9 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
 
     MP_VERBOSE(demuxer, "All headers are parsed!\n");
 
-    process_tags(demuxer);
     display_create_tracks(demuxer);
     add_coverart(demuxer);
-    demuxer->allow_refresh_seeks = true;
+    process_tags(demuxer);
 
     probe_first_timestamp(demuxer);
     if (opts->demux_mkv->probe_duration)
diff --git a/demux/demux_raw.c b/demux/demux_raw.c
index 0d7517c..bd8e113 100644
--- a/demux/demux_raw.c
+++ b/demux/demux_raw.c
@@ -36,7 +36,7 @@
 #include "osdep/endian.h"
 
 struct demux_rawaudio_opts {
-    struct mp_chmap channels;
+    struct m_channels channels;
     int samplerate;
     int aformat;
 };
@@ -49,13 +49,13 @@ struct demux_rawaudio_opts {
 #define OPT_BASE_STRUCT struct demux_rawaudio_opts
 const struct m_sub_options demux_rawaudio_conf = {
     .opts = (const m_option_t[]) {
-        OPT_CHMAP("channels", channels, CONF_MIN, .min = 1),
+        OPT_CHANNELS("channels", channels, 0, .min = 1),
         OPT_INTRANGE("rate", samplerate, 0, 1000, 8 * 48000),
         OPT_CHOICE("format", aformat, 0,
                    ({"u8",      PCM(0, 0,  8, 0)},
                     {"s8",      PCM(1, 0,  8, 0)},
                     {"u16le",   PCM(0, 0, 16, 0)}, {"u16be",    PCM(0, 0, 16, 1)},
-                    {"s16le",   PCM(1, 0, 16, 0)}, {"u16be",    PCM(1, 0, 16, 1)},
+                    {"s16le",   PCM(1, 0, 16, 0)}, {"s16be",    PCM(1, 0, 16, 1)},
                     {"u24le",   PCM(0, 0, 24, 0)}, {"u24be",    PCM(0, 0, 24, 1)},
                     {"s24le",   PCM(1, 0, 24, 0)}, {"s24be",    PCM(1, 0, 24, 1)},
                     {"u32le",   PCM(0, 0, 32, 0)}, {"u32be",    PCM(0, 0, 32, 1)},
@@ -75,7 +75,11 @@ const struct m_sub_options demux_rawaudio_conf = {
     .size = sizeof(struct demux_rawaudio_opts),
     .defaults = &(const struct demux_rawaudio_opts){
         // Note that currently, stream_cdda expects exactly these parameters!
-        .channels = MP_CHMAP_INIT_STEREO,
+        .channels = {
+            .set = 1,
+            .chmaps = (struct mp_chmap[]){ MP_CHMAP_INIT_STEREO, },
+            .num_chmaps = 1,
+        },
         .samplerate = 44100,
         .aformat = PCM(1, 0, 16, 0), // s16le
     },
@@ -130,9 +134,14 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
     if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
         return -1;
 
+    if (opts->channels.num_chmaps != 1) {
+        MP_ERR(demuxer, "Invalid channels option given.\n");
+        return -1;
+    }
+
     struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
     struct mp_codec_params *c = sh->codec;
-    c->channels = opts->channels;
+    c->channels = opts->channels.chmaps[0];
     c->force_channels = true;
     c->samplerate = opts->samplerate;
 
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index af3389b..a7024fe 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -84,7 +84,7 @@ static void reselect_streams(struct demuxer *demuxer)
             // This stops demuxer readahead for inactive segments.
             if (!p->current || seg->d != p->current->d)
                 selected = false;
-            demuxer_select_track(seg->d, sh, selected);
+            demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
         }
     }
 }
@@ -197,6 +197,11 @@ static int d_fill_buffer(struct demuxer *demuxer)
     if (pkt->stream < 0)
         goto drop;
 
+    // for refresh seeks, demux.c prefers monotonically increasing packet pos
+    // since the packet pos is meaningless anyway for timeline, use it
+    if (pkt->pos >= 0)
+        pkt->pos |= (seg->index & 0x7FFFULL) << 48;
+
     struct virtual_stream *vs = &p->streams[pkt->stream];
 
     if (pkt->pts != MP_NOPTS_VALUE && pkt->pts >= seg->end) {
@@ -301,6 +306,9 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
     demuxer->metadata = meta->metadata;
     demuxer->attachments = meta->attachments;
     demuxer->num_attachments = meta->num_attachments;
+    demuxer->editions = meta->editions;
+    demuxer->num_editions = meta->num_editions;
+    demuxer->edition = meta->edition;
 
     int num_streams = demux_get_num_stream(meta);
     for (int n = 0; n < num_streams; n++) {
diff --git a/demux/stheader.h b/demux/stheader.h
index 77d0eb1..f9d564c 100644
--- a/demux/stheader.h
+++ b/demux/stheader.h
@@ -46,6 +46,8 @@ struct sh_stream {
     bool forced_track;          // container forced track flag
     int hls_bitrate;
 
+    struct mp_tags *tags;
+
     bool missing_timestamps;
 
     // stream is a picture (such as album art)
diff --git a/etc/input.conf b/etc/input.conf
index 0c29a47..59ee288 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -79,7 +79,7 @@
 #> playlist-next                        # skip to next file
 #ENTER playlist-next                    # skip to next file
 #< playlist-prev                        # skip to previous file
-#O no-osd cycle_values osd-level 3 1    # cycle through OSD mode
+#O no-osd cycle-values osd-level 3 1    # cycle through OSD mode
 #o show-progress
 #P show-progress
 #I show-text "${filename}"              # display filename in osd
diff --git a/etc/mplayer-input.conf b/etc/mplayer-input.conf
index c881777..742913c 100644
--- a/etc/mplayer-input.conf
+++ b/etc/mplayer-input.conf
@@ -24,19 +24,19 @@ SHARP cycle audio           # switch audio streams
 BS set speed 1.0	# reset speed to normal
 q quit
 ESC quit
-ENTER playlist_next force       # skip to next file
+ENTER playlist-next force       # skip to next file
 p cycle pause
-. frame_step            # advance one frame and pause
+. frame-step            # advance one frame and pause
 SPACE cycle pause
 HOME set playlist-pos 0 # not the same as MPlayer
 #END pt_up_step -1
-> playlist_next             # skip to next file
-< playlist_prev             #         previous
+> playlist-next             # skip to next file
+< playlist-prev             #         previous
 #INS alt_src_step 1
 #DEL alt_src_step -1
 o osd
-I show_text "${filename}"     # display filename in osd
-P show_progress
+I show-text "${filename}"     # display filename in osd
+P show-progress
 z add sub-delay -0.1        # subtract 100 ms delay from subs
 x add sub-delay +0.1        # add
 9 add volume -1
@@ -57,8 +57,8 @@ d cycle framedrop
 D cycle deinterlace # toggle deinterlacer (auto-inserted filter)
 r add sub-pos -1            # move subtitles up
 t add sub-pos +1            #                down
-#? sub_step +1		# immediately display next subtitle
-#? sub_step -1		#                     previous
+#? sub-step +1		# immediately display next subtitle
+#? sub-step -1		#                     previous
 #? add sub-scale +0.1	# increase subtitle font size
 #? add sub-scale -0.1	# decrease subtitle font size
 f cycle fullscreen
diff --git a/etc/restore-old-bindings.conf b/etc/restore-old-bindings.conf
index e8499b5..aab0c9f 100644
--- a/etc/restore-old-bindings.conf
+++ b/etc/restore-old-bindings.conf
@@ -17,7 +17,7 @@ d cycle framedrop
 
 # changed in mpv 0.7.0
 
-ENTER playlist_next force
+ENTER playlist-next force
 
 # changed in mpv 0.6.0
 
@@ -38,4 +38,4 @@ TAB cycle program
 A cycle angle
 U stop
 o osd
-I show_text "${filename}"
+I show-text "${filename}"
diff --git a/input/cmd_list.c b/input/cmd_list.c
index b5e29aa..8cc6210 100644
--- a/input/cmd_list.c
+++ b/input/cmd_list.c
@@ -257,7 +257,7 @@ static const struct legacy_cmd legacy_cmds[] = {
     {"saturation",              "add saturation"},
     {"hue",                     "add hue"},
     {"switch_vsync",            "cycle vsync"},
-    {"sub_load",                "sub_add"},
+    {"sub_load",                "sub-add"},
     {"sub_select",              "cycle sub"},
     {"sub_pos",                 "add sub-pos"},
     {"sub_delay",               "add sub-delay"},
diff --git a/input/event.c b/input/event.c
index 188e800..76ddae2 100644
--- a/input/event.c
+++ b/input/event.c
@@ -31,7 +31,7 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files,
         for (int i = 0; i < num_files; i++) {
             const char *cmd[] = {
                 "osd-auto",
-                "sub_add",
+                "sub-add",
                 files[i],
                 NULL
             };
diff --git a/input/ipc-unix.c b/input/ipc-unix.c
index 12d7018..0f4b713 100644
--- a/input/ipc-unix.c
+++ b/input/ipc-unix.c
@@ -133,8 +133,7 @@ static void *client_thread(void *p)
         }
 
         if (fds[0].revents & POLLIN) {
-            char discard[100];
-            (void)read(pipe_fd, discard, sizeof(discard));
+            mp_flush_wakeup_pipe(pipe_fd);
 
             while (1) {
                 mpv_event *event = mpv_wait_event(arg->client, 0);
diff --git a/libmpv/client.h b/libmpv/client.h
index 3f5d759..fb4b29b 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -215,7 +215,7 @@ extern "C" {
  * relational operators (<, >, <=, >=).
  */
 #define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
-#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 21)
+#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 22)
 
 /**
  * Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
@@ -248,7 +248,7 @@ typedef enum mpv_error {
      * making asynchronous requests. (Bugs in the client API implementation
      * could also trigger this, e.g. if events become "lost".)
      */
-    MPV_ERROR_EVENT_QUEUE_FULL = -1,
+    MPV_ERROR_EVENT_QUEUE_FULL  = -1,
     /**
      * Memory allocation failed.
      */
@@ -299,7 +299,7 @@ typedef enum mpv_error {
      */
     MPV_ERROR_COMMAND           = -12,
     /**
-     * Generic error on loading (used with mpv_event_end_file.error).
+     * Generic error on loading (usually used with mpv_event_end_file.error).
      */
     MPV_ERROR_LOADING_FAILED    = -13,
     /**
@@ -329,7 +329,11 @@ typedef enum mpv_error {
     /**
      * The API function which was called is a stub only.
      */
-    MPV_ERROR_NOT_IMPLEMENTED   = -19
+    MPV_ERROR_NOT_IMPLEMENTED   = -19,
+    /**
+     * Unspecified error.
+     */
+    MPV_ERROR_GENERIC           = -20
 } mpv_error;
 
 /**
@@ -660,7 +664,7 @@ typedef enum mpv_format {
     MPV_FORMAT_NODE_MAP         = 8,
     /**
      * A raw, untyped byte array. Only used only with mpv_node, and only in
-     * some very special situations. (Currently, only for the screenshot_raw
+     * some very special situations. (Currently, only for the screenshot-raw
      * command.)
      */
     MPV_FORMAT_BYTE_ARRAY       = 9
@@ -1148,13 +1152,13 @@ typedef enum mpv_event_id {
      * @deprecated This was used internally with the internal "script_dispatch"
      *             command to dispatch keyboard and mouse input for the OSC.
      *             It was never useful in general and has been completely
-     *             replaced with "script_binding".
+     *             replaced with "script-binding".
      *             This event never happens anymore, and is included in this
      *             header only for compatibility.
      */
     MPV_EVENT_SCRIPT_INPUT_DISPATCH = 15,
     /**
-     * Triggered by the script_message input command. The command uses the
+     * Triggered by the script-message input command. The command uses the
      * first argument of the command as client name (see mpv_client_name()) to
      * dispatch the message, and passes along all arguments starting from the
      * second argument as strings.
diff --git a/libmpv/mpv.def b/libmpv/mpv.def
index 840ba1e..0384c7d 100644
--- a/libmpv/mpv.def
+++ b/libmpv/mpv.def
@@ -37,6 +37,7 @@ mpv_set_property
 mpv_set_property_async
 mpv_set_property_string
 mpv_set_wakeup_callback
+mpv_stream_cb_add_ro
 mpv_suspend
 mpv_terminate_destroy
 mpv_unobserve_property
diff --git a/libmpv/stream_cb.h b/libmpv/stream_cb.h
new file mode 100644
index 0000000..a9a9e05
--- /dev/null
+++ b/libmpv/stream_cb.h
@@ -0,0 +1,230 @@
+/* Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Note: the client API is licensed under ISC (see above) to ease
+ * interoperability with other licenses. But keep in mind that the
+ * mpv core is still mostly GPLv2+. It's up to lawyers to decide
+ * whether applications using this API are affected by the GPL.
+ * One argument against this is that proprietary applications
+ * using mplayer in slave mode is apparently tolerated, and this
+ * API is basically equivalent to slave mode.
+ */
+
+#ifndef MPV_CLIENT_API_STREAM_CB_H_
+#define MPV_CLIENT_API_STREAM_CB_H_
+
+#include "client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Warning: this API is not stable yet.
+ *
+ * Overview
+ * --------
+ *
+ * This API can be used to make mpv read from a stream with a custom
+ * implementation. This interface is inspired by funopen on BSD and
+ * fopencookie on linux. The stream is backed by user-defined callbacks
+ * which can implement customized open, read, seek, size and close behaviors.
+ *
+ * Usage
+ * -----
+ *
+ * Register your stream callbacks with the mpv_stream_cb_add_ro() function. You
+ * have to provide a mpv_stream_cb_open_ro_fn callback to it (open_fn argument).
+ *
+ * Once registered, you can `loadfile myprotocol://myfile`. Your open_fn will be
+ * invoked with the URI and you must fill out the provided mpv_stream_cb_info
+ * struct. This includes your stream callbacks (like read_fn), and an opaque
+ * cookie, which will be passed as the first argument to all the remaining
+ * stream callbacks.
+ *
+ * Note that your custom callbacks must not invoke libmpv APIs as that would
+ * cause a deadlock.
+ *
+ * Stream lifetime
+ * ---------------
+ *
+ * A stream remains valid until its close callback has been called. It's up to
+ * libmpv to call the close callback, and the libmpv user cannot close it
+ * directly with the stream_cb API.
+ *
+ * For example, if you consider your custom stream to become suddenly invalid
+ * (maybe because the underlying stream died), libmpv will continue using your
+ * stream. All you can do is returning errors from each callback, until libmpv
+ * gives up and closes it.
+ *
+ * Protocol registration and lifetime
+ * ----------------------------------
+ *
+ * Protocols remain registered until the mpv instance is terminated. This means
+ * in particular that it can outlive the mpv_handle that was used to register
+ * it, but once mpv_terminate_destroy() is called, your registered callbacks
+ * will not be called again.
+ *
+ * Protocol unregistration is finished after the mpv core has been destroyed
+ * (e.g. after mpv_terminate_destroy() has returned).
+ *
+ * If you do not call mpv_terminate_destroy() yourself (e.g. plugin-style code),
+ * you will have to deal with the registration or even streams outliving your
+ * code. Here are some possible ways to do this:
+ * - call mpv_terminate_destroy(), which destroys the core, and will make sure
+ *   all streams are closed once this function returns
+ * - you refcount all resources your stream "cookies" reference, so that it
+ *   doesn't matter if streams live longer than expected
+ * - create "cancellation" semantics: after your protocol has been unregistered,
+ *   notify all your streams that are still opened, and make them drop all
+ *   referenced resources - then return errors from the stream callbacks as
+ *   long as the stream is still opened
+ *
+ */
+
+/**
+ * Read callback used to implement a custom stream. The semantics of the
+ * callback match read(2) in blocking mode. Short reads are allowed (you can
+ * return less bytes than requested, and libmpv will retry reading the rest
+ * with a nother call). If no data can be immediately read, the callback must
+ * block until there is new data. A return of 0 will be interpreted as final
+ * EOF, although libmpv might retry the read, or seek to a different position.
+ *
+ * @param cookie opaque cookie identifying the stream,
+ *               returned from mpv_stream_cb_open_fn
+ * @param buf buffer to read data into
+ * @param size of the buffer
+ * @return number of bytes read into the buffer
+ * @return 0 on EOF
+ * @return -1 on error
+ */
+typedef int64_t (*mpv_stream_cb_read_fn)(void *cookie, char *buf, uint64_t nbytes);
+
+/**
+ * Seek callback used to implement a custom stream.
+ *
+ * Note that mpv will issue a seek to position 0 immediately after opening. This
+ * is used to test whether the stream is seekable (since seekability might
+ * depend on the URI contents, not just the protocol). Return
+ * MPV_ERROR_UNSUPPORTED if seeking is not implemented for this stream. This
+ * seek also servies to establish the fact that streams start at position 0.
+ *
+ * This callback can be NULL, in which it behaves as if always returning
+ * MPV_ERROR_UNSUPPORTED.
+ *
+ * @param cookie opaque cookie identifying the stream,
+ *               returned from mpv_stream_cb_open_fn
+ * @param offset target absolut stream position
+ * @return the resulting offset of the stream
+ *         MPV_ERROR_UNSUPPORTED or MPV_ERROR_GENERIC if the seek failed
+ */
+typedef int64_t (*mpv_stream_cb_seek_fn)(void *cookie, int64_t offset);
+
+/**
+ * Size callback used to implement a custom stream.
+ *
+ * Return MPV_ERROR_UNSUPPORTED if no size is known.
+ *
+ * This callback can be NULL, in which it behaves as if always returning
+ * MPV_ERROR_UNSUPPORTED.
+ *
+ * @param cookie opaque cookie identifying the stream,
+ *               returned from mpv_stream_cb_open_fn
+ * @return the total size in bytes of the stream
+ */
+typedef int64_t (*mpv_stream_cb_size_fn)(void *cookie);
+
+/**
+ * Close callback used to implement a custom stream.
+ *
+ * @param cookie opaque cookie identifying the stream,
+ *               returned from mpv_stream_cb_open_fn
+ */
+typedef void (*mpv_stream_cb_close_fn)(void *cookie);
+
+/**
+ * See mpv_stream_cb_open_ro_fn callback.
+ */
+typedef struct mpv_stream_cb_info {
+    /**
+     * Opaque user-provided value, which will be passed to the other callbacks.
+     * The close callback will be called to release the cookie. It is not
+     * interpreted by mpv. It doesn't even need to be a valid pointer.
+     *
+     * The user sets this in the mpv_stream_cb_open_ro_fn callback.
+     */
+    void *cookie;
+
+    /**
+     * Callbacks set by the user in the mpv_stream_cb_open_ro_fn callback. Some
+     * of them are optional, and can be left unset.
+     *
+     * The following callbacks are mandatory: read_fn, close_fn
+     */
+    mpv_stream_cb_read_fn read_fn;
+    mpv_stream_cb_seek_fn seek_fn;
+    mpv_stream_cb_size_fn size_fn;
+    mpv_stream_cb_close_fn close_fn;
+} mpv_stream_cb_info;
+
+/**
+ * Open callback used to implement a custom read-only (ro) stream. The user
+ * must set the callback fields in the passed info struct. The cookie field
+ * also can be set to store state associated to the stream instance.
+ *
+ * Note that the info struct is valid only for the duration of this callback.
+ * You can't change the callbacks or the pointer to the cookie at a later point.
+ *
+ * Each stream instance created by the open callback can have different
+ * callbacks.
+ *
+ * The close_fn callback will terminate the stream instance. The pointers to
+ * your callbacks and cookie will be discarded, and the callbacks will not be
+ * called again.
+ *
+ * @param user_data opaque user data provided via mpv_stream_cb_add()
+ * @param uri name of the stream to be opened (with protocol prefix)
+ * @param info fields which the user should fill
+ * @return opaque cookie identifing the newly opened stream
+ * @return 0 on success, MPV_ERROR_LOADING_FAILED if the URI cannot be opened.
+ */
+typedef int (*mpv_stream_cb_open_ro_fn)(void *user_data, char *uri,
+                                        mpv_stream_cb_info *info);
+
+/**
+ * Add a custom stream protocol. This will register a protocol handler under
+ * the given protocol prefix, and invoke the given callbacks if an URI with the
+ * matching protocol prefix is opened.
+ *
+ * The "ro" is for read-only - only read-only streams can be registered with
+ * this function.
+ *
+ * The callback remains registered until the mpv core is registered.
+ *
+ * If a custom stream with the same name is already registered, then the
+ * MPV_ERROR_INVALID_PARAMETER error is returned.
+ *
+ * @param protocol protocol prefix, for example "foo" for "foo://" URIs
+ * @param user_data opaque pointer passed into the mpv_stream_cb_open_fn
+ *                  callback.
+ * @return error code
+ */
+int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data,
+                         mpv_stream_cb_open_ro_fn open_fn);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/options/m_option.c b/options/m_option.c
index b7b7663..fd1e677 100644
--- a/options/m_option.c
+++ b/options/m_option.c
@@ -2287,51 +2287,111 @@ const m_option_type_t m_option_type_afmt = {
 
 #include "audio/chmap.h"
 
-static int parse_chmap(struct mp_log *log, const m_option_t *opt,
-                       struct bstr name, struct bstr param, void *dst)
+static int parse_channels(struct mp_log *log, const m_option_t *opt,
+                          struct bstr name, struct bstr param, void *dst)
 {
-    // min>0: at least min channels, min=0: empty ok
-    int min_ch = (opt->flags & M_OPT_MIN) ? opt->min : 1;
-    assert(min_ch >= 0);
+    // see OPT_CHANNELS for semantics.
+    bool limited = opt->min;
+
+    struct m_channels res = {0};
 
     if (bstr_equals0(param, "help")) {
         mp_chmap_print_help(log);
+        if (!limited) {
+            mp_info(log, "\nOther values:\n"
+                         "    auto-safe\n");
+        }
         return M_OPT_EXIT - 1;
     }
 
-    if (param.len == 0 && min_ch >= 1)
-        return M_OPT_MISSING_PARAM;
-
-    struct mp_chmap res = {0};
-    if (!mp_chmap_from_str(&res, param)) {
-        mp_err(log, "Error parsing channel layout: %.*s\n", BSTR_P(param));
-        return M_OPT_INVALID;
+    bool auto_safe = bstr_equals0(param, "auto-safe");
+    if (bstr_equals0(param, "auto") || bstr_equals0(param, "empty") || auto_safe) {
+        if (limited) {
+            mp_err(log, "Disallowed parameter.\n");
+            return M_OPT_INVALID;
+        }
+        param.len = 0;
+        res.set = true;
+        res.auto_safe = auto_safe;
     }
 
-    if (!mp_chmap_is_valid(&res) && !(min_ch == 0 && mp_chmap_is_empty(&res))) {
-        mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(param));
-        return M_OPT_INVALID;
+    while (param.len) {
+        bstr item;
+        if (limited) {
+            item = param;
+            param.len = 0;
+        } else {
+            bstr_split_tok(param, ",", &item, &param);
+        }
+
+        struct mp_chmap map = {0};
+        if (!mp_chmap_from_str(&map, item) || !mp_chmap_is_valid(&map)) {
+            mp_err(log, "Invalid channel layout: %.*s\n", BSTR_P(item));
+            talloc_free(res.chmaps);
+            return M_OPT_INVALID;
+        }
+
+        MP_TARRAY_APPEND(NULL, res.chmaps, res.num_chmaps, map);
+        res.set = true;
     }
 
-    if (dst)
-        *(struct mp_chmap *)dst = res;
+    if (dst) {
+        *(struct m_channels *)dst = res;
+    } else {
+        talloc_free(res.chmaps);
+    }
 
     return 1;
 }
 
-static char *print_chmap(const m_option_t *opt, const void *val)
+static char *print_channels(const m_option_t *opt, const void *val)
 {
-    const struct mp_chmap *chmap = val;
-    return talloc_strdup(NULL, mp_chmap_to_str(chmap));
+    const struct m_channels *ch = val;
+    if (!ch->set)
+        return talloc_strdup(NULL, "");
+    if (ch->auto_safe)
+        return talloc_strdup(NULL, "auto-safe");
+    if (ch->num_chmaps > 0) {
+        char *res = talloc_strdup(NULL, "");
+        for (int n = 0; n < ch->num_chmaps; n++) {
+            if (n > 0)
+                res = talloc_strdup_append(res, ",");
+            res = talloc_strdup_append(res, mp_chmap_to_str(&ch->chmaps[n]));
+        }
+        return res;
+    }
+    return talloc_strdup(NULL, "auto");
 }
 
+static void free_channels(void *src)
+{
+    if (!src)
+        return;
 
-const m_option_type_t m_option_type_chmap = {
+    struct m_channels *ch = src;
+    talloc_free(ch->chmaps);
+    *ch = (struct m_channels){0};
+}
+
+static void copy_channels(const m_option_t *opt, void *dst, const void *src)
+{
+    if (!(dst && src))
+        return;
+
+    struct m_channels *ch = dst;
+    free_channels(dst);
+    *ch = *(struct m_channels *)src;
+    ch->chmaps =
+        talloc_memdup(NULL, ch->chmaps, sizeof(ch->chmaps[0]) * ch->num_chmaps);
+}
+
+const m_option_type_t m_option_type_channels = {
     .name  = "Audio channels or channel map",
-    .size  = sizeof(struct mp_chmap),
-    .parse = parse_chmap,
-    .print = print_chmap,
-    .copy  = copy_opt,
+    .size  = sizeof(struct m_channels),
+    .parse = parse_channels,
+    .print = print_channels,
+    .copy  = copy_channels,
+    .free = free_channels,
 };
 
 static int parse_timestring(struct bstr str, double *time, char endchar)
diff --git a/options/m_option.h b/options/m_option.h
index e77452a..80be447 100644
--- a/options/m_option.h
+++ b/options/m_option.h
@@ -61,7 +61,7 @@ extern const m_option_type_t m_option_type_afmt;
 extern const m_option_type_t m_option_type_color;
 extern const m_option_type_t m_option_type_geometry;
 extern const m_option_type_t m_option_type_size_box;
-extern const m_option_type_t m_option_type_chmap;
+extern const m_option_type_t m_option_type_channels;
 extern const m_option_type_t m_option_type_node;
 
 // Used internally by m_config.c
@@ -98,6 +98,13 @@ struct m_geometry {
 void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
                       int scrw, int scrh, struct m_geometry *gm);
 
+struct m_channels {
+    bool set : 1;
+    bool auto_safe : 1;
+    struct mp_chmap *chmaps;
+    int num_chmaps;
+};
+
 struct m_obj_desc {
     // Name which will be used in the option string
     const char *name;
@@ -218,7 +225,7 @@ union m_option_value {
     struct m_color color;
     struct m_geometry geometry;
     struct m_geometry size_box;
-    struct mp_chmap chmap;
+    struct m_channels channels;
 };
 
 ////////////////////////////////////////////////////////////////////////////
@@ -626,9 +633,10 @@ extern const char m_option_path_separator;
 #define OPT_AUDIOFORMAT(...) \
     OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt)
 
-#define OPT_CHMAP(...) \
-    OPT_GENERAL(struct mp_chmap, __VA_ARGS__, .type = &m_option_type_chmap)
-
+// If .min==1, then passing auto is disallowed, but "" is still accepted, and
+// limit channel list to 1 item.
+#define OPT_CHANNELS(...) \
+    OPT_GENERAL(struct m_channels, __VA_ARGS__, .type = &m_option_type_channels)
 
 #define M_CHOICES(choices)                                              \
     .priv = (void *)&(const struct m_opt_choice_alternatives[]){        \
diff --git a/options/options.c b/options/options.c
index e58eea1..9e3e703 100644
--- a/options/options.c
+++ b/options/options.c
@@ -40,7 +40,6 @@
 #include "video/csputils.h"
 #include "video/hwdec.h"
 #include "sub/osd.h"
-#include "audio/mixer.h"
 #include "audio/filter/af.h"
 #include "audio/decode/dec_audio.h"
 #include "player/core.h"
@@ -85,6 +84,7 @@ const struct m_opt_choice_alternatives mp_hwdec_names[] = {
     {"auto-copy",   HWDEC_AUTO_COPY},
     {"vdpau",       HWDEC_VDPAU},
     {"videotoolbox",HWDEC_VIDEOTOOLBOX},
+    {"videotoolbox-copy",HWDEC_VIDEOTOOLBOX_COPY},
     {"vaapi",       HWDEC_VAAPI},
     {"vaapi-copy",  HWDEC_VAAPI_COPY},
     {"dxva2",       HWDEC_DXVA2},
@@ -279,7 +279,7 @@ const m_option_t mp_opts[] = {
     // force video/audio rate:
     OPT_DOUBLE("fps", force_fps, CONF_MIN, .min = 0),
     OPT_INTRANGE("audio-samplerate", force_srate, 0, 1000, 16*48000),
-    OPT_CHMAP("audio-channels", audio_output_channels, CONF_MIN, .min = 0),
+    OPT_CHANNELS("audio-channels", audio_output_channels, 0),
     OPT_AUDIOFORMAT("audio-format", audio_output_format, 0),
     OPT_FLAG("audio-normalize-downmix", audio_normalize, 0),
     OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE | M_OPT_FIXED,
@@ -288,7 +288,7 @@ const m_option_t mp_opts[] = {
     OPT_FLAG("audio-pitch-correction", pitch_correction, 0),
 
     // set a-v distance
-    OPT_FLOATRANGE("audio-delay", audio_delay, 0, -100.0, 100.0),
+    OPT_FLOAT("audio-delay", audio_delay, 0),
 
 // ------------------------- codec/vfilter options --------------------
 
@@ -342,6 +342,7 @@ const m_option_t mp_opts[] = {
     OPT_PATHLIST("sub-paths", sub_paths, 0),
     OPT_PATHLIST("audio-file-paths", audiofile_paths, 0),
     OPT_STRING_APPEND_LIST("external-file", external_files, M_OPT_FILE),
+    OPT_FLAG("autoload-files", autoload_files, 0),
     OPT_STRING("sub-codepage", sub_cp, 0),
     OPT_FLOAT("sub-delay", sub_delay, 0),
     OPT_FLOAT("sub-fps", sub_fps, 0),
@@ -396,6 +397,8 @@ const m_option_t mp_opts[] = {
     OPT_STRING("audio-device", audio_device, 0),
     OPT_STRING("audio-client-name", audio_client_name, 0),
     OPT_FLAG("audio-fallback-to-null", ao_null_fallback, 0),
+    OPT_FLAG("audio-stream-silence", audio_stream_silence, 0),
+    OPT_FLOATRANGE("audio-wait-open", audio_wait_open, 0, 0, 60),
     OPT_CHOICE("force-window", force_vo, 0,
                ({"no", 0}, {"yes", 1}, {"immediate", 2})),
     OPT_FLAG("taskbar-progress", vo.taskbar_progress, 0),
@@ -423,6 +426,7 @@ const m_option_t mp_opts[] = {
                 {"weak", -1})),
     OPT_DOUBLE("audio-buffer", audio_buffer, M_OPT_MIN | M_OPT_MAX,
                .min = 0, .max = 10),
+    OPT_FLOATRANGE("balance", balance, 0, -1, 1),
 
     OPT_GEOMETRY("geometry", vo.geometry, 0),
     OPT_SIZE_BOX("autofit", vo.autofit, 0),
@@ -760,6 +764,7 @@ const struct MPOpts mp_default_opts = {
     .sync_audio_drop_size = 0.020,
     .load_config = 1,
     .position_resume = 1,
+    .autoload_files = 1,
     .stream_cache = {
         .size = -1,
         .def_size = 75000,
@@ -804,7 +809,6 @@ const struct MPOpts mp_default_opts = {
     .sub_visibility = 1,
     .sub_pos = 100,
     .sub_speed = 1.0,
-    .audio_output_channels = MP_CHMAP_INIT_STEREO,
     .audio_output_format = 0,  // AF_FORMAT_UNKNOWN
     .playback_speed = 1.,
     .pitch_correction = 1,
@@ -827,7 +831,7 @@ const struct MPOpts mp_default_opts = {
     .sub_cp = "auto",
     .screenshot_template = "mpv-shot%n",
 
-    .hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video",
+    .hwdec_codecs = "h264,vc1,wmv3,hevc,mpeg2video,vp9",
     .videotoolbox_format = IMGFMT_NV12,
 
     .index_mode = 1,
diff --git a/options/options.h b/options/options.h
index 3e8474f..4de4a83 100644
--- a/options/options.h
+++ b/options/options.h
@@ -87,9 +87,12 @@ typedef struct MPOpts {
     char *audio_device;
     char *audio_client_name;
     int ao_null_fallback;
+    int audio_stream_silence;
+    float audio_wait_open;
     int force_vo;
     int softvol;
     float softvol_volume;
+    float balance;
     int softvol_mute;
     float softvol_max;
     int gapless_audio;
@@ -225,7 +228,7 @@ typedef struct MPOpts {
     double force_fps;
     int index_mode;
 
-    struct mp_chmap audio_output_channels;
+    struct m_channels audio_output_channels;
     int audio_output_format;
     int audio_normalize;
     int force_srate;
@@ -242,6 +245,7 @@ typedef struct MPOpts {
     char **sub_paths;
     char **audiofile_paths;
     char **external_files;
+    int autoload_files;
     int sub_auto;
     int audiofile_auto;
     int osd_bar_visible;
diff --git a/osdep/atomics.h b/osdep/atomics.h
index c4f3128..bfcaa38 100644
--- a/osdep/atomics.h
+++ b/osdep/atomics.h
@@ -28,15 +28,14 @@
 
 // Emulate the parts of C11 stdatomic.h needed by mpv.
 // Still relies on gcc/clang atomic builtins.
-// The t field is a hack to make the non-atomic fallback macro mess work.
 
-typedef struct { volatile unsigned long v, t;       } atomic_ulong;
-typedef struct { volatile int v, t;                 } atomic_int;
-typedef struct { volatile unsigned int v, t;        } atomic_uint;
-typedef struct { volatile _Bool v, t;               } atomic_bool;
-typedef struct { volatile long long v, t;           } atomic_llong;
-typedef struct { volatile uint_least32_t v, t;      } atomic_uint_least32_t;
-typedef struct { volatile unsigned long long v, t;  } atomic_ullong;
+typedef struct { volatile unsigned long v;      } atomic_ulong;
+typedef struct { volatile int v;                } atomic_int;
+typedef struct { volatile unsigned int v;       } atomic_uint;
+typedef struct { volatile _Bool v;              } atomic_bool;
+typedef struct { volatile long long v;          } atomic_llong;
+typedef struct { volatile uint_least32_t v;     } atomic_uint_least32_t;
+typedef struct { volatile unsigned long long v; } atomic_ullong;
 
 #define ATOMIC_VAR_INIT(x) \
     {.v = (x)}
@@ -82,19 +81,7 @@ typedef struct { volatile unsigned long long v, t;  } atomic_ullong;
        ok_; })
 
 #else
-
-// This is extremely wrong. The build system actually disables code that has
-// a serious dependency on working atomics, so this is barely ok.
-#define atomic_load(p) ((p)->v)
-#define atomic_store(p, val) ((p)->v = (val))
-#define atomic_fetch_op_(a, b, op) \
-    ((a)->t = (a)->v, (a)->v = (a)->v op (b), (a)->t)
-#define atomic_fetch_add(a, b) atomic_fetch_op_(a, b, +)
-#define atomic_fetch_and(a, b) atomic_fetch_op_(a, b, &)
-#define atomic_fetch_or(a, b) atomic_fetch_op_(a, b, |)
-#define atomic_compare_exchange_strong(p, old, new) \
-    ((p)->v == *(old) ? ((p)->v = (new), 1) : (*(old) = (p)->v, 0))
-
+# error "this should have been a configuration error, report a bug please"
 #endif /* no atomics */
 
 #endif /* else HAVE_STDATOMIC */
diff --git a/osdep/io.c b/osdep/io.c
index 5952f21..2688918 100644
--- a/osdep/io.c
+++ b/osdep/io.c
@@ -85,6 +85,14 @@ int mp_make_wakeup_pipe(int pipes[2])
 }
 #endif
 
+void mp_flush_wakeup_pipe(int pipe_end)
+{
+#ifndef __MINGW32__
+    char buf[100];
+    (void)read(pipe_end, buf, sizeof(buf));
+#endif
+}
+
 #ifdef _WIN32
 
 #include <windows.h>
diff --git a/osdep/io.h b/osdep/io.h
index 541e36a..333ed4f 100644
--- a/osdep/io.h
+++ b/osdep/io.h
@@ -47,6 +47,7 @@
 bool mp_set_cloexec(int fd);
 int mp_make_cloexec_pipe(int pipes[2]);
 int mp_make_wakeup_pipe(int pipes[2]);
+void mp_flush_wakeup_pipe(int pipe_end);
 
 #ifdef _WIN32
 #include <wchar.h>
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
index 21d57b4..bf415d9 100644
--- a/osdep/macosx_application.m
+++ b/osdep/macosx_application.m
@@ -161,7 +161,7 @@ static void terminate_cocoa_application(void)
 
 - (void)stopPlaybackAndRememberPosition
 {
-    [self stopMPV:"quit_watch_later"];
+    [self stopMPV:"quit-watch-later"];
 }
 
 - (void)stopMPV:(char *)cmd
diff --git a/osdep/mpv.rc b/osdep/mpv.rc
index 638d020..67c09f4 100644
--- a/osdep/mpv.rc
+++ b/osdep/mpv.rc
@@ -19,13 +19,31 @@
 
 #include <winver.h>
 
-1 VERSIONINFO
-FILEVERSION 2,0,0,0
-PRODUCTVERSION 2,0,0,0
-FILEOS VOS__WINDOWS32
-FILETYPE VFT_APP
-{
-}
+VS_VERSION_INFO VERSIONINFO
+    FILEVERSION 2, 0, 0, 0
+    PRODUCTVERSION 2, 0, 0, 0
+    FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+    FILEFLAGS 0
+    FILEOS VOS__WINDOWS32
+    FILETYPE VFT_APP
+    FILESUBTYPE 0
+    {
+        BLOCK "StringFileInfo" {
+            BLOCK "000004b0" {
+                VALUE "Comments", "mpv is distributed under the terms of the GNU General Public License Version 2 or later."
+                VALUE "CompanyName", "mpv"
+                VALUE "FileDescription", "mpv"
+                VALUE "FileVersion", "2.0.0.0"
+                VALUE "LegalCopyright", "(C) 2000-2016 mpv/mplayer2/MPlayer"
+                VALUE "OriginalFilename", "mpv.exe"
+                VALUE "ProductName", "mpv"
+                VALUE "ProductVersion", "2.0.0.0"
+            }
+        }
+        BLOCK "VarFileInfo" {
+            VALUE "Translation", 0, 1200
+        }
+    }
 
 IDI_ICON1 ICON DISCARDABLE  "etc/mpv-icon.ico"
 
diff --git a/player/audio.c b/player/audio.c
index b61e464..5b52ece 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -31,7 +31,6 @@
 #include "common/common.h"
 #include "osdep/timer.h"
 
-#include "audio/mixer.h"
 #include "audio/audio.h"
 #include "audio/audio_buffer.h"
 #include "audio/decode/dec_audio.h"
@@ -120,6 +119,69 @@ fail:
     mp_notify(mpctx, MP_EVENT_CHANGE_ALL, NULL);
 }
 
+// Called when opts->softvol_volume or opts->softvol_mute were changed.
+void audio_update_volume(struct MPContext *mpctx)
+{
+    struct MPOpts *opts = mpctx->opts;
+    struct ao_chain *ao_c = mpctx->ao_chain;
+    if (!ao_c || ao_c->af->initialized < 1)
+        return;
+
+    float gain = MPMAX(opts->softvol_volume / 100.0, 0);
+    if (opts->softvol_mute == 1)
+        gain = 0.0;
+
+    if (!af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)) {
+        if (gain == 1.0)
+            return;
+        MP_VERBOSE(mpctx, "Inserting volume filter.\n");
+        if (!(af_add(ao_c->af, "volume", "softvol", NULL)
+              && af_control_any_rev(ao_c->af, AF_CONTROL_SET_VOLUME, &gain)))
+            MP_ERR(mpctx, "No volume control available.\n");
+    }
+}
+
+/* NOTE: Currently the balance code is seriously buggy: it always changes
+ * the af_pan mapping between the first two input channels and first two
+ * output channels to particular values. These values make sense for an
+ * af_pan instance that was automatically inserted for balance control
+ * only and is otherwise an identity transform, but if the filter was
+ * there for another reason, then ignoring and overriding the original
+ * values is completely wrong.
+ */
+void audio_update_balance(struct MPContext *mpctx)
+{
+    struct MPOpts *opts = mpctx->opts;
+    struct ao_chain *ao_c = mpctx->ao_chain;
+    if (!ao_c || ao_c->af->initialized < 1)
+        return;
+
+    float val = opts->balance;
+
+    if (af_control_any_rev(ao_c->af, AF_CONTROL_SET_PAN_BALANCE, &val))
+        return;
+
+    if (val == 0)
+        return;
+
+    struct af_instance *af_pan_balance;
+    if (!(af_pan_balance = af_add(ao_c->af, "pan", "autopan", NULL))) {
+        MP_ERR(mpctx, "No balance control available.\n");
+        return;
+    }
+
+    /* make all other channels pass through since by default pan blocks all */
+    for (int i = 2; i < AF_NCH; i++) {
+        float level[AF_NCH] = {0};
+        level[i] = 1.f;
+        af_control_ext_t arg_ext = { .ch = i, .arg = level };
+        af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_LEVEL,
+                                &arg_ext);
+    }
+
+    af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
+}
+
 static int recreate_audio_filters(struct MPContext *mpctx)
 {
     assert(mpctx->ao_chain);
@@ -132,7 +194,11 @@ static int recreate_audio_filters(struct MPContext *mpctx)
     if (afs->initialized < 1 && af_init(afs) < 0)
         goto fail;
 
-    mixer_reinit_audio(mpctx->mixer, afs);
+    if (mpctx->opts->softvol == SOFTVOL_NO)
+        MP_ERR(mpctx, "--softvol=no is not supported anymore.\n");
+
+    audio_update_volume(mpctx);
+    audio_update_balance(mpctx);
 
     mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
 
@@ -202,6 +268,7 @@ void reset_audio_state(struct MPContext *mpctx)
     mpctx->delay = 0;
     mpctx->audio_drop_throttle = 0;
     mpctx->audio_stat_start = 0;
+    mpctx->audio_allow_second_chance_seek = false;
 }
 
 void uninit_audio_out(struct MPContext *mpctx)
@@ -210,7 +277,6 @@ void uninit_audio_out(struct MPContext *mpctx)
         // Note: with gapless_audio, stop_play is not correctly set
         if (mpctx->opts->gapless_audio || mpctx->stop_play == AT_END_OF_FILE)
             ao_drain(mpctx->ao);
-        mixer_uninit_audio(mpctx->mixer);
         ao_uninit(mpctx->ao);
 
         mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
@@ -243,7 +309,6 @@ static void ao_chain_uninit(struct ao_chain *ao_c)
 void uninit_audio_chain(struct MPContext *mpctx)
 {
     if (mpctx->ao_chain) {
-        mixer_uninit_audio(mpctx->mixer);
         ao_chain_uninit(mpctx->ao_chain);
         mpctx->ao_chain = NULL;
 
@@ -289,7 +354,10 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
     } else if (af_fmt_is_pcm(in_format.format)) {
         afs->output.rate = opts->force_srate;
         mp_audio_set_format(&afs->output, opts->audio_output_format);
-        mp_audio_set_channels(&afs->output, &opts->audio_output_channels);
+        if (opts->audio_output_channels.num_chmaps == 1) {
+            mp_audio_set_channels(&afs->output,
+                                  &opts->audio_output_channels.chmaps[0]);
+        }
     }
 
     // filter input format: same as codec's output format:
@@ -304,15 +372,25 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
     }
 
     if (!mpctx->ao) {
+        int ao_flags = 0;
         bool spdif_fallback = af_fmt_is_spdif(afs->output.format) &&
                               ao_c->spdif_passthrough;
-        bool ao_null_fallback = opts->ao_null_fallback && !spdif_fallback;
 
-        mp_chmap_remove_useless_channels(&afs->output.channels,
-                                         &opts->audio_output_channels);
+        if (opts->ao_null_fallback && !spdif_fallback)
+            ao_flags |= AO_INIT_NULL_FALLBACK;
+
+        if (!opts->audio_output_channels.set || opts->audio_output_channels.auto_safe)
+            ao_flags |= AO_INIT_SAFE_MULTICHANNEL_ONLY;
+
+        if (opts->audio_stream_silence)
+            ao_flags |= AO_INIT_STREAM_SILENCE;
+
+        mp_chmap_sel_list(&afs->output.channels, opts->audio_output_channels.chmaps,
+                          opts->audio_output_channels.num_chmaps);
+
         mp_audio_set_channels(&afs->output, &afs->output.channels);
 
-        mpctx->ao = ao_init_best(mpctx->global, ao_null_fallback, mpctx->input,
+        mpctx->ao = ao_init_best(mpctx->global, ao_flags, mpctx->input,
                                  mpctx->encode_lavc_ctx, afs->output.rate,
                                  afs->output.format, afs->output.channels);
         ao_c->ao = mpctx->ao;
@@ -363,6 +441,9 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
                 mp_audio_config_to_str(&fmt));
         MP_VERBOSE(mpctx, "AO: Description: %s\n", ao_get_description(mpctx->ao));
         update_window_title(mpctx, true);
+
+        ao_c->ao_resume_time =
+            opts->audio_wait_open > 0 ? mp_time_sec() + opts->audio_wait_open : 0;
     }
 
     if (recreate_audio_filters(mpctx) < 0)
@@ -596,6 +677,9 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
             sync_pts = mpctx->video_pts - opts->audio_delay;
     } else if (mpctx->hrseek_active) {
         sync_pts = mpctx->hrseek_pts;
+    } else {
+        // If audio-only is enabled mid-stream during playback, sync accordingly.
+        sync_pts = mpctx->playback_pts;
     }
     if (sync_pts == MP_NOPTS_VALUE) {
         mpctx->audio_status = STATUS_FILLING;
@@ -611,6 +695,27 @@ static bool get_sync_samples(struct MPContext *mpctx, int *skip)
     }
     ptsdiff = MPCLAMP(ptsdiff, -3600, 3600);
 
+    // Heuristic: if audio is "too far" ahead, and one of them is a separate
+    // track, allow a refresh seek to the correct position to fix it.
+    if (ptsdiff > 0.2 && mpctx->audio_allow_second_chance_seek && sync_to_video) {
+        struct ao_chain *ao_c = mpctx->ao_chain;
+        if (ao_c && ao_c->track && mpctx->vo_chain && mpctx->vo_chain->track &&
+            ao_c->track->demuxer != mpctx->vo_chain->track->demuxer)
+        {
+            struct track *track = ao_c->track;
+            double pts = mpctx->video_pts;
+            if (pts != MP_NOPTS_VALUE)
+                pts += get_track_seek_offset(mpctx, track);
+            // (disable it first to make it take any effect)
+            demuxer_select_track(track->demuxer, track->stream, pts, false);
+            demuxer_select_track(track->demuxer, track->stream, pts, true);
+            reset_audio_state(mpctx);
+            MP_VERBOSE(mpctx, "retrying audio seek\n");
+            return false;
+        }
+    }
+    mpctx->audio_allow_second_chance_seek = false;
+
     int align = af_format_sample_alignment(out_format.format);
     *skip = (int)(-ptsdiff * play_samplerate) / align * align;
     return true;
@@ -760,6 +865,12 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
         return; // try again next iteration
     }
 
+    if (ao_c->ao_resume_time > mp_time_sec()) {
+        double remaining = ao_c->ao_resume_time - mp_time_sec();
+        mpctx->sleeptime = MPMIN(mpctx->sleeptime, remaining);
+        return;
+    }
+
     if (mpctx->vo_chain && ao_c->pts_reset) {
         MP_VERBOSE(mpctx, "Reset playback due to audio timestamp reset.\n");
         reset_playback_state(mpctx);
diff --git a/player/client.c b/player/client.c
index f5a321b..b34247d 100644
--- a/player/client.c
+++ b/player/client.c
@@ -24,6 +24,7 @@
 #include "common/global.h"
 #include "common/msg.h"
 #include "common/msg_control.h"
+#include "common/global.h"
 #include "input/input.h"
 #include "input/cmd_list.h"
 #include "misc/ctype.h"
@@ -62,9 +63,13 @@ struct mp_client_api {
     pthread_mutex_t lock;
 
     // -- protected by lock
+
     struct mpv_handle **clients;
     int num_clients;
     uint64_t event_masks;   // combined events of all clients, or 0 if unknown
+
+    struct mp_custom_protocol *custom_protocols;
+    int num_custom_protocols;
 };
 
 struct observe_property {
@@ -1592,6 +1597,7 @@ static const char *const err_table[] = {
     [-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format",
     [-MPV_ERROR_UNSUPPORTED] = "not supported",
     [-MPV_ERROR_NOT_IMPLEMENTED] = "operation not implemented",
+    [-MPV_ERROR_GENERIC] = "something happened",
 };
 
 const char *mpv_error_string(int error)
@@ -1722,3 +1728,59 @@ void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api)
     unlock_core(ctx);
     return res;
 }
+
+struct mp_custom_protocol {
+    char *protocol;
+    void *user_data;
+    mpv_stream_cb_open_ro_fn open_fn;
+};
+
+int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data,
+                         mpv_stream_cb_open_ro_fn open_fn)
+{
+    if (!open_fn)
+        return MPV_ERROR_INVALID_PARAMETER;
+
+    struct mp_client_api *clients = ctx->clients;
+    int r = 0;
+    pthread_mutex_lock(&clients->lock);
+    for (int n = 0; n < clients->num_custom_protocols; n++) {
+        struct mp_custom_protocol *proto = &clients->custom_protocols[n];
+        if (strcmp(proto->protocol, protocol) == 0) {
+            r = MPV_ERROR_INVALID_PARAMETER;
+            break;
+        }
+    }
+    if (stream_has_proto(protocol))
+        r = MPV_ERROR_INVALID_PARAMETER;
+    if (r >= 0) {
+        struct mp_custom_protocol proto = {
+            .protocol = talloc_strdup(clients, protocol),
+            .user_data = user_data,
+            .open_fn = open_fn,
+        };
+        MP_TARRAY_APPEND(clients, clients->custom_protocols,
+                         clients->num_custom_protocols, proto);
+    }
+    pthread_mutex_unlock(&clients->lock);
+    return r;
+}
+
+bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol,
+                        void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn)
+{
+    struct mp_client_api *clients = g->client_api;
+    bool found = false;
+    pthread_mutex_lock(&clients->lock);
+    for (int n = 0; n < clients->num_custom_protocols; n++) {
+        struct mp_custom_protocol *proto = &clients->custom_protocols[n];
+        if (strcmp(proto->protocol, protocol) == 0) {
+            *out_user_data = proto->user_data;
+            *out_fn = proto->open_fn;
+            found = true;
+            break;
+        }
+    }
+    pthread_mutex_unlock(&clients->lock);
+    return found;
+}
diff --git a/player/client.h b/player/client.h
index a9d6cbd..e886622 100644
--- a/player/client.h
+++ b/player/client.h
@@ -5,6 +5,7 @@
 #include <stdbool.h>
 
 #include "libmpv/client.h"
+#include "libmpv/stream_cb.h"
 
 struct MPContext;
 struct mpv_handle;
@@ -46,4 +47,7 @@ struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
                                                struct mp_client_api *client_api);
 void kill_video(struct mp_client_api *client_api);
 
+bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol,
+                        void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn);
+
 #endif
diff --git a/player/command.c b/player/command.c
index 3bca64b..ac97e10 100644
--- a/player/command.c
+++ b/player/command.c
@@ -54,7 +54,6 @@
 #include "video/decode/vd.h"
 #include "video/out/vo.h"
 #include "video/csputils.h"
-#include "audio/mixer.h"
 #include "audio/audio_buffer.h"
 #include "audio/out/ao.h"
 #include "audio/filter/af.h"
@@ -220,7 +219,7 @@ static void mp_hook_add(struct MPContext *mpctx, char *client, char *name,
     qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook);
 }
 
-// Call before a seek, in order to allow revert_seek to undo the seek.
+// Call before a seek, in order to allow revert-seek to undo the seek.
 static void mark_seek(struct MPContext *mpctx)
 {
     struct command_ctx *cmd = mpctx->command_ctx;
@@ -347,7 +346,19 @@ static int mp_property_filename(void *ctx, struct m_property *prop,
     if (mp_is_url(bstr0(filename)))
         mp_url_unescape_inplace(filename);
     char *f = (char *)mp_basename(filename);
-    int r = m_property_strdup_ro(action, arg, f[0] ? f : filename);
+    if (!f[0])
+        f = filename;
+    if (action == M_PROPERTY_KEY_ACTION) {
+        struct m_property_action_arg *ka = arg;
+        if (strcmp(ka->key, "no-ext") == 0) {
+            action = ka->action;
+            arg = ka->arg;
+            bstr root;
+            if (mp_splitext(f, &root))
+                f = bstrto0(filename, root);
+        }
+    }
+    int r = m_property_strdup_ro(action, arg, f);
     talloc_free(filename);
     return r;
 }
@@ -1561,7 +1572,8 @@ static int mp_property_mixer_active(void *ctx, struct m_property *prop,
                                     int action, void *arg)
 {
     MPContext *mpctx = ctx;
-    return m_property_flag_ro(action, arg, mixer_audio_initialized(mpctx->mixer));
+    struct ao_chain *ao_c = mpctx->ao_chain;
+    return m_property_flag_ro(action, arg, ao_c && ao_c->af->initialized > 0);
 }
 
 /// Volume (RW)
@@ -1590,7 +1602,7 @@ static int mp_property_volume(void *ctx, struct m_property *prop,
 
     int r = mp_property_generic_option(mpctx, prop, action, arg);
     if (action == M_PROPERTY_SET)
-        mixer_update_volume(mpctx->mixer);
+        audio_update_volume(mpctx);
     return r;
 }
 
@@ -1607,7 +1619,7 @@ static int mp_property_mute(void *ctx, struct m_property *prop,
 
     int r = mp_property_generic_option(mpctx, prop, action, arg);
     if (action == M_PROPERTY_SET)
-        mixer_update_volume(mpctx->mixer);
+        audio_update_volume(mpctx);
     return r;
 }
 
@@ -1635,9 +1647,21 @@ static int mp_property_ao_volume(void *ctx, struct m_property *prop,
         return M_PROPERTY_OK;
     }
     case M_PROPERTY_GET_TYPE:
-        *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLOAT};
+        *(struct m_option *)arg = (struct m_option){
+            .type = CONF_TYPE_FLOAT,
+            .flags = M_OPT_RANGE,
+            .min = 0,
+            .max = 100,
+        };
+        return M_PROPERTY_OK;
+    case M_PROPERTY_PRINT: {
+        ao_control_vol_t vol = {0};
+        if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK)
+            return M_PROPERTY_UNAVAILABLE;
+        *(char **)arg = talloc_asprintf(NULL, "%.f", (vol.left + vol.right) / 2.0f);
         return M_PROPERTY_OK;
     }
+    }
     return M_PROPERTY_NOT_IMPLEMENTED;
 }
 
@@ -1829,23 +1853,10 @@ static int mp_property_balance(void *ctx, struct m_property *prop,
                                int action, void *arg)
 {
     MPContext *mpctx = ctx;
-    float bal;
 
-    switch (action) {
-    case M_PROPERTY_GET:
-        mixer_getbalance(mpctx->mixer, arg);
-        return M_PROPERTY_OK;
-    case M_PROPERTY_GET_TYPE:
-        *(struct m_option *)arg = (struct m_option){
-            .type = CONF_TYPE_FLOAT,
-            .flags = M_OPT_RANGE,
-            .min = -1,
-            .max = 1,
-        };
-        return M_PROPERTY_OK;
-    case M_PROPERTY_PRINT: {
+    if (action == M_PROPERTY_PRINT) {
         char **str = arg;
-        mixer_getbalance(mpctx->mixer, &bal);
+        float bal = mpctx->opts->balance;
         if (bal == 0.f)
             *str = talloc_strdup(NULL, "center");
         else if (bal == -1.f)
@@ -1859,11 +1870,11 @@ static int mp_property_balance(void *ctx, struct m_property *prop,
         }
         return M_PROPERTY_OK;
     }
-    case M_PROPERTY_SET:
-        mixer_setbalance(mpctx->mixer, *(float *)arg);
-        return M_PROPERTY_OK;
-    }
-    return M_PROPERTY_NOT_IMPLEMENTED;
+
+    int r = mp_property_generic_option(mpctx, prop, action, arg);
+    if (action == M_PROPERTY_SET)
+        audio_update_balance(mpctx);
+    return r;
 }
 
 static struct track* track_next(struct MPContext *mpctx, enum stream_type type,
@@ -1999,6 +2010,10 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
     if (track->d_audio)
         decoder_desc = track->d_audio->decoder_desc;
 
+    bool has_rg = track->stream->codec->replaygain_data;
+    struct replaygain_data rg = has_rg ? *track->stream->codec->replaygain_data
+                                       : (struct replaygain_data){0};
+
     struct m_sub_property props[] = {
         {"id",          SUB_PROP_INT(track->user_tid)},
         {"type",        SUB_PROP_STR(stream_type_name(track->type)),
@@ -2032,6 +2047,14 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
         {"demux-samplerate", SUB_PROP_INT(p.samplerate),
                         .unavailable = !p.samplerate},
         {"demux-fps",   SUB_PROP_DOUBLE(p.fps), .unavailable = p.fps <= 0},
+        {"replaygain-track-peak", SUB_PROP_FLOAT(rg.track_peak),
+                        .unavailable = !has_rg},
+        {"replaygain-track-gain", SUB_PROP_FLOAT(rg.track_gain),
+                        .unavailable = !has_rg},
+        {"replaygain-album-peak", SUB_PROP_FLOAT(rg.album_peak),
+                        .unavailable = !has_rg},
+        {"replaygain-album-gain", SUB_PROP_FLOAT(rg.album_gain),
+                        .unavailable = !has_rg},
         {0}
     };
 
@@ -2521,6 +2544,8 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
             SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.color.primaries))},
         {"gamma",
             SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.color.gamma))},
+        {"nom-peak", SUB_PROP_FLOAT(p.color.nom_peak)},
+        {"sig-peak", SUB_PROP_FLOAT(p.color.sig_peak)},
         {"chroma-location",
             SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))},
         {"stereo-in",
@@ -4051,7 +4076,11 @@ static const struct property_osd_display {
     { "volume", "Volume",
       .msg = "Volume: ${?volume:${volume}% ${?mute==yes:(Muted)}}${!volume:${volume}}",
       .osd_progbar = OSD_VOLUME },
+    { "ao-volume", "AO Volume",
+      .msg = "AO Volume: ${?ao-volume:${ao-volume}% ${?ao-mute==yes:(Muted)}}${!ao-volume:${ao-volume}}",
+      .osd_progbar = OSD_VOLUME },
     { "mute", "Mute" },
+    { "ao-mute", "AO Mute" },
     { "audio-delay", "A-V delay" },
     { "audio", "Audio" },
     { "balance", "Balance", .osd_progbar = OSD_BALANCE },
@@ -4367,15 +4396,15 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
 {
     int r = -1;
     if (strcmp(fmt, "bgra") != 0) {
-        MP_ERR(mpctx, "overlay_add: unsupported OSD format '%s'\n", fmt);
+        MP_ERR(mpctx, "overlay-add: unsupported OSD format '%s'\n", fmt);
         goto error;
     }
     if (id < 0 || id >= 64) { // arbitrary upper limit
-        MP_ERR(mpctx, "overlay_add: invalid id %d\n", id);
+        MP_ERR(mpctx, "overlay-add: invalid id %d\n", id);
         goto error;
     }
     if (w <= 0 || h <= 0 || stride < w * 4 || (stride % 4)) {
-        MP_ERR(mpctx, "overlay_add: inconsistent parameters\n");
+        MP_ERR(mpctx, "overlay-add: inconsistent parameters\n");
         goto error;
     }
     struct overlay overlay = {
@@ -4413,7 +4442,7 @@ static int overlay_add(struct MPContext *mpctx, int id, int x, int y,
             p = m;
     }
     if (!p) {
-        MP_ERR(mpctx, "overlay_add: could not open or map '%s'\n", file);
+        MP_ERR(mpctx, "overlay-add: could not open or map '%s'\n", file);
         talloc_free(overlay.source);
         goto error;
     }
diff --git a/player/core.h b/player/core.h
index 8afcfbe..8f90a19 100644
--- a/player/core.h
+++ b/player/core.h
@@ -185,6 +185,7 @@ struct ao_chain {
     struct af_stream *af;
     struct ao *ao;
     struct mp_audio_buffer *ao_buffer;
+    double ao_resume_time;
 
     // 1-element input frame queue.
     struct mp_audio *input_frame;
@@ -287,7 +288,6 @@ typedef struct MPContext {
 
     struct lavfi *lavfi;
 
-    struct mixer *mixer;
     struct ao *ao;
     struct mp_audio *ao_decoder_fmt; // for weak gapless audio check
     struct ao_chain *ao_chain;
@@ -383,6 +383,10 @@ typedef struct MPContext {
         bool immediate; // disable seek delay logic
     } seek;
 
+    // Allow audio to issue a second seek if audio is too far ahead (for non-hr
+    // seeks with external audio tracks).
+    bool audio_allow_second_chance_seek;
+
     /* Heuristic for relative chapter seeks: keep track which chapter
      * the user wanted to go to, even if we aren't exactly within the
      * boundaries of that chapter due to an inaccurate seek. */
@@ -429,6 +433,8 @@ void uninit_audio_out(struct MPContext *mpctx);
 void uninit_audio_chain(struct MPContext *mpctx);
 int init_audio_decoder(struct MPContext *mpctx, struct track *track);
 void reinit_audio_chain_src(struct MPContext *mpctx, struct lavfi_pad *src);
+void audio_update_volume(struct MPContext *mpctx);
+void audio_update_balance(struct MPContext *mpctx);
 
 // configfiles.c
 void mp_parse_cfgfiles(struct MPContext *mpctx);
@@ -485,6 +491,7 @@ int stream_dump(struct MPContext *mpctx, const char *source_filename);
 int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg),
                         void *thread_arg);
 struct mpv_global *create_sub_global(struct MPContext *mpctx);
+double get_track_seek_offset(struct MPContext *mpctx, struct track *track);
 
 // osd.c
 void set_osd_bar(struct MPContext *mpctx, int type,
@@ -558,4 +565,11 @@ int init_video_decoder(struct MPContext *mpctx, struct track *track);
 int get_deinterlacing(struct MPContext *mpctx);
 void set_deinterlacing(struct MPContext *mpctx, bool enable);
 
+// Values of MPOpts.softvol
+enum {
+    SOFTVOL_NO = 0,
+    SOFTVOL_YES = 1,
+    SOFTVOL_AUTO = 2,
+};
+
 #endif /* MPLAYER_MP_CORE_H */
diff --git a/player/loadfile.c b/player/loadfile.c
index 2b88ecf..590839c 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -42,7 +42,6 @@
 #include "common/encode.h"
 #include "input/input.h"
 
-#include "audio/mixer.h"
 #include "audio/audio.h"
 #include "audio/audio_buffer.h"
 #include "audio/decode/dec_audio.h"
@@ -195,23 +194,10 @@ void reselect_demux_stream(struct MPContext *mpctx, struct track *track)
 {
     if (!track->stream)
         return;
-    demuxer_select_track(track->demuxer, track->stream, track->selected);
-    // External files may need an explicit seek to the correct position, if
-    // they were not implicitly advanced during playback.
-    if (track->selected && track->demuxer != mpctx->demuxer) {
-        bool position_ok = false;
-        for (int n = 0; n < demux_get_num_stream(track->demuxer); n++) {
-            struct sh_stream *stream = demux_get_stream(track->demuxer, n);
-            if (stream != track->stream && stream->type != STREAM_SUB)
-                position_ok |= demux_stream_is_selected(stream);
-        }
-        if (!position_ok) {
-            double pts = get_current_time(mpctx);
-            if (pts == MP_NOPTS_VALUE)
-                pts = 0;
-            demux_seek(track->demuxer, pts, 0);
-        }
-    }
+    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);
 }
 
 // Called from the demuxer thread if a new packet is available.
@@ -266,7 +252,7 @@ static struct track *add_stream_track(struct MPContext *mpctx,
     };
     MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track);
 
-    demuxer_select_track(track->demuxer, stream, false);
+    demuxer_select_track(track->demuxer, stream, MP_NOPTS_VALUE, false);
 
     mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
 
@@ -310,6 +296,8 @@ static int match_lang(char **langs, char *lang)
 static bool compare_track(struct track *t1, struct track *t2, char **langs,
                           struct MPOpts *opts)
 {
+    if (!opts->autoload_files && t1->is_external != t2->is_external)
+        return !t1->is_external;
     bool ext1 = t1->is_external && !t1->no_default;
     bool ext2 = t2->is_external && !t2->no_default;
     if (ext1 != ext2)
@@ -368,6 +356,8 @@ struct track *select_default_track(struct MPContext *mpctx, int order,
         pick = NULL;
     if (pick && pick->attached_picture && !mpctx->opts->audio_display)
         pick = NULL;
+    if (pick && !opts->autoload_files && pick->is_external)
+        pick = NULL;
     return pick;
 }
 
@@ -467,9 +457,6 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
         reselect_demux_stream(mpctx, current);
     }
 
-    if (track && track->demuxer == mpctx->demuxer)
-        demux_set_enable_refresh_seeks(mpctx->demuxer, true);
-
     mpctx->current_track[order][type] = track;
 
     if (track) {
@@ -477,8 +464,6 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
         reselect_demux_stream(mpctx, track);
     }
 
-    demux_set_enable_refresh_seeks(mpctx->demuxer, false);
-
     if (type == STREAM_VIDEO && order == 0) {
         reinit_video_chain(mpctx);
     } else if (type == STREAM_AUDIO && order == 0) {
@@ -624,6 +609,8 @@ void autoload_external_files(struct MPContext *mpctx)
 {
     if (mpctx->opts->sub_auto < 0 && mpctx->opts->audiofile_auto < 0)
         return;
+    if (!mpctx->opts->autoload_files)
+        return;
 
     void *tmp = talloc_new(NULL);
     char *base_filename = mpctx->filename;
diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua
index e499ecc..901a8cf 100644
--- a/player/lua/defaults.lua
+++ b/player/lua/defaults.lua
@@ -42,8 +42,8 @@ function mp.input_disable_section(section)
     mp.commandv("disable-section", section)
 end
 
--- For dispatching script_binding. This is sent as:
---      script_message_to $script_name $binding_name $keystate
+-- For dispatching script-binding. This is sent as:
+--      script-message-to $script_name $binding_name $keystate
 -- The array is indexed by $binding_name, and has functions like this as value:
 --      fn($binding_name, $keystate)
 local dispatch_key_bindings = {}
@@ -412,7 +412,7 @@ mp.register_event("shutdown", function() mp.keep_running = false end)
 mp.register_event("client-message", message_dispatch)
 mp.register_event("property-change", property_change)
 
--- sent by "script_binding"
+-- sent by "script-binding"
 mp.register_script_message("key-binding", dispatch_key_binding)
 
 mp.msg = {
diff --git a/player/lua/osc.lua b/player/lua/osc.lua
index 23f7cc6..09142e0 100644
--- a/player/lua/osc.lua
+++ b/player/lua/osc.lua
@@ -1383,7 +1383,7 @@ function osc_init()
     ne.content = "\238\132\144"
     ne.visible = have_pl
     ne.eventresponder["mouse_btn0_up"] =
-        function () mp.commandv("playlist_prev", "weak") end
+        function () mp.commandv("playlist-prev", "weak") end
     ne.eventresponder["shift+mouse_btn0_up"] =
         function () show_message(mp.get_property_osd("playlist"), 3) end
 
@@ -1393,7 +1393,7 @@ function osc_init()
     ne.content = "\238\132\129"
     ne.visible = have_pl
     ne.eventresponder["mouse_btn0_up"] =
-        function () mp.commandv("playlist_next", "weak") end
+        function () mp.commandv("playlist-next", "weak") end
     ne.eventresponder["shift+mouse_btn0_up"] =
         function () show_message(mp.get_property_osd("playlist"), 3) end
 
@@ -1421,7 +1421,7 @@ function osc_init()
     ne.eventresponder["mouse_btn0_down"] =
         function () mp.commandv("seek", -5, "relative", "keyframes") end
     ne.eventresponder["shift+mouse_btn0_down"] =
-        function () mp.commandv("frame_back_step") end
+        function () mp.commandv("frame-back-step") end
     ne.eventresponder["mouse_btn2_down"] =
         function () mp.commandv("seek", -30, "relative", "keyframes") end
 
@@ -1433,7 +1433,7 @@ function osc_init()
     ne.eventresponder["mouse_btn0_down"] =
         function () mp.commandv("seek", 10, "relative", "keyframes") end
     ne.eventresponder["shift+mouse_btn0_down"] =
-        function () mp.commandv("frame_step") end
+        function () mp.commandv("frame-step") end
     ne.eventresponder["mouse_btn2_down"] =
         function () mp.commandv("seek", 60, "relative", "keyframes") end
 
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index 9f94951..a637d4e 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -222,6 +222,10 @@ mp.add_hook("on_load", 10, function ()
 
             -- DASH?
             if not (json["requested_formats"] == nil) then
+                if (json["requested_formats"][1].protocol == "http_dash_segments") then
+                    msg.error("MPEG-Dash Segments unsupported, add [protocol!=http_dash_segments] to your ytdl-format.")
+                    return
+                end
 
                 -- video url
                 streamurl = json["requested_formats"][1].url
diff --git a/player/main.c b/player/main.c
index 88b60e1..67dde34 100644
--- a/player/main.c
+++ b/player/main.c
@@ -52,7 +52,6 @@
 
 #include "audio/decode/dec_audio.h"
 #include "audio/out/ao.h"
-#include "audio/mixer.h"
 #include "demux/demux.h"
 #include "stream/stream.h"
 #include "sub/osd.h"
@@ -360,7 +359,6 @@ struct MPContext *mp_create(void)
 
     mpctx->input = mp_input_init(mpctx->global);
     screenshot_init(mpctx);
-    mpctx->mixer = mixer_init(mpctx, mpctx->global);
     command_init(mpctx);
     init_libav(mpctx->global);
     mp_clients_init(mpctx);
@@ -453,11 +451,6 @@ int mp_initialize(struct MPContext *mpctx, char **options)
             return -1;
         }
         m_config_set_profile(mpctx->mconfig, "encoding", 0);
-        // never use auto
-        if (!opts->audio_output_channels.num) {
-            m_config_set_option_ext(mpctx->mconfig, bstr0("audio-channels"),
-                                    bstr0("stereo"), M_SETOPT_PRESERVE_CMDLINE);
-        }
         mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE);
     }
 #endif
diff --git a/player/misc.c b/player/misc.c
index 941c493..a9174c4 100644
--- a/player/misc.c
+++ b/player/misc.c
@@ -93,6 +93,18 @@ double get_play_end_pts(struct MPContext *mpctx)
     return end;
 }
 
+double get_track_seek_offset(struct MPContext *mpctx, struct track *track)
+{
+    struct MPOpts *opts = mpctx->opts;
+    if (track->selected) {
+        if (track->type == STREAM_AUDIO)
+            return -opts->audio_delay;
+        if (track->type == STREAM_SUB)
+            return -opts->sub_delay;
+    }
+    return 0;
+}
+
 float mp_get_cache_percent(struct MPContext *mpctx)
 {
     struct stream_cache_info info = {0};
diff --git a/player/osd.c b/player/osd.c
index aa4d724..e260682 100644
--- a/player/osd.c
+++ b/player/osd.c
@@ -132,7 +132,7 @@ static void term_osd_set_status(struct MPContext *mpctx, const char *text)
 
     int w = 80, h = 24;
     terminal_get_size(&w, &h);
-    if (strlen(mpctx->term_osd_status) > w)
+    if (strlen(mpctx->term_osd_status) > w && !strchr(mpctx->term_osd_status, '\n'))
         mpctx->term_osd_status[w] = '\0';
 
     term_osd_update(mpctx);
diff --git a/player/playloop.c b/player/playloop.c
index 0062a30..9b15ac3 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -36,7 +36,6 @@
 #include "osdep/terminal.h"
 #include "osdep/timer.h"
 
-#include "audio/mixer.h"
 #include "audio/decode/dec_audio.h"
 #include "audio/filter/af.h"
 #include "audio/out/ao.h"
@@ -248,6 +247,12 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
         // The value is arbitrary, but should be "good enough" in most situations.
         if (hr_seek_very_exact)
             hr_seek_offset = MPMAX(hr_seek_offset, 0.5); // arbitrary
+        for (int n = 0; n < mpctx->num_tracks; n++) {
+            double offset = 0;
+            if (!mpctx->tracks[n]->is_external)
+                offset += get_track_seek_offset(mpctx, mpctx->tracks[n]);
+            hr_seek_offset = MPMAX(hr_seek_offset, -offset);
+        }
         demux_pts -= hr_seek_offset;
         demux_flags = (demux_flags | SEEK_HR | SEEK_BACKWARD) & ~SEEK_FORWARD;
     }
@@ -259,6 +264,8 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
         struct track *track = mpctx->tracks[t];
         if (track->selected && track->is_external && track->demuxer) {
             double main_new_pos = demux_pts;
+            if (!hr_seek || track->is_external)
+                main_new_pos += get_track_seek_offset(mpctx, track);
             if (demux_flags & SEEK_FACTOR)
                 main_new_pos = seek_pts;
             demux_seek(track->demuxer, main_new_pos, 0);
@@ -291,6 +298,9 @@ static void mp_seek(MPContext *mpctx, struct seek_params seek)
 
     mp_notify(mpctx, MPV_EVENT_SEEK, NULL);
     mp_notify(mpctx, MPV_EVENT_TICK, NULL);
+
+    mpctx->audio_allow_second_chance_seek =
+        !hr_seek && !(demux_flags & SEEK_FORWARD);
 }
 
 // This combines consecutive seek requests.
@@ -882,6 +892,7 @@ static void handle_playback_restart(struct MPContext *mpctx)
     if (!mpctx->restart_complete) {
         mpctx->hrseek_active = false;
         mpctx->restart_complete = true;
+        mpctx->audio_allow_second_chance_seek = false;
         mp_notify(mpctx, MPV_EVENT_PLAYBACK_RESTART, NULL);
         if (!mpctx->playing_msg_shown) {
             if (opts->playing_msg && opts->playing_msg[0]) {
diff --git a/player/video.c b/player/video.c
index 59ce72f..9790f34 100644
--- a/player/video.c
+++ b/player/video.c
@@ -214,7 +214,8 @@ static void filter_reconfig(struct MPContext *mpctx, struct vo_chain *vo_c)
     }
 
     // Make sure to reset this even if runtime deint switching is used.
-    video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0});
+    if (mpctx->opts->deinterlace >= 0)
+        video_vf_vo_control(vo_c, VFCTRL_SET_DEINTERLACE, &(int){0});
 
     if (params.rotate && (params.rotate % 90 == 0)) {
         if (!(vo_c->vo->driver->caps & VO_CAP_ROTATE90)) {
@@ -1022,8 +1023,11 @@ static double find_best_speed(struct MPContext *mpctx, double vsync)
 
 static bool using_spdif_passthrough(struct MPContext *mpctx)
 {
-    if (mpctx->ao_chain)
-        return !af_fmt_is_pcm(mpctx->ao_chain->input_format.format);
+    if (mpctx->ao_chain && mpctx->ao_chain->ao) {
+        struct mp_audio out_format = {0};
+        ao_get_format(mpctx->ao_chain->ao, &out_format);
+        return !af_fmt_is_pcm(out_format.format);
+    }
     return false;
 }
 
@@ -1205,6 +1209,12 @@ static void handle_display_sync_frame(struct MPContext *mpctx,
     // Likewise, we know sync is off, but is going to be compensated.
     time_left += drop_repeat * vsync;
 
+    // If syncing took too long, disregard timing of the first frame.
+    if (mpctx->num_past_frames == 2 && time_left < 0) {
+        vo_discard_timing_info(vo);
+        time_left = 0;
+    }
+
     if (drop_repeat) {
         mpctx->mistimed_frames_total += 1;
         MP_STATS(mpctx, "mistimed");
diff --git a/stream/cache.c b/stream/cache.c
index 4765ddb..3aad35d 100644
--- a/stream/cache.c
+++ b/stream/cache.c
@@ -124,6 +124,7 @@ enum {
     CACHE_CTRL_NONE = 0,
     CACHE_CTRL_QUIT = -1,
     CACHE_CTRL_PING = -2,
+    CACHE_CTRL_SEEK = -3,
 
     // we should fill buffer only if space>=FILL_LIMIT
     FILL_LIMIT = 16 * 1024,
@@ -132,7 +133,8 @@ enum {
 // Used by the main thread to wakeup the cache thread, and to wait for the
 // cache thread. The cache mutex has to be locked when calling this function.
 // *retry_time should be set to 0 on the first call.
-static void cache_wakeup_and_wait(struct priv *s, double *retry_time)
+// Return false if the stream has been aborted.
+static bool cache_wakeup_and_wait(struct priv *s, double *retry_time)
 {
     double start = mp_time_sec();
     if (*retry_time >= CACHE_WAIT_TIME) {
@@ -146,6 +148,8 @@ static void cache_wakeup_and_wait(struct priv *s, double *retry_time)
 
     if (*retry_time >= 0)
         *retry_time += mp_time_sec() - start;
+
+    return !mp_cancel_test(s->cache->cancel);
 }
 
 // Runs in the cache thread
@@ -200,12 +204,9 @@ static size_t read_buffer(struct priv *s, unsigned char *dst,
     return read;
 }
 
-// Runs in the cache thread.
-static void cache_fill(struct priv *s)
+static bool cache_update_stream_position(struct priv *s)
 {
     int64_t read = s->read_filepos;
-    bool read_attempted = false;
-    int len = 0;
 
     // drop cache contents only if seeking backward or too much fwd.
     // This is also done for on-disk files, since it loses the backseek cache.
@@ -221,11 +222,23 @@ static void cache_fill(struct priv *s)
     if (stream_tell(s->stream) != s->max_filepos && s->seekable) {
         MP_VERBOSE(s, "Seeking underlying stream: %"PRId64" -> %"PRId64"\n",
                    stream_tell(s->stream), s->max_filepos);
-        stream_seek(s->stream, s->max_filepos);
-        if (stream_tell(s->stream) != s->max_filepos)
-            goto done;
+        if (!stream_seek(s->stream, s->max_filepos))
+            return false;
     }
 
+    return stream_tell(s->stream) == s->max_filepos;
+}
+
+// Runs in the cache thread.
+static void cache_fill(struct priv *s)
+{
+    int64_t read = s->read_filepos;
+    bool read_attempted = false;
+    int len = 0;
+
+    if (!cache_update_stream_position(s))
+        goto done;
+
     if (!s->enable_readahead && s->read_min <= s->max_filepos)
         goto done;
 
@@ -496,6 +509,10 @@ static void *cache_thread(void *arg)
         }
         if (s->control > 0) {
             cache_execute_control(s);
+        } else if (s->control == CACHE_CTRL_SEEK) {
+            s->control_res = cache_update_stream_position(s);
+            s->control = CACHE_CTRL_NONE;
+            pthread_cond_signal(&s->wakeup);
         } else {
             cache_fill(s);
         }
@@ -537,9 +554,8 @@ static int cache_fill_buffer(struct stream *cache, char *buffer, int max_len)
             if (s->eof && s->read_filepos >= s->max_filepos && s->reads >= retry)
                 break;
             s->idle = false;
-            if (mp_cancel_test(s->cache->cancel))
+            if (!cache_wakeup_and_wait(s, &retry_time))
                 break;
-            cache_wakeup_and_wait(s, &retry_time);
         }
     }
 
@@ -570,6 +586,14 @@ static int cache_seek(stream_t *cache, int64_t pos)
     } else {
         cache->pos = s->read_filepos = s->read_min = pos;
         s->eof = false; // so that cache_read() will actually wait for new data
+        s->control = CACHE_CTRL_SEEK;
+        s->control_res = 0;
+        double retry = 0;
+        while (s->control != CACHE_CTRL_NONE) {
+            if (!cache_wakeup_and_wait(s, &retry))
+                break;
+        }
+        r = s->control_res;
         pthread_cond_signal(&s->wakeup);
     }
 
@@ -597,12 +621,11 @@ static int cache_control(stream_t *cache, int cmd, void *arg)
     s->control_arg = arg;
     double retry = 0;
     while (s->control != CACHE_CTRL_NONE) {
-        if (mp_cancel_test(s->cache->cancel)) {
+        if (!cache_wakeup_and_wait(s, &retry)) {
             s->eof = 1;
             r = STREAM_UNSUPPORTED;
             goto done;
         }
-        cache_wakeup_and_wait(s, &retry);
     }
     r = s->control_res;
     if (s->control_flush) {
diff --git a/stream/stream.c b/stream/stream.c
index 846765f..3ecdfb0 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -75,6 +75,7 @@ extern const stream_info_t stream_info_bdnav;
 extern const stream_info_t stream_info_rar;
 extern const stream_info_t stream_info_edl;
 extern const stream_info_t stream_info_libarchive;
+extern const stream_info_t stream_info_cb;
 
 static const stream_info_t *const stream_list[] = {
 #if HAVE_CDDA
@@ -115,6 +116,7 @@ static const stream_info_t *const stream_list[] = {
     &stream_info_edl,
     &stream_info_rar,
     &stream_info_file,
+    &stream_info_cb,
     NULL
 };
 
@@ -243,6 +245,9 @@ static stream_t *new_stream(void)
 
 static const char *match_proto(const char *url, const char *proto)
 {
+    if (strcmp(proto, "*") == 0)
+        return url;
+
     int l = strlen(proto);
     if (l > 0) {
         if (strncasecmp(url, proto, l) == 0 && strncmp("://", url + l, 3) == 0)
@@ -800,8 +805,10 @@ int stream_enable_cache(stream_t **stream, struct mp_cache_opts *opts)
     if (res <= 0) {
         cache->uncached_stream = NULL; // don't free original stream
         free_stream(cache);
-        if (fcache != orig)
+        if (fcache != orig) {
+            fcache->uncached_stream = NULL;
             free_stream(fcache);
+        }
     } else {
         *stream = cache;
     }
@@ -1111,3 +1118,17 @@ void stream_print_proto_list(struct mp_log *log)
     talloc_free(list);
     mp_info(log, "\nTotal: %d protocols\n", count);
 }
+
+bool stream_has_proto(const char *proto)
+{
+    for (int i = 0; stream_list[i]; i++) {
+        const stream_info_t *stream_info = stream_list[i];
+
+        for (int j = 0; stream_info->protocols && stream_info->protocols[j]; j++) {
+            if (strcmp(stream_info->protocols[j], proto) == 0)
+                return true;
+        }
+    }
+
+    return false;
+}
diff --git a/stream/stream.h b/stream/stream.h
index 21e497e..1112e09 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -299,5 +299,6 @@ void mp_setup_av_network_options(struct AVDictionary **dict,
 
 void stream_print_proto_list(struct mp_log *log);
 char **stream_get_proto_list(void);
+bool stream_has_proto(const char *proto);
 
 #endif /* MPLAYER_STREAM_H */
diff --git a/stream/stream_bluray.c b/stream/stream_bluray.c
index 7a58d88..2a9c864 100644
--- a/stream/stream_bluray.c
+++ b/stream/stream_bluray.c
@@ -425,6 +425,8 @@ static int bluray_stream_open(stream_t *s)
             return STREAM_UNSUPPORTED;
         }
 
+        MP_VERBOSE(s, "List of available titles:\n");
+
         /* parse titles information */
         uint64_t max_duration = 0;
         for (int i = 0; i < b->num_titles; i++) {
@@ -432,6 +434,10 @@ static int bluray_stream_open(stream_t *s)
             if (!ti)
                 continue;
 
+            MP_VERBOSE(s, "idx: %3d duration: %s (playlist: %05d.mpls)\n",
+                       i + 1, mp_format_time(ti->duration / 90000, false),
+                       ti->playlist);
+
             /* try to guess which title may contain the main movie */
             if (ti->duration > max_duration) {
                 max_duration = ti->duration;
diff --git a/stream/stream_cb.c b/stream/stream_cb.c
new file mode 100644
index 0000000..4496e63
--- /dev/null
+++ b/stream/stream_cb.c
@@ -0,0 +1,108 @@
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "osdep/io.h"
+
+#include "common/common.h"
+#include "common/msg.h"
+#include "common/global.h"
+#include "stream.h"
+#include "options/m_option.h"
+#include "options/path.h"
+#include "player/client.h"
+#include "libmpv/stream_cb.h"
+
+struct priv {
+    mpv_stream_cb_info info;
+};
+
+static int fill_buffer(stream_t *s, char *buffer, int max_len)
+{
+    struct priv *p = s->priv;
+    return (int)p->info.read_fn(p->info.cookie, buffer, (size_t)max_len);
+}
+
+static int seek(stream_t *s, int64_t newpos)
+{
+    struct priv *p = s->priv;
+    return (int)p->info.seek_fn(p->info.cookie, newpos) >= 0;
+}
+
+static int control(stream_t *s, int cmd, void *arg)
+{
+    struct priv *p = s->priv;
+    switch (cmd) {
+    case STREAM_CTRL_GET_SIZE: {
+        if (!p->info.size_fn)
+            break;
+        int64_t size = p->info.size_fn(p->info.cookie);
+        if (size >= 0) {
+            *(int64_t *)arg = size;
+            return 1;
+        }
+        break;
+    }
+    }
+    return STREAM_UNSUPPORTED;
+}
+
+static void s_close(stream_t *s)
+{
+    struct priv *p = s->priv;
+    p->info.close_fn(p->info.cookie);
+}
+
+static int open_cb(stream_t *stream)
+{
+    struct priv *p = talloc_ptrtype(stream, p);
+    stream->priv = p;
+
+    bstr bproto = mp_split_proto(bstr0(stream->url), NULL);
+    char *proto = bstrto0(stream, bproto);
+
+    void *user_data;
+    mpv_stream_cb_open_ro_fn open_fn;
+
+    if (!mp_streamcb_lookup(stream->global, proto, &user_data, &open_fn))
+        return STREAM_UNSUPPORTED;
+
+    mpv_stream_cb_info info = {0};
+
+    int r = open_fn(user_data, stream->url, &info);
+    if (r < 0) {
+        if (r != MPV_ERROR_LOADING_FAILED)
+            MP_WARN(stream, "unknown error from user callback\n");
+        return STREAM_ERROR;
+    }
+
+    if (!info.read_fn || !info.close_fn) {
+        MP_FATAL(stream, "required read_fn or close_fn callbacks not set.\n");
+        return STREAM_ERROR;
+    }
+
+    p->info = info;
+
+    if (p->info.seek_fn && p->info.seek_fn(p->info.cookie, 0) >= 0) {
+        stream->seek = seek;
+        stream->seekable = true;
+    }
+    stream->fast_skip = true;
+    stream->fill_buffer = fill_buffer;
+    stream->control = control;
+    stream->read_chunk = 64 * 1024;
+    stream->close = s_close;
+
+    return STREAM_OK;
+}
+
+const stream_info_t stream_info_cb = {
+    .name = "stream_callback",
+    .open = open_cb,
+    .protocols = (const char*const[]){ "*", NULL },
+};
diff --git a/stream/stream_libarchive.c b/stream/stream_libarchive.c
index a164da8..5017702 100644
--- a/stream/stream_libarchive.c
+++ b/stream/stream_libarchive.c
@@ -218,6 +218,7 @@ struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
                                   int flags)
 {
     struct mp_archive *mpa = talloc_zero(NULL, struct mp_archive);
+    mpa->log = log;
     mpa->arch = archive_read_new();
     mpa->primary_src = src;
     if (!mpa->arch)
@@ -265,6 +266,43 @@ err:
     return NULL;
 }
 
+// Iterate entries. The first call establishes the first entry. Returns false
+// if no entry found, otherwise returns true and sets mpa->entry/entry_filename.
+bool mp_archive_next_entry(struct mp_archive *mpa)
+{
+    mpa->entry = NULL;
+    talloc_free(mpa->entry_filename);
+    mpa->entry_filename = NULL;
+
+    for (;;) {
+        struct archive_entry *entry;
+        int r = archive_read_next_header(mpa->arch, &entry);
+        if (r == ARCHIVE_EOF)
+            break;
+        if (r < ARCHIVE_OK)
+            MP_ERR(mpa, "%s\n", archive_error_string(mpa->arch));
+        if (r < ARCHIVE_WARN) {
+            MP_FATAL(mpa, "could not read archive entry\n");
+            break;
+        }
+        if (archive_entry_filetype(entry) != AE_IFREG)
+            continue;
+        // Some archives may have no filenames, or libarchive won't return some.
+        const char *fn = archive_entry_pathname(entry);
+        char buf[64];
+        if (!fn || bstr_validate_utf8(bstr0(fn)) < 0) {
+            snprintf(buf, sizeof(buf), "mpv_unknown#%d", mpa->entry_num);
+            fn = buf;
+        }
+        mpa->entry = entry;
+        mpa->entry_filename = talloc_strdup(mpa, fn);
+        mpa->entry_num += 1;
+        return true;
+    }
+
+    return false;
+}
+
 struct priv {
     struct mp_archive *mpa;
     struct stream *src;
@@ -282,39 +320,18 @@ static int reopen_archive(stream_t *s)
 
     // Follows the same logic as demux_libarchive.c.
     struct mp_archive *mpa = p->mpa;
-    int num_files = 0;
-    for (;;) {
-        struct archive_entry *entry;
-        int r = archive_read_next_header(mpa->arch, &entry);
-        if (r == ARCHIVE_EOF) {
-            MP_ERR(s, "archive entry not found. '%s'\n", p->entry_name);
-            goto error;
-        }
-        if (r < ARCHIVE_OK)
-            MP_ERR(s, "%s\n", archive_error_string(mpa->arch));
-        if (r < ARCHIVE_WARN)
-            goto error;
-        if (archive_entry_filetype(entry) != AE_IFREG)
-            continue;
-        const char *fn = archive_entry_pathname(entry);
-        char buf[64];
-        if (!fn) {
-            snprintf(buf, sizeof(buf), "mpv_unknown#%d\n", num_files);
-            fn = buf;
-        }
-        if (strcmp(p->entry_name, fn) == 0) {
+    while (mp_archive_next_entry(mpa)) {
+        if (strcmp(p->entry_name, mpa->entry_filename) == 0) {
             p->entry_size = -1;
-            if (archive_entry_size_is_set(entry))
-                p->entry_size = archive_entry_size(entry);
+            if (archive_entry_size_is_set(mpa->entry))
+                p->entry_size = archive_entry_size(mpa->entry);
             return STREAM_OK;
         }
-        num_files++;
     }
 
-error:
     mp_archive_free(p->mpa);
     p->mpa = NULL;
-    MP_ERR(s, "could not open archive\n");
+    MP_ERR(s, "archive entry not found. '%s'\n", p->entry_name);
     return STREAM_ERROR;
 }
 
diff --git a/stream/stream_libarchive.h b/stream/stream_libarchive.h
index ebded5b..c15dc1b 100644
--- a/stream/stream_libarchive.h
+++ b/stream/stream_libarchive.h
@@ -1,9 +1,15 @@
 struct mp_log;
 
 struct mp_archive {
+    struct mp_log *log;
     struct archive *arch;
     struct stream *primary_src;
     char buffer[4096];
+
+    // Current entry, as set by mp_archive_next_entry().
+    struct archive_entry *entry;
+    char *entry_filename;
+    int entry_num;
 };
 
 void mp_archive_free(struct mp_archive *mpa);
@@ -11,3 +17,5 @@ void mp_archive_free(struct mp_archive *mpa);
 #define MP_ARCHIVE_FLAG_UNSAFE 1
 struct mp_archive *mp_archive_new(struct mp_log *log, struct stream *src,
                                   int flags);
+
+bool mp_archive_next_entry(struct mp_archive *mpa);
diff --git a/stream/tvi_v4l2.c b/stream/tvi_v4l2.c
index 91c810a..ae651f0 100644
--- a/stream/tvi_v4l2.c
+++ b/stream/tvi_v4l2.c
@@ -1003,10 +1003,15 @@ static int uninit(priv_t *priv)
     set_mute(priv, 1);
 
     /* free memory and close device */
-    free(priv->map);                priv->map = NULL;
+    free(priv->map);
+    priv->map = NULL;
     priv->mapcount = 0;
-    if(priv->video_fd!=-1)v4l2_close(priv->video_fd);        priv->video_fd  = -1;
-    free(priv->video_dev);        priv->video_dev = NULL;
+    if (priv->video_fd != -1) {
+        v4l2_close(priv->video_fd);
+        priv->video_fd = -1;
+    }
+    free(priv->video_dev);
+    priv->video_dev = NULL;
 
     if (priv->video_ringbuffer) {
         for (int n = 0; n < priv->video_buffer_size_current; n++) {
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index 22dc332..39eb032 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -208,7 +208,7 @@ bool sub_read_packets(struct dec_sub *sub, double video_pts)
     while (1) {
         bool read_more = true;
         if (sub->sd->driver->accepts_packet)
-            read_more = sub->sd->driver->accepts_packet(sub->sd);
+            read_more = sub->sd->driver->accepts_packet(sub->sd, video_pts);
 
         if (!read_more)
             break;
diff --git a/sub/sd.h b/sub/sd.h
index c8056d3..178a6d5 100644
--- a/sub/sd.h
+++ b/sub/sd.h
@@ -34,7 +34,7 @@ struct sd_functions {
     void (*select)(struct sd *sd, bool selected);
     void (*uninit)(struct sd *sd);
 
-    bool (*accepts_packet)(struct sd *sd); // implicit default if NULL: true
+    bool (*accepts_packet)(struct sd *sd, double pts); // implicit default if NULL: true
     int (*control)(struct sd *sd, enum sd_ctrl cmd, void *arg);
 
     void (*get_bitmaps)(struct sd *sd, struct mp_osd_res dim, int format,
diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c
index fca4374..9c217a6 100644
--- a/sub/sd_lavc.c
+++ b/sub/sd_lavc.c
@@ -485,11 +485,21 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res d, int format,
     osd_rescale_bitmaps(res, insize[0], insize[1], d, video_par);
 }
 
-static bool accepts_packet(struct sd *sd)
+static bool accepts_packet(struct sd *sd, double min_pts)
 {
     struct sd_lavc_priv *priv = sd->priv;
 
     double pts = priv->current_pts;
+    if (min_pts != MP_NOPTS_VALUE) {
+        // guard against bogus rendering PTS in the future.
+        if (pts == MP_NOPTS_VALUE || min_pts < pts)
+            pts = min_pts;
+        // Heuristic: we assume rendering cannot lag behind more than 1 second
+        // behind decoding.
+        if (pts + 1 < min_pts)
+            pts = min_pts;
+    }
+
     int last_needed = -1;
     for (int n = 0; n < MAX_QUEUE; n++) {
         struct sub *sub = &priv->subs[n];
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index 5962f88..eb63e58 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -126,6 +126,7 @@ const struct m_sub_options vd_lavc_conf = {
 
 extern const struct vd_lavc_hwdec mp_vd_lavc_vdpau;
 extern const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox;
+extern const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox_copy;
 extern const struct vd_lavc_hwdec mp_vd_lavc_vaapi;
 extern const struct vd_lavc_hwdec mp_vd_lavc_vaapi_copy;
 extern const struct vd_lavc_hwdec mp_vd_lavc_dxva2;
@@ -158,6 +159,7 @@ static const struct vd_lavc_hwdec *const hwdec_list[] = {
 #endif
 #if HAVE_VIDEOTOOLBOX_HWACCEL
     &mp_vd_lavc_videotoolbox,
+    &mp_vd_lavc_videotoolbox_copy,
 #endif
 #if HAVE_VAAPI_HWACCEL
     &mp_vd_lavc_vaapi,
diff --git a/video/decode/videotoolbox.c b/video/decode/videotoolbox.c
index c69d5e8..c6f1a47 100644
--- a/video/decode/videotoolbox.c
+++ b/video/decode/videotoolbox.c
@@ -24,14 +24,16 @@
 #include "common/msg.h"
 #include "video/mp_image.h"
 #include "video/decode/lavc.h"
+#include "video/mp_image_pool.h"
 #include "config.h"
 
+struct priv {
+    struct mp_image_pool *sw_pool;
+};
 
-static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
+static int probe_copy(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
                  const char *codec)
 {
-    if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_VIDEOTOOLBOX))
-        return HWDEC_ERR_NO_CTX;
     switch (mp_codec_to_av_codec_id(codec)) {
     case AV_CODEC_ID_H264:
     case AV_CODEC_ID_H263:
@@ -45,8 +47,19 @@ static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
     return 0;
 }
 
+static int probe(struct lavc_ctx *ctx, struct vd_lavc_hwdec *hwdec,
+                 const char *codec)
+{
+    if (!hwdec_devices_load(ctx->hwdec_devs, HWDEC_VIDEOTOOLBOX))
+        return HWDEC_ERR_NO_CTX;
+    return probe_copy(ctx, hwdec, codec);
+}
+
 static int init(struct lavc_ctx *ctx)
 {
+    struct priv *p = talloc_ptrtype(NULL, p);
+    p->sw_pool = talloc_steal(p, mp_image_pool_new(17));
+    ctx->hwdec_priv = p;
     return 0;
 }
 
@@ -82,15 +95,10 @@ static void print_videotoolbox_error(struct mp_log *log, int lev, char *message,
     mp_msg(log, lev, "%s: %d\n", message, error_code);
 }
 
-static int init_decoder(struct lavc_ctx *ctx, int w, int h)
+static int init_decoder_common(struct lavc_ctx *ctx, int w, int h, AVVideotoolboxContext *vtctx)
 {
     av_videotoolbox_default_free(ctx->avctx);
 
-    AVVideotoolboxContext *vtctx = av_videotoolbox_alloc_context();
-
-    struct mp_vt_ctx *vt = hwdec_devices_load(ctx->hwdec_devs, HWDEC_VIDEOTOOLBOX);
-    vtctx->cv_pix_fmt_type = vt->get_vt_fmt(vt);
-
     int err = av_videotoolbox_default_init2(ctx->avctx, vtctx);
     if (err < 0) {
         print_videotoolbox_error(ctx->log, MSGL_ERR, "failed to init videotoolbox decoder", err);
@@ -100,17 +108,99 @@ static int init_decoder(struct lavc_ctx *ctx, int w, int h)
     return 0;
 }
 
+static int init_decoder(struct lavc_ctx *ctx, int w, int h)
+{
+    AVVideotoolboxContext *vtctx = av_videotoolbox_alloc_context();
+    struct mp_vt_ctx *vt = hwdec_devices_load(ctx->hwdec_devs, HWDEC_VIDEOTOOLBOX);
+    vtctx->cv_pix_fmt_type = vt->get_vt_fmt(vt);
+
+    return init_decoder_common(ctx, w, h, vtctx);
+}
+
+static int init_decoder_copy(struct lavc_ctx *ctx, int w, int h)
+{
+    return init_decoder_common(ctx, w, h, NULL);
+}
+
 static void uninit(struct lavc_ctx *ctx)
 {
     if (ctx->avctx)
         av_videotoolbox_default_free(ctx->avctx);
+
+    struct priv *p = ctx->hwdec_priv;
+    if (!p)
+        return;
+
+    talloc_free(p->sw_pool);
+    p->sw_pool = NULL;
+
+    talloc_free(p);
+    ctx->hwdec_priv = NULL;
+}
+
+static int mp_imgfmt_from_cvpixelformat(uint32_t cvpixfmt)
+{
+    switch (cvpixfmt) {
+    case kCVPixelFormatType_420YpCbCr8Planar:               return IMGFMT_420P;
+    case kCVPixelFormatType_422YpCbCr8:                     return IMGFMT_UYVY;
+    case kCVPixelFormatType_32BGRA:                         return IMGFMT_RGB0;
+    case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:   return IMGFMT_NV12;
+    }
+    return 0;
+}
+
+static struct mp_image *copy_image(struct lavc_ctx *ctx, struct mp_image *hw_image)
+{
+    if (hw_image->imgfmt != IMGFMT_VIDEOTOOLBOX)
+        return hw_image;
+
+    struct priv *p = ctx->hwdec_priv;
+    struct mp_image *image = NULL;
+    CVPixelBufferRef pbuf = (CVPixelBufferRef)hw_image->planes[3];
+    CVPixelBufferLockBaseAddress(pbuf, kCVPixelBufferLock_ReadOnly);
+    size_t width  = CVPixelBufferGetWidth(pbuf);
+    size_t height = CVPixelBufferGetHeight(pbuf);
+    uint32_t cvpixfmt = CVPixelBufferGetPixelFormatType(pbuf);
+    int pixfmt = mp_imgfmt_from_cvpixelformat(cvpixfmt);
+    if (!pixfmt)
+        goto unlock;
+
+    struct mp_image img = {0};
+    mp_image_setfmt(&img, pixfmt);
+    mp_image_set_size(&img, width, height);
+
+    if (CVPixelBufferIsPlanar(pbuf)) {
+        int planes = CVPixelBufferGetPlaneCount(pbuf);
+        for (int i = 0; i < planes; i++) {
+            img.planes[i] = CVPixelBufferGetBaseAddressOfPlane(pbuf, i);
+            img.stride[i] = CVPixelBufferGetBytesPerRowOfPlane(pbuf, i);
+        }
+    } else {
+        img.planes[0] = CVPixelBufferGetBaseAddress(pbuf);
+        img.stride[0] = CVPixelBufferGetBytesPerRow(pbuf);
+    }
+
+    mp_image_copy_attributes(&img, hw_image);
+
+    image = mp_image_pool_new_copy(p->sw_pool, &img);
+
+unlock:
+    CVPixelBufferUnlockBaseAddress(pbuf, kCVPixelBufferLock_ReadOnly);
+
+    if (image) {
+        talloc_free(hw_image);
+        return image;
+    } else {
+        return hw_image;
+    }
 }
 
 static struct mp_image *process_image(struct lavc_ctx *ctx, struct mp_image *img)
 {
     if (img->imgfmt == IMGFMT_VIDEOTOOLBOX) {
         CVPixelBufferRef pbuf = (CVPixelBufferRef)img->planes[3];
-        img->params.hw_subfmt = CVPixelBufferGetPixelFormatType(pbuf);
+        uint32_t cvpixfmt = CVPixelBufferGetPixelFormatType(pbuf);
+        img->params.hw_subfmt = mp_imgfmt_from_cvpixelformat(cvpixfmt);
     }
     return img;
 }
@@ -124,3 +214,15 @@ const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox = {
     .init_decoder = init_decoder,
     .process_image = process_image,
 };
+
+const struct vd_lavc_hwdec mp_vd_lavc_videotoolbox_copy = {
+    .type = HWDEC_VIDEOTOOLBOX_COPY,
+    .copying = true,
+    .image_format = IMGFMT_VIDEOTOOLBOX,
+    .probe = probe_copy,
+    .init = init,
+    .uninit = uninit,
+    .init_decoder = init_decoder_copy,
+    .process_image = copy_image,
+    .delay_queue = HWDEC_DELAY_QUEUE_COUNT,
+};
diff --git a/video/filter/refqueue.c b/video/filter/refqueue.c
index 04de312..6b2e5a2 100644
--- a/video/filter/refqueue.c
+++ b/video/filter/refqueue.c
@@ -75,15 +75,6 @@ bool mp_refqueue_should_deint(struct mp_refqueue *q)
            !(q->flags & MP_MODE_INTERLACED_ONLY);
 }
 
-// Whether the current output frame is marked as interlaced.
-bool mp_refqueue_is_interlaced(struct mp_refqueue *q)
-{
-    if (!mp_refqueue_has_output(q))
-        return false;
-
-    return q->queue[q->pos]->fields & MP_IMGFIELD_INTERLACED;
-}
-
 // Whether the current output frame (field) is the top field, bottom field
 // otherwise. (Assumes the caller forces deinterlacing.)
 bool mp_refqueue_is_top_field(struct mp_refqueue *q)
diff --git a/video/filter/refqueue.h b/video/filter/refqueue.h
index ef23bee..bb23506 100644
--- a/video/filter/refqueue.h
+++ b/video/filter/refqueue.h
@@ -27,7 +27,6 @@ enum {
 
 void mp_refqueue_set_mode(struct mp_refqueue *q, int flags);
 bool mp_refqueue_should_deint(struct mp_refqueue *q);
-bool mp_refqueue_is_interlaced(struct mp_refqueue *q);
 bool mp_refqueue_is_top_field(struct mp_refqueue *q);
 bool mp_refqueue_top_field_first(struct mp_refqueue *q);
 bool mp_refqueue_is_second_field(struct mp_refqueue *q);
diff --git a/video/filter/vf_d3d11vpp.c b/video/filter/vf_d3d11vpp.c
index 6faf712..7a52565 100644
--- a/video/filter/vf_d3d11vpp.c
+++ b/video/filter/vf_d3d11vpp.c
@@ -29,7 +29,12 @@
 #include "video/mp_image_pool.h"
 
 // missing in MinGW
+#define D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BLEND 0x1
 #define D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BOB 0x2
+#define D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_ADAPTIVE 0x4
+#define D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_MOTION_COMPENSATION 0x8
+#define D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_INVERSE_TELECINE 0x10
+#define D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_FRAME_RATE_CONVERSION 0x20
 
 struct vf_priv_s {
     ID3D11Device *vo_dev;
@@ -57,6 +62,7 @@ struct vf_priv_s {
 
     int deint_enabled;
     int interlaced_only;
+    int mode;
 };
 
 static void release_tex(void *arg)
@@ -159,8 +165,8 @@ static int recreate_video_proc(struct vf_instance *vf)
     if (FAILED(hr))
         goto fail;
 
-    MP_VERBOSE(vf, "Found %d rate conversion caps.\n",
-               (int)caps.RateConversionCapsCount);
+    MP_VERBOSE(vf, "Found %d rate conversion caps. Looking for caps=0x%x.\n",
+               (int)caps.RateConversionCapsCount, p->mode);
 
     int rindex = -1;
     for (int n = 0; n < caps.RateConversionCapsCount; n++) {
@@ -170,8 +176,7 @@ static int recreate_video_proc(struct vf_instance *vf)
         if (FAILED(hr))
             goto fail;
         MP_VERBOSE(vf, "  - %d: 0x%08x\n", n, (unsigned)rcaps.ProcessorCaps);
-        if (rcaps.ProcessorCaps & D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BOB)
-        {
+        if (rcaps.ProcessorCaps & p->mode) {
             MP_VERBOSE(vf, "       (matching)\n");
             if (rindex < 0)
                 rindex = n;
@@ -179,10 +184,12 @@ static int recreate_video_proc(struct vf_instance *vf)
     }
 
     if (rindex < 0) {
-        MP_WARN(vf, "No video deinterlacing processor found.\n");
+        MP_WARN(vf, "No fitting video processor found, picking #0.\n");
         rindex = 0;
     }
 
+    // TOOD: so, how do we select which rate conversion mode the processor uses?
+
     hr = ID3D11VideoDevice_CreateVideoProcessor(p->video_dev, p->vp_enum, rindex,
                                                 &p->video_proc);
     if (FAILED(hr)) {
@@ -262,7 +269,7 @@ static int render(struct vf_instance *vf)
     mp_image_copy_attributes(out, in);
 
     D3D11_VIDEO_FRAME_FORMAT d3d_frame_format;
-    if (!mp_refqueue_is_interlaced(p->queue)) {
+    if (!mp_refqueue_should_deint(p->queue)) {
         d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
     } else if (mp_refqueue_top_field_first(p->queue)) {
         d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST;
@@ -282,7 +289,7 @@ static int render(struct vf_instance *vf)
             goto cleanup;
     }
 
-    if (!mp_refqueue_is_interlaced(p->queue)) {
+    if (!mp_refqueue_should_deint(p->queue)) {
         d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_PROGRESSIVE;
     } else if (mp_refqueue_is_top_field(p->queue)) {
         d3d_frame_format = D3D11_VIDEO_FRAME_FORMAT_INTERLACED_TOP_FIELD_FIRST;
@@ -518,6 +525,13 @@ fail:
 static const m_option_t vf_opts_fields[] = {
     OPT_FLAG("deint", deint_enabled, 0),
     OPT_FLAG("interlaced-only", interlaced_only, 0),
+    OPT_CHOICE("mode", mode, 0,
+        ({"blend", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BLEND},
+         {"bob", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BOB},
+         {"adaptive", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_ADAPTIVE},
+         {"mocomp", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_MOTION_COMPENSATION},
+         {"ivctc", D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_INVERSE_TELECINE},
+         {"none", 0})),
     {0}
 };
 
@@ -530,6 +544,7 @@ const vf_info_t vf_info_d3d11vpp = {
     .priv_defaults = &(const struct vf_priv_s) {
         .deint_enabled = 1,
         .interlaced_only = 1,
+        .mode = D3D11_VIDEO_PROCESSOR_PROCESSOR_CAPS_DEINTERLACE_BOB,
     },
     .options = vf_opts_fields,
 };
diff --git a/video/filter/vf_vavpp.c b/video/filter/vf_vavpp.c
index b24f886..ad669ac 100644
--- a/video/filter/vf_vavpp.c
+++ b/video/filter/vf_vavpp.c
@@ -169,7 +169,7 @@ static struct mp_image *render(struct vf_instance *vf)
     mp_image_copy_attributes(img, in);
 
     unsigned int flags = va_get_colorspace_flag(p->params.color.space);
-    if (!mp_refqueue_is_interlaced(p->queue)) {
+    if (!mp_refqueue_should_deint(p->queue)) {
         flags |= VA_FRAME_PICTURE;
     } else if (mp_refqueue_is_top_field(p->queue)) {
         flags |= VA_TOP_FIELD;
diff --git a/video/hwdec.h b/video/hwdec.h
index 5d563c9..4d99076 100644
--- a/video/hwdec.h
+++ b/video/hwdec.h
@@ -12,6 +12,7 @@ enum hwdec_type {
     HWDEC_AUTO_COPY,
     HWDEC_VDPAU,
     HWDEC_VIDEOTOOLBOX,
+    HWDEC_VIDEOTOOLBOX_COPY,
     HWDEC_VAAPI,
     HWDEC_VAAPI_COPY,
     HWDEC_DXVA2,
diff --git a/video/mp_image.c b/video/mp_image.c
index a4ce6d1..531565f 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -512,7 +512,7 @@ char *mp_image_params_to_str_buf(char *b, size_t bs,
             mp_snprintf_cat(b, bs, " [%d:%d]", p->p_w, p->p_h);
         mp_snprintf_cat(b, bs, " %s", mp_imgfmt_to_name(p->imgfmt));
         if (p->hw_subfmt)
-            mp_snprintf_cat(b, bs, "[%llu]", (unsigned long long)(p->hw_subfmt));
+            mp_snprintf_cat(b, bs, "[%s]", mp_imgfmt_to_name(p->hw_subfmt));
         mp_snprintf_cat(b, bs, " %s/%s",
                         m_opt_choice_str(mp_csp_names, p->color.space),
                         m_opt_choice_str(mp_csp_levels_names, p->color.levels));
diff --git a/video/mp_image.h b/video/mp_image.h
index dfbe4ee..13e364a 100644
--- a/video/mp_image.h
+++ b/video/mp_image.h
@@ -39,8 +39,7 @@
 // usually copy the whole struct, so that fields added later will be preserved.
 struct mp_image_params {
     enum mp_imgfmt imgfmt;      // pixel format
-    uint64_t hw_subfmt;         // underlying format for some hwaccel pixfmts
-                                // (will use the HW API's format identifiers)
+    enum mp_imgfmt hw_subfmt;   // underlying format for some hwaccel pixfmts
     int w, h;                   // image dimensions
     int p_w, p_h;               // define pixel aspect ratio (undefined: 0/0)
     struct mp_colorspace color;
diff --git a/video/out/cocoa_common.m b/video/out/cocoa_common.m
index 21e1246..557e28e 100644
--- a/video/out/cocoa_common.m
+++ b/video/out/cocoa_common.m
@@ -90,8 +90,6 @@ struct vo_cocoa_state {
     uint32_t old_dwidth;
     uint32_t old_dheight;
 
-    NSData *icc_wnd_profile;
-    NSData *icc_fs_profile;
     id   fs_icc_changed_ns_observer;
 
     pthread_mutex_t lock;
@@ -421,7 +419,6 @@ static void vo_cocoa_update_screen_info(struct vo *vo, struct mp_rect *out_rc)
         return;
 
     vo_cocoa_update_screens_pointers(vo);
-    vo_cocoa_update_screen_fps(vo);
 
     if (out_rc) {
         NSRect r = [s->current_screen frame];
@@ -584,6 +581,7 @@ static void cocoa_screen_reconfiguration_observer(
         struct vo *vo = ctx;
         MP_WARN(vo, "detected display mode change, updating screen info\n");
         vo_cocoa_update_screen_info(vo, NULL);
+        vo_cocoa_update_screen_fps(vo);
     }
 }
 
@@ -614,6 +612,7 @@ int vo_cocoa_config_window(struct vo *vo)
     run_on_main_thread(vo, ^{
         struct mp_rect screenrc;
         vo_cocoa_update_screen_info(vo, &screenrc);
+        vo_cocoa_update_screen_fps(vo);
 
         struct vo_win_geometry geo;
         vo_calc_window_geometry(vo, &screenrc, &geo);
@@ -953,6 +952,7 @@ int vo_cocoa_control(struct vo *vo, int *events, int request, void *arg)
 - (void)windowDidChangeScreen:(NSNotification *)notification
 {
     vo_cocoa_update_screen_info(self.vout, NULL);
+    vo_cocoa_update_screen_fps(self.vout);
 }
 
 - (void)didChangeWindowedScreenProfile:(NSScreen *)screen
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
index df842bc..a546e00 100644
--- a/video/out/opengl/context.h
+++ b/video/out/opengl/context.h
@@ -63,6 +63,11 @@ struct mpgl_driver {
     // This behaves exactly like vo_driver.control().
     int (*control)(struct MPGLContext *ctx, int *events, int request, void *arg);
 
+    // These behave exactly like vo_driver.wakeup/wait_events. They are
+    // optional.
+    void (*wakeup)(struct MPGLContext *ctx);
+    void (*wait_events)(struct MPGLContext *ctx, int64_t until_time_us);
+
     // Destroy the GL context and possibly the underlying VO backend.
     void (*uninit)(struct MPGLContext *ctx);
 };
diff --git a/video/out/opengl/context_angle.c b/video/out/opengl/context_angle.c
index 28515f4..ebc803f 100644
--- a/video/out/opengl/context_angle.c
+++ b/video/out/opengl/context_angle.c
@@ -15,6 +15,7 @@
  * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <initguid.h>
 #include <windows.h>
 #include <EGL/egl.h>
 #include <EGL/eglext.h>
@@ -33,11 +34,15 @@
 #define EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE 0x0002
 #endif
 
+// Windows 8 enum value, not present in mingw-w64 headers
+#define DXGI_ADAPTER_FLAG_SOFTWARE (2)
+
 struct priv {
     EGLDisplay egl_display;
     EGLContext egl_context;
     EGLSurface egl_surface;
     bool use_es2;
+    bool sw_adapter_msg_shown;
     PFNEGLPOSTSUBBUFFERNVPROC eglPostSubBufferNV;
 };
 
@@ -104,6 +109,15 @@ static bool create_context_egl(MPGLContext *ctx, EGLConfig config, int version)
     return true;
 }
 
+static void show_sw_adapter_msg(struct MPGLContext *ctx)
+{
+    struct priv *p = ctx->priv;
+    if (p->sw_adapter_msg_shown)
+        return;
+    MP_WARN(ctx->vo, "Using a software adapter\n");
+    p->sw_adapter_msg_shown = true;
+}
+
 static void d3d_init(struct MPGLContext *ctx)
 {
     HRESULT hr;
@@ -111,6 +125,7 @@ static void d3d_init(struct MPGLContext *ctx)
     struct vo *vo = ctx->vo;
     IDXGIDevice *dxgi_dev = NULL;
     IDXGIAdapter *dxgi_adapter = NULL;
+    IDXGIAdapter1 *dxgi_adapter1 = NULL;
     IDXGIFactory *dxgi_factory = NULL;
 
     PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT =
@@ -147,6 +162,25 @@ static void d3d_init(struct MPGLContext *ctx)
             goto done;
         }
 
+        // Windows 8 can choose a software adapter even if mpv didn't ask for
+        // one. If this is the case, show a warning message.
+        hr = IDXGIAdapter_QueryInterface(dxgi_adapter, &IID_IDXGIAdapter1,
+            (void**)&dxgi_adapter1);
+        if (SUCCEEDED(hr)) {
+            DXGI_ADAPTER_DESC1 desc;
+            hr = IDXGIAdapter1_GetDesc1(dxgi_adapter1, &desc);
+            if (SUCCEEDED(hr)) {
+                if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
+                    show_sw_adapter_msg(ctx);
+
+                // If the primary display adapter is a software adapter, the
+                // DXGI_ADAPTER_FLAG_SOFTWARE won't be set, but the device IDs
+                // should still match the Microsoft Basic Render Driver
+                if (desc.VendorId == 0x1414 && desc.DeviceId == 0x8c)
+                    show_sw_adapter_msg(ctx);
+            }
+        }
+
         hr = IDXGIAdapter_GetParent(dxgi_adapter, &IID_IDXGIFactory,
             (void**)&dxgi_factory);
         if (FAILED(hr)) {
@@ -168,6 +202,8 @@ done:
         IDXGIDevice_Release(dxgi_dev);
     if (dxgi_adapter)
         IDXGIAdapter_Release(dxgi_adapter);
+    if (dxgi_adapter1)
+        IDXGIAdapter1_Release(dxgi_adapter1);
     if (dxgi_factory)
         IDXGIFactory_Release(dxgi_factory);
 }
@@ -204,31 +240,39 @@ static int angle_init(struct MPGLContext *ctx, int flags)
     }
 
     EGLint d3d_types[] = {EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE,
-                          EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE};
+                          EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
+                          EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE};
+    EGLint d3d_dev_types[] = {EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
+                              EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
+                              EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE};
     for (int i = 0; i < MP_ARRAY_SIZE(d3d_types); i++) {
         EGLint display_attributes[] = {
             EGL_PLATFORM_ANGLE_TYPE_ANGLE,
                 d3d_types[i],
             EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE,
-                EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE,
+                d3d_dev_types[i],
             EGL_NONE,
         };
 
         p->egl_display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, dc,
             display_attributes);
-        if (p->egl_display != EGL_NO_DISPLAY)
-            break;
+        if (p->egl_display == EGL_NO_DISPLAY)
+            continue;
+
+        if (!eglInitialize(p->egl_display, NULL, NULL)) {
+            p->egl_display = EGL_NO_DISPLAY;
+            continue;
+        }
+
+        if (d3d_dev_types[i] == EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE)
+            show_sw_adapter_msg(ctx);
+        break;
     }
     if (p->egl_display == EGL_NO_DISPLAY) {
         MP_FATAL(vo, "Couldn't get display\n");
         goto fail;
     }
 
-    if (!eglInitialize(p->egl_display, NULL, NULL)) {
-        MP_FATAL(vo, "Couldn't initialize EGL\n");
-        goto fail;
-    }
-
     const char *exts = eglQueryString(p->egl_display, EGL_EXTENSIONS);
     if (exts)
         MP_DBG(ctx->vo, "EGL extensions: %s\n", exts);
diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c
index e74132b..efb6128 100644
--- a/video/out/opengl/context_wayland.c
+++ b/video/out/opengl/context_wayland.c
@@ -59,9 +59,7 @@ static void egl_resize(struct vo_wayland_state *wl)
     /* set size for mplayer */
     wl->vo->dwidth  = scale*wl->window.width;
     wl->vo->dheight = scale*wl->window.height;
-
     wl->vo->want_redraw = true;
-    wl->window.events = 0;
 }
 
 static int egl_create_context(struct vo_wayland_state *wl,
@@ -207,8 +205,7 @@ static void waylandgl_swap_buffers(MPGLContext *ctx)
     if (!wl->frame.callback)
         vo_wayland_request_frame(ctx->vo, NULL, NULL);
 
-    if (!vo_wayland_wait_frame(ctx->vo))
-        MP_DBG(wl, "discarding frame callback\n");
+    vo_wayland_wait_events(ctx->vo, 0);
 
     eglSwapBuffers(wl->egl_context.egl.dpy, wl->egl_context.egl_surface);
 }
@@ -225,6 +222,16 @@ static int waylandgl_control(MPGLContext *ctx, int *events, int request,
     return r;
 }
 
+static void wayland_wakeup(struct MPGLContext *ctx)
+{
+    vo_wayland_wakeup(ctx->vo);
+}
+
+static void wayland_wait_events(struct MPGLContext *ctx, int64_t until_time_us)
+{
+    vo_wayland_wait_events(ctx->vo, until_time_us);
+}
+
 static int waylandgl_init(struct MPGLContext *ctx, int flags)
 {
     if (!vo_wayland_init(ctx->vo))
@@ -239,5 +246,7 @@ const struct mpgl_driver mpgl_driver_wayland = {
     .reconfig       = waylandgl_reconfig,
     .swap_buffers   = waylandgl_swap_buffers,
     .control        = waylandgl_control,
+    .wakeup         = wayland_wakeup,
+    .wait_events    = wayland_wait_events,
     .uninit         = waylandgl_uninit,
 };
diff --git a/video/out/opengl/context_x11.c b/video/out/opengl/context_x11.c
index 11700ef..48533fe 100644
--- a/video/out/opengl/context_x11.c
+++ b/video/out/opengl/context_x11.c
@@ -308,6 +308,16 @@ static void glx_swap_buffers(struct MPGLContext *ctx)
     glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
 }
 
+static void glx_wakeup(struct MPGLContext *ctx)
+{
+    vo_x11_wakeup(ctx->vo);
+}
+
+static void glx_wait_events(struct MPGLContext *ctx, int64_t until_time_us)
+{
+    vo_x11_wait_events(ctx->vo, until_time_us);
+}
+
 const struct mpgl_driver mpgl_driver_x11 = {
     .name           = "x11",
     .priv_size      = sizeof(struct glx_context),
@@ -315,6 +325,8 @@ const struct mpgl_driver mpgl_driver_x11 = {
     .reconfig       = glx_reconfig,
     .swap_buffers   = glx_swap_buffers,
     .control        = glx_control,
+    .wakeup         = glx_wakeup,
+    .wait_events    = glx_wait_events,
     .uninit         = glx_uninit,
 };
 
@@ -325,5 +337,7 @@ const struct mpgl_driver mpgl_driver_x11_probe = {
     .reconfig       = glx_reconfig,
     .swap_buffers   = glx_swap_buffers,
     .control        = glx_control,
+    .wakeup         = glx_wakeup,
+    .wait_events    = glx_wait_events,
     .uninit         = glx_uninit,
 };
diff --git a/video/out/opengl/context_x11egl.c b/video/out/opengl/context_x11egl.c
index 2e4fd5f..aea388b 100644
--- a/video/out/opengl/context_x11egl.c
+++ b/video/out/opengl/context_x11egl.c
@@ -191,6 +191,16 @@ static void mpegl_swap_buffers(MPGLContext *ctx)
     eglSwapBuffers(p->egl_display, p->egl_surface);
 }
 
+static void mpegl_wakeup(struct MPGLContext *ctx)
+{
+    vo_x11_wakeup(ctx->vo);
+}
+
+static void mpegl_wait_events(struct MPGLContext *ctx, int64_t until_time_us)
+{
+    vo_x11_wait_events(ctx->vo, until_time_us);
+}
+
 const struct mpgl_driver mpgl_driver_x11egl = {
     .name           = "x11egl",
     .priv_size      = sizeof(struct priv),
@@ -198,5 +208,7 @@ const struct mpgl_driver mpgl_driver_x11egl = {
     .reconfig       = mpegl_reconfig,
     .swap_buffers   = mpegl_swap_buffers,
     .control        = mpegl_control,
+    .wakeup         = mpegl_wakeup,
+    .wait_events    = mpegl_wait_events,
     .uninit         = mpegl_uninit,
 };
diff --git a/video/out/opengl/hwdec_d3d11egl.c b/video/out/opengl/hwdec_d3d11egl.c
index 07333c3..4360286 100644
--- a/video/out/opengl/hwdec_d3d11egl.c
+++ b/video/out/opengl/hwdec_d3d11egl.c
@@ -255,6 +255,7 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
         goto fail;
 
     params->imgfmt = params->hw_subfmt;
+    params->hw_subfmt = 0;
 
     for (int n = 0; n < num_planes; n++) {
         gl->ActiveTexture(GL_TEXTURE0 + texunits + n);
diff --git a/video/out/opengl/hwdec_d3d11eglrgb.c b/video/out/opengl/hwdec_d3d11eglrgb.c
index be8057c..d43b1f5 100644
--- a/video/out/opengl/hwdec_d3d11eglrgb.c
+++ b/video/out/opengl/hwdec_d3d11eglrgb.c
@@ -190,6 +190,7 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
     gl->BindTexture(GL_TEXTURE_2D, 0);
 
     params->imgfmt = IMGFMT_RGB0;
+    params->hw_subfmt = 0;
     return 0;
 }
 
diff --git a/video/out/opengl/hwdec_dxva2egl.c b/video/out/opengl/hwdec_dxva2egl.c
index f206b96..aa06c43 100644
--- a/video/out/opengl/hwdec_dxva2egl.c
+++ b/video/out/opengl/hwdec_dxva2egl.c
@@ -273,6 +273,7 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
     gl->BindTexture(GL_TEXTURE_2D, 0);
 
     params->imgfmt = IMGFMT_RGB0;
+    params->hw_subfmt = 0;
     return 0;
 fail:
     destroy_textures(hw);
diff --git a/video/out/opengl/hwdec_dxva2gldx.c b/video/out/opengl/hwdec_dxva2gldx.c
index 4cd8c1c..c0bca28 100644
--- a/video/out/opengl/hwdec_dxva2gldx.c
+++ b/video/out/opengl/hwdec_dxva2gldx.c
@@ -160,6 +160,7 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
     }
 
     params->imgfmt = SHARED_SURFACE_MPFMT;
+    params->hw_subfmt = 0;
 
     return 0;
 fail:
diff --git a/video/out/opengl/hwdec_osx.c b/video/out/opengl/hwdec_osx.c
index 6ddfa66..e0fc5ab 100644
--- a/video/out/opengl/hwdec_osx.c
+++ b/video/out/opengl/hwdec_osx.c
@@ -70,6 +70,16 @@ static struct vt_format vt_formats[] = {
         }
     },
     {
+        .cvpixfmt = kCVPixelFormatType_420YpCbCr8Planar,
+        .imgfmt = IMGFMT_420P,
+        .planes = 3,
+        .gl = {
+            { GL_RED, GL_UNSIGNED_BYTE, GL_RED },
+            { GL_RED, GL_UNSIGNED_BYTE, GL_RED },
+            { GL_RED, GL_UNSIGNED_BYTE, GL_RED },
+        }
+    },
+    {
         .cvpixfmt = kCVPixelFormatType_32BGRA,
         .imgfmt = IMGFMT_RGB0,
         .planes = 1,
@@ -186,13 +196,14 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
 {
     assert(params->imgfmt == hw->driver->imgfmt);
 
-    struct vt_format *f = vt_get_gl_format(params->hw_subfmt);
+    struct vt_format *f = vt_get_gl_format_from_imgfmt(params->hw_subfmt);
     if (!f) {
         MP_ERR(hw, "Unsupported CVPixelBuffer format.\n");
         return -1;
     }
 
     params->imgfmt = f->imgfmt;
+    params->hw_subfmt = 0;
     return 0;
 }
 
diff --git a/video/out/opengl/hwdec_vaegl.c b/video/out/opengl/hwdec_vaegl.c
index 6c52cdd..85f994e 100644
--- a/video/out/opengl/hwdec_vaegl.c
+++ b/video/out/opengl/hwdec_vaegl.c
@@ -250,19 +250,19 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
     }
     gl->BindTexture(GL_TEXTURE_2D, 0);
 
-    p->current_mpfmt = va_fourcc_to_imgfmt(params->hw_subfmt);
+    p->current_mpfmt = params->hw_subfmt;
     if (p->current_mpfmt != IMGFMT_NV12 &&
         p->current_mpfmt != IMGFMT_420P)
     {
         MP_FATAL(p, "unsupported VA image format %s\n",
-                 mp_tag_str(params->hw_subfmt));
+                 mp_imgfmt_to_name(p->current_mpfmt));
         return -1;
     }
 
-    MP_VERBOSE(p, "format: %s %s\n", mp_tag_str(params->hw_subfmt),
-               mp_imgfmt_to_name(p->current_mpfmt));
+    MP_VERBOSE(p, "hw format: %s\n", mp_imgfmt_to_name(p->current_mpfmt));
 
     params->imgfmt = p->current_mpfmt;
+    params->hw_subfmt = 0;
 
     return 0;
 }
diff --git a/video/out/opengl/hwdec_vaglx.c b/video/out/opengl/hwdec_vaglx.c
index 0400604..ac817d7 100644
--- a/video/out/opengl/hwdec_vaglx.c
+++ b/video/out/opengl/hwdec_vaglx.c
@@ -167,6 +167,7 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
     gl->BindTexture(GL_TEXTURE_2D, 0);
 
     params->imgfmt = IMGFMT_RGB0;
+    params->hw_subfmt = 0;
 
     return 0;
 }
diff --git a/video/out/opengl/hwdec_vdpau.c b/video/out/opengl/hwdec_vdpau.c
index 83f664a..f9d101c 100644
--- a/video/out/opengl/hwdec_vdpau.c
+++ b/video/out/opengl/hwdec_vdpau.c
@@ -173,6 +173,7 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
 
     if (p->direct_mode) {
         params->imgfmt = IMGFMT_NV12;
+        params->hw_subfmt = 0;
     } else {
         vdp_st = vdp->output_surface_create(p->ctx->vdp_device,
                                             VDP_RGBA_FORMAT_B8G8R8A8,
@@ -188,6 +189,7 @@ static int reinit(struct gl_hwdec *hw, struct mp_image_params *params)
         gl->VDPAUSurfaceAccessNV(p->vdpgl_surface, GL_READ_ONLY);
 
         params->imgfmt = IMGFMT_RGB0;
+        params->hw_subfmt = 0;
     }
 
     gl_check_error(gl, hw->log, "After initializing vdpau OpenGL interop");
diff --git a/video/out/opengl/lcms.c b/video/out/opengl/lcms.c
index eaeb86f..ec352cd 100644
--- a/video/out/opengl/lcms.c
+++ b/video/out/opengl/lcms.c
@@ -59,7 +59,7 @@ static bool parse_3dlut_size(const char *arg, int *p1, int *p2, int *p3)
         return false;
     for (int n = 0; n < 3; n++) {
         int s = ((int[]) { *p1, *p2, *p3 })[n];
-        if (s < 2 || s > 512 || ((s - 1) & s))
+        if (s < 2 || s > 512)
             return false;
     }
     return true;
@@ -89,7 +89,7 @@ const struct m_sub_options mp_icc_conf = {
     },
     .size = sizeof(struct mp_icc_opts),
     .defaults = &(const struct mp_icc_opts) {
-        .size_str = "128x256x64",
+        .size_str = "64x64x64",
         .intent = INTENT_RELATIVE_COLORIMETRIC,
     },
 };
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index 468bee9..901e208 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -59,6 +59,7 @@ static const char *const fixed_scale_filters[] = {
 };
 static const char *const fixed_tscale_filters[] = {
     "oversample",
+    "linear",
     NULL
 };
 
@@ -186,6 +187,7 @@ struct gl_video {
 
     GLuint lut_3d_texture;
     bool use_lut_3d;
+    int lut_3d_size[3];
 
     GLuint dither_texture;
     int dither_size;
@@ -629,8 +631,10 @@ static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim,
 
     gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_3DLUT);
     gl->BindTexture(GL_TEXTURE_3D, p->lut_3d_texture);
+    gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1);
     gl->TexImage3D(GL_TEXTURE_3D, 0, GL_RGB16, lut3d->size[0], lut3d->size[1],
                    lut3d->size[2], 0, GL_RGB, GL_UNSIGNED_SHORT, lut3d->data);
+    gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
     gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@@ -640,6 +644,9 @@ static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim,
 
     debug_check_gl(p, "after 3d lut creation");
 
+    for (int i = 0; i < 3; i++)
+        p->lut_3d_size[i] = lut3d->size[i];
+
     talloc_free(lut3d);
 
     return true;
@@ -2187,7 +2194,10 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
 
     if (p->use_lut_3d) {
         gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT);
-        GLSL(color.rgb = texture3D(lut_3d, color.rgb).rgb;)
+        GLSL(vec3 cpos;)
+        for (int i = 0; i < 3; i++)
+            GLSLF("cpos[%d] = LUT_POS(color[%d], %d.0);\n", i, i, p->lut_3d_size[i]);
+        GLSL(color.rgb = texture3D(lut_3d, cpos).rgb;)
     }
 }
 
@@ -2525,9 +2535,10 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
     struct scaler *tscale = &p->scaler[SCALER_TSCALE];
     reinit_scaler(p, tscale, &p->opts.scaler[SCALER_TSCALE], 1, tscale_sizes);
     bool oversample = strcmp(tscale->conf.kernel.name, "oversample") == 0;
+    bool linear = strcmp(tscale->conf.kernel.name, "linear") == 0;
     int size;
 
-    if (oversample) {
+    if (oversample || linear) {
         size = 2;
     } else {
         assert(tscale->kernel && !tscale->kernel->polar);
@@ -2612,8 +2623,9 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
             }
         }
 
-        // Blend the frames together
         if (oversample) {
+            // Oversample uses the frame area as mix ratio, not the the vsync
+            // position itself
             double vsync_dist = t->vsync_interval / t->ideal_frame_duration,
                    threshold = tscale->conf.kernel.params[0];
             threshold = isnan(threshold) ? 0.0 : threshold;
@@ -2621,6 +2633,10 @@ static void gl_video_interpolate_frame(struct gl_video *p, struct vo_frame *t,
             mix = mix <= 0 + threshold ? 0 : mix;
             mix = mix >= 1 - threshold ? 1 : mix;
             mix = 1 - mix;
+        }
+
+        // Blend the frames together
+        if (oversample || linear) {
             gl_sc_uniform_f(p->sc, "inter_coeff", mix);
             GLSL(color = mix(texture(texture0, texcoord0),
                              texture(texture1, texcoord1),
@@ -3489,7 +3505,7 @@ void gl_video_configure_queue(struct gl_video *p, struct vo *vo)
             radius = radius > 0 ? radius : p->opts.scaler[SCALER_TSCALE].radius;
             queue_size += 1 + ceil(radius);
         } else {
-            // Oversample case
+            // Oversample/linear case
             queue_size += 2;
         }
     }
diff --git a/video/out/vo.c b/video/out/vo.c
index 07476ad..aa92d34 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -23,11 +23,6 @@
 #include <pthread.h>
 #include <math.h>
 
-#ifndef __MINGW32__
-#include <unistd.h>
-#include <poll.h>
-#endif
-
 #include "mpv_talloc.h"
 
 #include "config.h"
@@ -127,9 +122,6 @@ struct vo_internal {
     bool need_wakeup;
     bool terminate;
 
-    int wakeup_pipe[2]; // used for VOs that use a unix FD for waiting
-
-
     bool hasframe;
     bool hasframe_rendered;
     bool request_redraw;            // redraw request from player to VO
@@ -215,8 +207,6 @@ static void dealloc_vo(struct vo *vo)
     forget_frames(vo); // implicitly synchronized
     pthread_mutex_destroy(&vo->in->lock);
     pthread_cond_destroy(&vo->in->wakeup);
-    for (int n = 0; n < 2; n++)
-        close(vo->in->wakeup_pipe[n]);
     talloc_free(vo);
 }
 
@@ -239,7 +229,6 @@ static struct vo *vo_create(bool probing, struct mpv_global *global,
         .encode_lavc_ctx = ex->encode_lavc_ctx,
         .input_ctx = ex->input_ctx,
         .osd = ex->osd,
-        .event_fd = -1,
         .monitor_par = 1,
         .extra = *ex,
         .probing = probing,
@@ -251,7 +240,6 @@ static struct vo *vo_create(bool probing, struct mpv_global *global,
         .req_frames = 1,
         .estimated_vsync_jitter = -1,
     };
-    mp_make_wakeup_pipe(vo->in->wakeup_pipe);
     mp_dispatch_set_wakeup_fn(vo->in->dispatch, dispatch_wakeup_cb, vo);
     pthread_mutex_init(&vo->in->lock, NULL);
     pthread_cond_init(&vo->in->wakeup, NULL);
@@ -580,60 +568,34 @@ static void forget_frames(struct vo *vo)
     }
 }
 
-#ifndef __MINGW32__
-static void wait_event_fd(struct vo *vo, int64_t until_time)
+// VOs which have no special requirements on UI event loops etc. can set the
+// vo_driver.wait_events callback to this (and leave vo_driver.wakeup unset).
+// This function must not be used or called for other purposes.
+void vo_wait_default(struct vo *vo, int64_t until_time)
 {
     struct vo_internal *in = vo->in;
 
-    struct pollfd fds[2] = {
-        { .fd = vo->event_fd, .events = POLLIN },
-        { .fd = in->wakeup_pipe[0], .events = POLLIN },
-    };
-    int64_t wait_us = until_time - mp_time_us();
-    int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
-
-    poll(fds, 2, timeout_ms);
-
-    if (fds[1].revents & POLLIN) {
-        char buf[100];
-        (void)read(in->wakeup_pipe[0], buf, sizeof(buf)); // flush
+    pthread_mutex_lock(&in->lock);
+    if (!in->need_wakeup) {
+        struct timespec ts = mp_time_us_to_timespec(until_time);
+        pthread_cond_timedwait(&in->wakeup, &in->lock, &ts);
     }
+    pthread_mutex_unlock(&in->lock);
 }
-static void wakeup_event_fd(struct vo *vo)
-{
-    struct vo_internal *in = vo->in;
-
-    (void)write(in->wakeup_pipe[1], &(char){0}, 1);
-}
-#else
-static void wait_event_fd(struct vo *vo, int64_t until_time){}
-static void wakeup_event_fd(struct vo *vo){}
-#endif
 
 // Called unlocked.
 static void wait_vo(struct vo *vo, int64_t until_time)
 {
     struct vo_internal *in = vo->in;
 
-    if (vo->event_fd >= 0) {
-        wait_event_fd(vo, until_time);
-        pthread_mutex_lock(&in->lock);
-        in->need_wakeup = false;
-        pthread_mutex_unlock(&in->lock);
-    } else if (vo->driver->wait_events) {
+    if (vo->driver->wait_events) {
         vo->driver->wait_events(vo, until_time);
-        pthread_mutex_lock(&in->lock);
-        in->need_wakeup = false;
-        pthread_mutex_unlock(&in->lock);
     } else {
-        pthread_mutex_lock(&in->lock);
-        if (!in->need_wakeup) {
-            struct timespec ts = mp_time_us_to_timespec(until_time);
-            pthread_cond_timedwait(&in->wakeup, &in->lock, &ts);
-        }
-        in->need_wakeup = false;
-        pthread_mutex_unlock(&in->lock);
+        vo_wait_default(vo, until_time);
     }
+    pthread_mutex_lock(&in->lock);
+    in->need_wakeup = false;
+    pthread_mutex_unlock(&in->lock);
 }
 
 static void wakeup_locked(struct vo *vo)
@@ -641,8 +603,6 @@ static void wakeup_locked(struct vo *vo)
     struct vo_internal *in = vo->in;
 
     pthread_cond_broadcast(&in->wakeup);
-    if (vo->event_fd >= 0)
-        wakeup_event_fd(vo);
     if (vo->driver->wakeup)
         vo->driver->wakeup(vo);
     in->need_wakeup = true;
@@ -1136,6 +1096,14 @@ double vo_get_delay(struct vo *vo)
     return res ? (res - mp_time_us()) / 1e6 : 0;
 }
 
+void vo_discard_timing_info(struct vo *vo)
+{
+    struct vo_internal *in = vo->in;
+    pthread_mutex_lock(&in->lock);
+    reset_vsync_timings(vo);
+    pthread_mutex_unlock(&in->lock);
+}
+
 int64_t vo_get_delayed_count(struct vo *vo)
 {
     struct vo_internal *in = vo->in;
diff --git a/video/out/vo.h b/video/out/vo.h
index 9c29d5f..a5280e5 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -269,9 +269,8 @@ struct vo_driver {
     void (*flip_page)(struct vo *vo);
 
     /* These optional callbacks can be provided if the GUI framework used by
-     * the VO requires entering a message loop for receiving events, does not
-     * provide event_fd, and does not call vo_wakeup() from a separate thread
-     * when there are new events.
+     * the VO requires entering a message loop for receiving events and does
+     * not call vo_wakeup() from a separate thread when there are new events.
      *
      * wait_events() will wait for new events, until the timeout expires, or the
      * function is interrupted. wakeup() is used to possibly interrupt the
@@ -282,7 +281,7 @@ struct vo_driver {
      * immediately.
      */
     void (*wakeup)(struct vo *vo);
-    int (*wait_events)(struct vo *vo, int64_t until_time_us);
+    void (*wait_events)(struct vo *vo, int64_t until_time_us);
 
     /*
      * Closes driver. Should restore the original state of the system.
@@ -319,7 +318,6 @@ struct vo {
 
     // --- The following fields are generally only changed during initialization.
 
-    int event_fd;  // check_events() should be called when this has input
     bool probing;
 
     // --- The following fields are only changed with vo_reconfig(), and can
@@ -368,8 +366,10 @@ double vo_get_estimated_vsync_interval(struct vo *vo);
 double vo_get_estimated_vsync_jitter(struct vo *vo);
 double vo_get_display_fps(struct vo *vo);
 double vo_get_delay(struct vo *vo);
+void vo_discard_timing_info(struct vo *vo);
 
 void vo_wakeup(struct vo *vo);
+void vo_wait_default(struct vo *vo, int64_t until_time);
 
 struct mp_keymap {
   int from;
diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c
index 74ddb23..6279c0b 100644
--- a/video/out/vo_direct3d.c
+++ b/video/out/vo_direct3d.c
@@ -26,6 +26,7 @@
 #include <assert.h>
 #include <d3d9.h>
 #include <inttypes.h>
+#include <limits.h>
 #include "config.h"
 #include "options/options.h"
 #include "options/m_option.h"
diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c
index 5a7c613..f03f503 100644
--- a/video/out/vo_drm.c
+++ b/video/out/vo_drm.c
@@ -253,9 +253,7 @@ static void acquire_vt(void *data)
     crtc_setup(vo);
 }
 
-
-
-static int wait_events(struct vo *vo, int64_t until_time_us)
+static void wait_events(struct vo *vo, int64_t until_time_us)
 {
     struct priv *p = vo->priv;
     if (p->vt_switcher_active) {
@@ -263,7 +261,6 @@ static int wait_events(struct vo *vo, int64_t until_time_us)
         int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
         vt_switcher_poll(&p->vt_switcher, timeout_ms);
     }
-    return 0;
 }
 
 static void wakeup(struct vo *vo)
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
index 095308f..8a7c797 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -357,6 +357,23 @@ static int control(struct vo *vo, uint32_t request, void *data)
     return r;
 }
 
+static void wakeup(struct vo *vo)
+{
+    struct gl_priv *p = vo->priv;
+    if (p->glctx && p->glctx->driver->wakeup)
+        p->glctx->driver->wakeup(p->glctx);
+}
+
+static void wait_events(struct vo *vo, int64_t until_time_us)
+{
+    struct gl_priv *p = vo->priv;
+    if (p->glctx->driver->wait_events) {
+        p->glctx->driver->wait_events(p->glctx, until_time_us);
+    } else {
+        vo_wait_default(vo, until_time_us);
+    }
+}
+
 static void uninit(struct vo *vo)
 {
     struct gl_priv *p = vo->priv;
@@ -466,6 +483,8 @@ const struct vo_driver video_out_opengl = {
     .control = control,
     .draw_frame = draw_frame,
     .flip_page = flip_page,
+    .wait_events = wait_events,
+    .wakeup = wakeup,
     .uninit = uninit,
     .priv_size = sizeof(struct gl_priv),
     .options = options,
@@ -481,6 +500,8 @@ const struct vo_driver video_out_opengl_hq = {
     .control = control,
     .draw_frame = draw_frame,
     .flip_page = flip_page,
+    .wait_events = wait_events,
+    .wakeup = wakeup,
     .uninit = uninit,
     .priv_size = sizeof(struct gl_priv),
     .priv_defaults = &(const struct gl_priv){
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
index 4ac0c96..06c90d2 100644
--- a/video/out/vo_opengl_cb.c
+++ b/video/out/vo_opengl_cb.c
@@ -175,10 +175,16 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts,
     if (ctx->renderer)
         return MPV_ERROR_INVALID_PARAMETER;
 
+    talloc_free(ctx->gl);
     ctx->gl = talloc_zero(ctx, GL);
 
     mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx,
                          exts, ctx->log);
+    if (!ctx->gl->version && !ctx->gl->es) {
+        MP_FATAL(ctx, "OpenGL not initialized.\n");
+        return MPV_ERROR_UNSUPPORTED;
+    }
+
     ctx->renderer = gl_video_init(ctx->gl, ctx->log, ctx->global);
     if (!ctx->renderer)
         return MPV_ERROR_UNSUPPORTED;
diff --git a/video/out/vo_sdl.c b/video/out/vo_sdl.c
index dd18f6e..d33ace1 100644
--- a/video/out/vo_sdl.c
+++ b/video/out/vo_sdl.c
@@ -533,7 +533,7 @@ static void wakeup(struct vo *vo)
     SDL_PushEvent(&event);
 }
 
-static int wait_events(struct vo *vo, int64_t until_time_us)
+static void wait_events(struct vo *vo, int64_t until_time_us)
 {
     int64_t wait_us = until_time_us - mp_time_us();
     int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
@@ -619,8 +619,6 @@ static int wait_events(struct vo *vo, int64_t until_time_us)
             break;
         }
     }
-
-    return 0;
 }
 
 static void uninit(struct vo *vo)
diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c
index 11bb469..6128793 100644
--- a/video/out/vo_vaapi.c
+++ b/video/out/vo_vaapi.c
@@ -682,6 +682,8 @@ const struct vo_driver video_out_vaapi = {
     .control = control,
     .draw_image = draw_image,
     .flip_page = flip_page,
+    .wakeup = vo_x11_wakeup,
+    .wait_events = vo_x11_wait_events,
     .uninit = uninit,
     .priv_size = sizeof(struct priv),
     .priv_defaults = &(const struct priv) {
diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c
index 6dd31e9..2a0566f 100644
--- a/video/out/vo_vdpau.c
+++ b/video/out/vo_vdpau.c
@@ -1139,6 +1139,8 @@ const struct vo_driver video_out_vdpau = {
     .control = control,
     .draw_frame = draw_frame,
     .flip_page = flip_page,
+    .wakeup = vo_x11_wakeup,
+    .wait_events = vo_x11_wait_events,
     .uninit = uninit,
     .priv_size = sizeof(struct vdpctx),
     .options = (const struct m_option []){
diff --git a/video/out/vo_wayland.c b/video/out/vo_wayland.c
index 2997b38..7761223 100644
--- a/video/out/vo_wayland.c
+++ b/video/out/vo_wayland.c
@@ -314,7 +314,6 @@ static bool resize(struct priv *p)
 
     p->x = x;
     p->y = y;
-    p->wl->window.events = 0;
     p->vo->want_redraw = true;
     return true;
 }
@@ -373,8 +372,7 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
         p->original_image = mpi;
     }
 
-    if (!vo_wayland_wait_frame(vo))
-        MP_DBG(p->wl, "discarding frame callback\n");
+    vo_wayland_wait_events(vo, 0);
 
     shm_buffer_t *buf = buffer_pool_get_back(&p->video_bufpool);
 
@@ -513,8 +511,7 @@ static void flip_page(struct vo *vo)
     if (!p->wl->frame.callback)
         vo_wayland_request_frame(vo, p, redraw);
 
-    if (!vo_wayland_wait_frame(vo))
-        MP_DBG(p->wl, "discarding frame callback\n");
+    vo_wayland_wait_events(vo, 0);
 }
 
 static int query_format(struct vo *vo, int format)
@@ -675,6 +672,8 @@ const struct vo_driver video_out_wayland = {
     .control = control,
     .draw_image = draw_image,
     .flip_page = flip_page,
+    .wakeup = vo_wayland_wakeup,
+    .wait_events = vo_wayland_wait_events,
     .uninit = uninit,
     .options = (const struct m_option[]) {
         OPT_FLAG("alpha", enable_alpha, 0),
diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c
index 01928b7..b06a231 100644
--- a/video/out/vo_x11.c
+++ b/video/out/vo_x11.c
@@ -449,5 +449,7 @@ const struct vo_driver video_out_x11 = {
     .control = control,
     .draw_image = draw_image,
     .flip_page = flip_page,
+    .wakeup = vo_x11_wakeup,
+    .wait_events = vo_x11_wait_events,
     .uninit = uninit,
 };
diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c
index 121dff0..e15c12b 100644
--- a/video/out/vo_xv.c
+++ b/video/out/vo_xv.c
@@ -905,6 +905,8 @@ const struct vo_driver video_out_xv = {
     .control = control,
     .draw_image = draw_image,
     .flip_page = flip_page,
+    .wakeup = vo_x11_wakeup,
+    .wait_events = vo_x11_wait_events,
     .uninit = uninit,
     .priv_size = sizeof(struct xvctx),
     .priv_defaults = &(const struct xvctx) {
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index e78e941..b1c95f7 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -95,7 +95,7 @@ struct vo_w32_state {
 
     bool disable_screensaver;
     bool cursor_visible;
-    int event_flags;
+    atomic_uint event_flags;
 
     BOOL tracking;
     TRACKMOUSEEVENT trackEvent;
@@ -569,7 +569,7 @@ static bool handle_char(struct vo_w32_state *w32, wchar_t wc)
 
 static void signal_events(struct vo_w32_state *w32, int events)
 {
-    w32->event_flags |= events;
+    atomic_fetch_or(&w32->event_flags, events);
     vo_wakeup(w32->vo);
 }
 
@@ -1498,8 +1498,7 @@ static void do_control(void *ptr)
     void *arg = p[3];
     int *ret = p[4];
     *ret = gui_thread_control(w32, request, arg);
-    *events |= w32->event_flags;
-    w32->event_flags = 0;
+    *events |= atomic_fetch_and(&w32->event_flags, 0);
     // Safe access, since caller (owner of vo) is blocked.
     if (*events & VO_EVENT_RESIZE) {
         w32->vo->dwidth = w32->dw;
@@ -1510,10 +1509,21 @@ static void do_control(void *ptr)
 int vo_w32_control(struct vo *vo, int *events, int request, void *arg)
 {
     struct vo_w32_state *w32 = vo->w32;
-    int r;
-    void *p[] = {w32, events, &request, arg, &r};
-    mp_dispatch_run(w32->dispatch, do_control, p);
-    return r;
+    if (request == VOCTRL_CHECK_EVENTS) {
+        *events |= atomic_fetch_and(&w32->event_flags, 0);
+        if (*events & VO_EVENT_RESIZE) {
+            mp_dispatch_lock(w32->dispatch);
+            vo->dwidth = w32->dw;
+            vo->dheight = w32->dh;
+            mp_dispatch_unlock(w32->dispatch);
+        }
+        return VO_TRUE;
+    } else {
+        int r;
+        void *p[] = {w32, events, &request, arg, &r};
+        mp_dispatch_run(w32->dispatch, do_control, p);
+        return r;
+    }
 }
 
 static void do_terminate(void *ptr)
diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c
index 0e44ddd..d4b7a1e 100644
--- a/video/out/wayland_common.c
+++ b/video/out/wayland_common.c
@@ -40,6 +40,7 @@
 
 #include "vo.h"
 #include "win_state.h"
+#include "osdep/io.h"
 #include "osdep/timer.h"
 
 #include "input/input.h"
@@ -57,7 +58,7 @@ static void schedule_resize(struct vo_wayland_state *wl,
                             int32_t width,
                             int32_t height);
 
-static void vo_wayland_fullscreen (struct vo *vo);
+static void vo_wayland_fullscreen(struct vo *vo);
 
 static const struct wl_callback_listener frame_listener;
 
@@ -140,6 +141,9 @@ static void ssurface_handle_configure(void *data,
                                       int32_t height)
 {
     struct vo_wayland_state *wl = data;
+    float win_aspect = wl->window.aspect;
+    if (!wl->window.is_fullscreen)
+        width = win_aspect * height;
     schedule_resize(wl, edges, width, height);
 }
 
@@ -225,7 +229,7 @@ static void surface_handle_enter(void *data,
         }
     }
 
-    wl->window.events |= VO_EVENT_WIN_STATE;
+    wl->window.events |= VO_EVENT_WIN_STATE | VO_EVENT_RESIZE;
 }
 
 static void surface_handle_leave(void *data,
@@ -521,104 +525,9 @@ static const struct wl_seat_listener seat_listener = {
     seat_handle_name,
 };
 
-static void data_offer_handle_offer(void *data,
-                                    struct wl_data_offer *offer,
-                                    const char *mime_type)
-{
-    struct vo_wayland_state *wl = data;
-    if (strcmp(mime_type, "text/uri-list") != 0)
-        MP_VERBOSE(wl, "unsupported mime type for drag and drop: %s\n", mime_type);
-}
-
-static const struct wl_data_offer_listener data_offer_listener = {
-    data_offer_handle_offer,
-};
-
-static void data_device_handle_data_offer(void *data,
-                                          struct wl_data_device *wl_data_device,
-                                          struct wl_data_offer *id)
-{
-    struct vo_wayland_state *wl = data;
-    if (wl->input.offer) {
-        MP_DBG(wl, "There is already a dnd entry point.\n");
-        wl_data_offer_destroy(wl->input.offer);
-    }
-
-    wl->input.offer = id;
-    wl_data_offer_add_listener(id, &data_offer_listener, wl);
-}
-
-static void data_device_handle_enter(void *data,
-                                     struct wl_data_device *wl_data_device,
-                                     uint32_t serial,
-                                     struct wl_surface *surface,
-                                     wl_fixed_t x,
-                                     wl_fixed_t y,
-                                     struct wl_data_offer *id)
-{
-    struct vo_wayland_state *wl = data;
-    if (wl->input.offer != id)
-        MP_FATAL(wl, "Fatal dnd error (Please report this issue)\n");
-
-    wl_data_offer_accept(id, serial, "text/uri-list");
-}
-
-static void data_device_handle_leave(void *data,
-                                     struct wl_data_device *wl_data_device)
-{
-    struct vo_wayland_state *wl = data;
-    if (wl->input.offer) {
-        wl_data_offer_destroy(wl->input.offer);
-        wl->input.offer = NULL;
-    }
-    // dnd fd is closed on POLLHUP
-}
-
-static void data_device_handle_motion(void *data,
-                                      struct wl_data_device *wl_data_device,
-                                      uint32_t time,
-                                      wl_fixed_t x,
-                                      wl_fixed_t y)
-{
-}
-
-static void data_device_handle_drop(void *data,
-                                    struct wl_data_device *wl_data_device)
-{
-    struct vo_wayland_state *wl = data;
-
-    int pipefd[2];
-
-    if (pipe(pipefd) == -1) {
-        MP_FATAL(wl, "can't create pipe for dnd communication\n");
-        return;
-    }
-
-    wl->input.dnd_fd = pipefd[0];
-    wl_data_offer_receive(wl->input.offer, "text/uri-list", pipefd[1]);
-    close(pipefd[1]);
-}
-
-static void data_device_handle_selection(void *data,
-                                         struct wl_data_device *wl_data_device,
-                                         struct wl_data_offer *id)
-{
-}
-
-static const struct wl_data_device_listener data_device_listener = {
-    data_device_handle_data_offer,
-    data_device_handle_enter,
-    data_device_handle_leave,
-    data_device_handle_motion,
-    data_device_handle_drop,
-    data_device_handle_selection
-};
-
-static void registry_handle_global (void *data,
-                                    struct wl_registry *reg,
-                                    uint32_t id,
-                                    const char *interface,
-                                    uint32_t version)
+static void registry_handle_global(void *data, struct wl_registry *reg,
+                                   uint32_t id, const char *interface,
+                                   uint32_t version)
 {
     struct vo_wayland_state *wl = data;
 
@@ -653,22 +562,11 @@ static void registry_handle_global (void *data,
         wl_list_insert(&wl->display.output_list, &output->link);
     }
 
-    else if (strcmp(interface, "wl_data_device_manager") == 0) {
-
-        wl->input.devman = wl_registry_bind(reg,
-                                            id,
-                                            &wl_data_device_manager_interface,
-                                            1);
-    }
-
     else if (strcmp(interface, "wl_seat") == 0) {
 
         wl->input.seat = wl_registry_bind(reg, id, &wl_seat_interface, 4);
         wl_seat_add_listener(wl->input.seat, &seat_listener, wl);
 
-        wl->input.datadev = wl_data_device_manager_get_data_device(
-                wl->input.devman, wl->input.seat);
-        wl_data_device_add_listener(wl->input.datadev, &data_device_listener, wl);
     }
 
     else if (strcmp(interface, "wl_subcompositor") == 0) {
@@ -678,9 +576,9 @@ static void registry_handle_global (void *data,
     }
 }
 
-static void registry_handle_global_remove (void *data,
-                                           struct wl_registry *registry,
-                                           uint32_t id)
+static void registry_handle_global_remove(void *data,
+                                          struct wl_registry *registry,
+                                          uint32_t id)
 {
 }
 
@@ -767,11 +665,13 @@ static void schedule_resize(struct vo_wayland_state *wl,
 
     MP_DBG(wl, "schedule resize: %dx%d\n", width, height);
 
-    if (width < minimum_size)
-        width = minimum_size;
-
-    if (height < minimum_size)
-        height = minimum_size;
+    width  = MPMAX(minimum_size,  width);
+    height = MPMAX(minimum_size, height);
+    if (wl->display.current_output) {
+        int scale = wl->display.current_output->scale;
+        width  = MPMIN(width,  wl->display.current_output->width /scale);
+        height = MPMIN(height, wl->display.current_output->height/scale);
+    }
 
     // don't keep the aspect ration in fullscreen mode, because the compositor
     // shows the desktop in the border regions if the video has not the same
@@ -807,9 +707,7 @@ static void schedule_resize(struct vo_wayland_state *wl,
     wl->window.sh_height = height;
     wl->window.sh_x = x;
     wl->window.sh_y = y;
-    wl->window.events |= VO_EVENT_WIN_STATE | VO_EVENT_RESIZE;
-    wl->vo->dwidth = width;
-    wl->vo->dheight = height;
+    wl->window.events |= VO_EVENT_RESIZE;
 }
 
 static void frame_callback(void *data,
@@ -833,17 +731,13 @@ static void frame_callback(void *data,
 
     wl_callback_add_listener(wl->frame.callback, &frame_listener, wl);
     wl_surface_commit(wl->window.video_surface);
-
-    wl->frame.last_us = mp_time_us();
-    wl->frame.pending = true;
-    wl->frame.dropping = false;
 }
 
 static const struct wl_callback_listener frame_listener = {
     frame_callback
 };
 
-static bool create_display (struct vo_wayland_state *wl)
+static bool create_display(struct vo_wayland_state *wl)
 {
     if (wl->vo->probing && !getenv("XDG_RUNTIME_DIR"))
         return false;
@@ -868,7 +762,7 @@ static bool create_display (struct vo_wayland_state *wl)
     return true;
 }
 
-static void destroy_display (struct vo_wayland_state *wl)
+static void destroy_display(struct vo_wayland_state *wl)
 {
     struct vo_wayland_output *output = NULL;
     struct vo_wayland_output *tmp = NULL;
@@ -902,7 +796,7 @@ static void destroy_display (struct vo_wayland_state *wl)
     }
 }
 
-static bool create_window (struct vo_wayland_state *wl)
+static bool create_window(struct vo_wayland_state *wl)
 {
     wl->window.video_surface =
         wl_compositor_create_surface(wl->display.compositor);
@@ -929,7 +823,7 @@ static bool create_window (struct vo_wayland_state *wl)
     return true;
 }
 
-static void destroy_window (struct vo_wayland_state *wl)
+static void destroy_window(struct vo_wayland_state *wl)
 {
     if (wl->window.shell_surface)
         wl_shell_surface_destroy(wl->window.shell_surface);
@@ -941,7 +835,7 @@ static void destroy_window (struct vo_wayland_state *wl)
         wl_callback_destroy(wl->frame.callback);
 }
 
-static bool create_cursor (struct vo_wayland_state *wl)
+static bool create_cursor(struct vo_wayland_state *wl)
 {
     if (!wl->display.shm) {
         MP_ERR(wl->vo, "no shm interface available\n");
@@ -961,7 +855,7 @@ static bool create_cursor (struct vo_wayland_state *wl)
     return true;
 }
 
-static void destroy_cursor (struct vo_wayland_state *wl)
+static void destroy_cursor(struct vo_wayland_state *wl)
 {
     if (wl->cursor.theme)
         wl_cursor_theme_destroy(wl->cursor.theme);
@@ -970,7 +864,7 @@ static void destroy_cursor (struct vo_wayland_state *wl)
         wl_surface_destroy(wl->cursor.surface);
 }
 
-static bool create_input (struct vo_wayland_state *wl)
+static bool create_input(struct vo_wayland_state *wl)
 {
     wl->input.xkb.context = xkb_context_new(0);
 
@@ -979,12 +873,10 @@ static bool create_input (struct vo_wayland_state *wl)
         return false;
     }
 
-    wl->input.dnd_fd = -1;
-
     return true;
 }
 
-static void destroy_input (struct vo_wayland_state *wl)
+static void destroy_input(struct vo_wayland_state *wl)
 {
     if (wl->input.keyboard) {
         wl_keyboard_destroy(wl->input.keyboard);
@@ -998,24 +890,21 @@ static void destroy_input (struct vo_wayland_state *wl)
     if (wl->input.pointer)
         wl_pointer_destroy(wl->input.pointer);
 
-    if (wl->input.datadev)
-        wl_data_device_destroy(wl->input.datadev);
-
-    if (wl->input.devman)
-        wl_data_device_manager_destroy(wl->input.devman);
-
     if (wl->input.seat)
         wl_seat_destroy(wl->input.seat);
 }
 
 /*** mplayer2 interface ***/
 
-int vo_wayland_init (struct vo *vo)
+int vo_wayland_init(struct vo *vo)
 {
     vo->wayland = talloc_zero(NULL, struct vo_wayland_state);
     struct vo_wayland_state *wl = vo->wayland;
-    wl->vo = vo;
-    wl->log = mp_log_new(wl, vo->log, "wayland");
+    *wl = (struct vo_wayland_state){
+        .vo = vo,
+        .log = mp_log_new(wl, vo->log, "wayland"),
+        .wakeup_pipe = {-1, -1},
+    };
 
     wl_list_init(&wl->display.output_list);
 
@@ -1045,23 +934,25 @@ int vo_wayland_init (struct vo *vo)
                        o->refresh_rate / 1000.0f);
     }
 
-    vo->event_fd = wl->display.display_fd;
+    mp_make_wakeup_pipe(wl->wakeup_pipe);
 
     return true;
 }
 
-void vo_wayland_uninit (struct vo *vo)
+void vo_wayland_uninit(struct vo *vo)
 {
     struct vo_wayland_state *wl = vo->wayland;
     destroy_cursor(wl);
     destroy_window(wl);
     destroy_display(wl);
     destroy_input(wl);
+    for (int n = 0; n < 2; n++)
+        close(wl->wakeup_pipe[n]);
     talloc_free(wl);
     vo->wayland = NULL;
 }
 
-static void vo_wayland_ontop (struct vo *vo)
+static void vo_wayland_ontop(struct vo *vo)
 {
     struct vo_wayland_state *wl = vo->wayland;
     MP_DBG(wl, "going ontop\n");
@@ -1070,7 +961,7 @@ static void vo_wayland_ontop (struct vo *vo)
     schedule_resize(wl, 0, wl->window.width, wl->window.height);
 }
 
-static void vo_wayland_fullscreen (struct vo *vo)
+static void vo_wayland_fullscreen(struct vo *vo)
 {
     struct vo_wayland_state *wl = vo->wayland;
     if (!wl->display.shell)
@@ -1083,6 +974,9 @@ static void vo_wayland_fullscreen (struct vo *vo)
         wl->window.is_fullscreen = true;
         wl->window.p_width = wl->window.width;
         wl->window.p_height = wl->window.height;
+        if (wl->display.current_output)
+            schedule_resize(wl, 0, wl->display.current_output->width,
+                            wl->display.current_output->height);
         wl_shell_surface_set_fullscreen(wl->window.shell_surface,
                 WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
                 0, fs_output);
@@ -1096,112 +990,6 @@ static void vo_wayland_fullscreen (struct vo *vo)
     }
 }
 
-static int vo_wayland_poll (struct vo *vo, int timeout_msecs)
-{
-    struct vo_wayland_state *wl = vo->wayland;
-    struct wl_display *dp = wl->display.display;
-
-    wl_display_dispatch_pending(dp);
-    wl_display_flush(dp);
-
-    struct pollfd fd = {
-        wl->display.display_fd,
-        POLLIN | POLLOUT | POLLERR | POLLHUP,
-        0
-    };
-
-    /* wl_display_dispatch is blocking
-     * wl_dipslay_dispatch_pending is non-blocking but does not read from the fd
-     *
-     * when pausing no input events get queued so we have to check if there
-     * are events to read from the file descriptor through poll */
-    int polled;
-    if ((polled = poll(&fd, 1, timeout_msecs)) > 0) {
-        if (fd.revents & POLLERR || fd.revents & POLLHUP) {
-            MP_FATAL(wl, "error occurred on the display fd: "
-                         "closing file descriptor\n");
-            close(wl->display.display_fd);
-            mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN);
-        }
-        if (fd.revents & POLLIN)
-            wl_display_dispatch(dp);
-        if (fd.revents & POLLOUT)
-            wl_display_flush(dp);
-    }
-
-    return polled;
-}
-
-static int vo_wayland_check_events (struct vo *vo)
-{
-    struct vo_wayland_state *wl = vo->wayland;
-
-    vo_wayland_poll(vo, 0);
-
-    /* If drag & drop was ended poll the file descriptor from the offer if
-     * there is data to read.
-     * We only accept the mime type text/uri-list.
-     */
-    if (wl->input.dnd_fd != -1) {
-        struct pollfd fd = {
-            wl->input.dnd_fd,
-            POLLIN | POLLERR | POLLHUP,
-            0
-        };
-
-        if (poll(&fd, 1, 0) > 0) {
-            if (fd.revents & POLLERR) {
-                MP_ERR(wl, "error occurred on the drag&drop fd\n");
-                close(wl->input.dnd_fd);
-                wl->input.dnd_fd = -1;
-            }
-
-            if (fd.revents & POLLIN) {
-                int const to_read = 2048;
-                char *buffer = malloc(to_read);
-                size_t buffer_len = to_read;
-                size_t str_len = 0;
-                int has_read = 0;
-
-                if (!buffer)
-                    goto fail;
-
-                while (0 < (has_read = read(fd.fd, buffer+str_len, to_read))) {
-                    if (buffer_len + to_read < buffer_len) {
-                        MP_ERR(wl, "Integer overflow while reading from fd\n");
-                        break;
-                    }
-
-                    str_len += has_read;
-                    buffer_len += to_read;
-                    void *ptr = realloc(buffer, buffer_len);
-                    if (!ptr)
-                        break;
-                    buffer = ptr;
-
-                    if (has_read < to_read) {
-                        buffer[str_len] = 0;
-                        struct bstr file_list = bstr0(buffer);
-                        mp_event_drop_mime_data(vo->input_ctx, "text/uri-list",
-                                                file_list, DND_REPLACE);
-                        break;
-                    }
-                }
-            fail:
-                free(buffer);
-            }
-
-            if (fd.revents & POLLHUP) {
-                close(wl->input.dnd_fd);
-                wl->input.dnd_fd = -1;
-            }
-        }
-    }
-
-    // window events are reset by the resizing code
-    return wl->window.events;
-}
-
 static void vo_wayland_update_screeninfo(struct vo *vo, struct mp_rect *screenrc)
 {
     struct vo_wayland_state *wl = vo->wayland;
@@ -1245,14 +1033,15 @@ static void vo_wayland_update_screeninfo(struct vo *vo, struct mp_rect *screenrc
     wl->window.fs_height = screenrc->y1;
 }
 
-int vo_wayland_control (struct vo *vo, int *events, int request, void *arg)
+int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)
 {
     struct vo_wayland_state *wl = vo->wayland;
     wl_display_dispatch_pending(wl->display.display);
 
     switch (request) {
     case VOCTRL_CHECK_EVENTS:
-        *events |= vo_wayland_check_events(vo);
+        *events |= wl->window.events;
+        wl->window.events = 0;
         return VO_TRUE;
     case VOCTRL_FULLSCREEN:
         vo->opts->fullscreen = !vo->opts->fullscreen;
@@ -1262,9 +1051,11 @@ int vo_wayland_control (struct vo *vo, int *events, int request, void *arg)
         vo_wayland_ontop(vo);
         return VO_TRUE;
     case VOCTRL_GET_UNFS_WINDOW_SIZE: {
-        int *s = arg;
-        s[0] = wl->window.width;
-        s[1] = wl->window.height;
+        int *s = arg, scale = 1;
+        if (wl->display.current_output)
+            scale = wl->display.current_output->scale;
+        s[0] = scale*wl->window.width;
+        s[1] = scale*wl->window.height;
         return VO_TRUE;
     }
     case VOCTRL_SET_UNFS_WINDOW_SIZE: {
@@ -1300,7 +1091,7 @@ int vo_wayland_control (struct vo *vo, int *events, int request, void *arg)
     return VO_NOTIMPL;
 }
 
-bool vo_wayland_config (struct vo *vo)
+bool vo_wayland_config(struct vo *vo)
 {
     struct vo_wayland_state *wl = vo->wayland;
 
@@ -1331,29 +1122,40 @@ void vo_wayland_request_frame(struct vo *vo, void *data, vo_wayland_frame_cb cb)
     frame_callback(wl, NULL, 0);
 }
 
-bool vo_wayland_wait_frame(struct vo *vo)
+void vo_wayland_wakeup(struct vo *vo)
 {
     struct vo_wayland_state *wl = vo->wayland;
+    (void)write(wl->wakeup_pipe[1], &(char){0}, 1);
+}
 
-    if (!wl->frame.callback || wl->frame.dropping)
-        return false;
+void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us)
+{
+    struct vo_wayland_state *wl = vo->wayland;
+    struct wl_display *dp = wl->display.display;
 
-    // If mpv isn't receiving frame callbacks (for 100ms), this usually means that
-    // mpv window is not visible and compositor tells kindly to not draw anything.
-    while (!wl->frame.pending) {
-        int64_t timeout = wl->frame.last_us + (100 * 1000) - mp_time_us();
+    struct pollfd fds[2] = {
+        {.fd = wl->display.display_fd, .events = POLLIN },
+        {.fd = wl->wakeup_pipe[0],     .events = POLLIN },
+    };
 
-        if (timeout <= 0)
-            break;
+    int64_t wait_us = until_time_us - mp_time_us();
+    int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
 
-        if (vo_wayland_poll(vo, timeout) <= 0)
-            break;
+    wl_display_dispatch_pending(dp);
+    wl_display_flush(dp);
+
+    poll(fds, 2, timeout_ms);
+
+    if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
+        MP_FATAL(wl, "error occurred on the display fd: "
+                     "closing file descriptor\n");
+        close(wl->display.display_fd);
+        mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN);
     }
 
-    wl->frame.dropping = !wl->frame.pending;
-    wl->frame.pending = false;
+    if (fds[0].revents & POLLIN)
+        wl_display_dispatch(dp);
 
-    // Return false if the frame callback was not received
-    // Handler should act accordingly.
-    return !wl->frame.dropping;
+    if (fds[1].revents & POLLIN)
+        mp_flush_wakeup_pipe(wl->wakeup_pipe[0]);
 }
diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h
index ec3f72c..4bb90d6 100644
--- a/video/out/wayland_common.h
+++ b/video/out/wayland_common.h
@@ -53,14 +53,12 @@ typedef void (*vo_wayland_frame_cb)(void *data, uint32_t time);
 struct vo_wayland_state {
     struct vo *vo;
     struct mp_log* log;
+    int wakeup_pipe[2];
 
     struct {
         void *data;
         vo_wayland_frame_cb function;
         struct wl_callback *callback;
-        uint64_t last_us;
-        bool pending;
-        bool dropping;
     } frame;
 
 #if HAVE_GL_WAYLAND
@@ -138,11 +136,6 @@ struct vo_wayland_state {
             struct xkb_keymap *keymap;
             struct xkb_state *state;
         } xkb;
-
-        struct wl_data_device_manager *devman;
-        struct wl_data_device *datadev;
-        struct wl_data_offer *offer;
-        int dnd_fd;
     } input;
 };
 
@@ -150,8 +143,9 @@ int vo_wayland_init(struct vo *vo);
 void vo_wayland_uninit(struct vo *vo);
 bool vo_wayland_config(struct vo *vo);
 int vo_wayland_control(struct vo *vo, int *events, int request, void *arg);
+void vo_wayland_wakeup(struct vo *vo);
+void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us);
 void vo_wayland_request_frame(struct vo *vo, void *data, vo_wayland_frame_cb cb);
-bool vo_wayland_wait_frame(struct vo *vo);
 
 #endif /* MPLAYER_WAYLAND_COMMON_H */
 
diff --git a/video/out/x11_common.c b/video/out/x11_common.c
index ce94679..a9d6a2a 100644
--- a/video/out/x11_common.c
+++ b/video/out/x11_common.c
@@ -20,6 +20,8 @@
 #include <math.h>
 #include <inttypes.h>
 #include <limits.h>
+#include <unistd.h>
+#include <poll.h>
 
 #include "config.h"
 #include "misc/bstr.h"
@@ -38,6 +40,7 @@
 #include "vo.h"
 #include "win_state.h"
 #include "osdep/atomics.h"
+#include "osdep/io.h"
 #include "osdep/timer.h"
 #include "osdep/subprocess.h"
 
@@ -540,6 +543,7 @@ int vo_x11_init(struct vo *vo)
         .input_ctx = vo->input_ctx,
         .screensaver_enabled = true,
         .xrandr_event = -1,
+        .wakeup_pipe = {-1, -1},
     };
     vo->x11 = x11;
 
@@ -593,7 +597,8 @@ int vo_x11_init(struct vo *vo)
 
     x11->wm_type = vo_wm_detect(vo);
 
-    vo->event_fd = ConnectionNumber(x11->display);
+    x11->event_fd = ConnectionNumber(x11->display);
+    mp_make_wakeup_pipe(x11->wakeup_pipe);
 
     xrandr_read(x11);
 
@@ -761,6 +766,9 @@ void vo_x11_uninit(struct vo *vo)
         sem_destroy(&x11->screensaver_sem);
     }
 
+    for (int n = 0; n < 2; n++)
+        close(x11->wakeup_pipe[n]);
+
     talloc_free(x11);
     vo->x11 = NULL;
 }
@@ -1011,7 +1019,7 @@ static void vo_x11_check_net_wm_state_fullscreen_change(struct vo *vo)
     }
 }
 
-int vo_x11_check_events(struct vo *vo)
+void vo_x11_check_events(struct vo *vo)
 {
     struct vo_x11_state *x11 = vo->x11;
     Display *display = vo->x11->display;
@@ -1123,6 +1131,7 @@ int vo_x11_check_events(struct vo *vo)
         case MapNotify:
             x11->window_hidden = false;
             x11->pseudo_mapped = true;
+            x11->current_icc_screen = -1;
             vo_x11_update_geometry(vo);
             break;
         case DestroyNotify:
@@ -1169,9 +1178,6 @@ int vo_x11_check_events(struct vo *vo)
     }
 
     update_vo_size(vo);
-    int ret = x11->pending_vo_events;
-    x11->pending_vo_events = 0;
-    return ret;
 }
 
 static void vo_x11_sizehint(struct vo *vo, struct mp_rect rc, bool override_pos)
@@ -1668,6 +1674,23 @@ static bool rc_overlaps(struct mp_rect rc1, struct mp_rect rc2)
     return mp_rect_intersection(&rc1, &rc2); // changes the first argument
 }
 
+// which screen's ICC profile we're going to use
+static int get_icc_screen(struct vo *vo)
+{
+    struct vo_x11_state *x11 = vo->x11;
+    int cx = x11->winrc.x0 + (x11->winrc.x1 - x11->winrc.x0)/2,
+    cy = x11->winrc.y0 + (x11->winrc.y1 - x11->winrc.y0)/2;
+    int screen = 0; // xinerama screen number
+    for (int n = 0; n < x11->num_displays; n++) {
+        struct xrandr_display *disp = &x11->displays[n];
+        if (mp_rect_contains(&disp->rc, cx, cy)) {
+            screen = n;
+            break;
+        }
+    }
+    return screen;
+}
+
 // update x11->winrc with current boundaries of vo->x11->window
 static void vo_x11_update_geometry(struct vo *vo)
 {
@@ -1700,7 +1723,12 @@ static void vo_x11_update_geometry(struct vo *vo)
         MP_VERBOSE(x11, "Current display FPS: %f\n", fps);
     x11->current_display_fps = fps;
     // might have changed displays
-    x11->pending_vo_events |= VO_EVENT_WIN_STATE | VO_EVENT_ICC_PROFILE_CHANGED;
+    x11->pending_vo_events |= VO_EVENT_WIN_STATE;
+    int icc_screen = get_icc_screen(vo);
+    if (x11->current_icc_screen != icc_screen) {
+        x11->current_icc_screen = icc_screen;
+        x11->pending_vo_events |= VO_EVENT_ICC_PROFILE_CHANGED;
+    }
 }
 
 static void vo_x11_fullscreen(struct vo *vo)
@@ -1760,7 +1788,9 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
     struct vo_x11_state *x11 = vo->x11;
     switch (request) {
     case VOCTRL_CHECK_EVENTS:
-        *events |= vo_x11_check_events(vo);
+        vo_x11_check_events(vo);
+        *events |= x11->pending_vo_events;
+        x11->pending_vo_events = 0;
         return VO_TRUE;
     case VOCTRL_FULLSCREEN:
         opts->fullscreen = !opts->fullscreen;
@@ -1840,16 +1870,7 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
     case VOCTRL_GET_ICC_PROFILE: {
         if (!x11->pseudo_mapped)
             return VO_NOTAVAIL;
-        int cx = x11->winrc.x0 + (x11->winrc.x1 - x11->winrc.x0)/2,
-            cy = x11->winrc.y0 + (x11->winrc.y1 - x11->winrc.y0)/2;
-        int screen = 0; // xinerama screen number
-        for (int n = 0; n < x11->num_displays; n++) {
-            struct xrandr_display *disp = &x11->displays[n];
-            if (mp_rect_contains(&disp->rc, cx, cy)) {
-                screen = n;
-                break;
-            }
-        }
+        int screen = get_icc_screen(vo);
         char prop[80];
         snprintf(prop, sizeof(prop), "_ICC_PROFILE");
         if (screen > 0)
@@ -1893,6 +1914,30 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
     return VO_NOTIMPL;
 }
 
+void vo_x11_wakeup(struct vo *vo)
+{
+    struct vo_x11_state *x11 = vo->x11;
+
+    (void)write(x11->wakeup_pipe[1], &(char){0}, 1);
+}
+
+void vo_x11_wait_events(struct vo *vo, int64_t until_time_us)
+{
+    struct vo_x11_state *x11 = vo->x11;
+
+    struct pollfd fds[2] = {
+        { .fd = x11->event_fd, .events = POLLIN },
+        { .fd = x11->wakeup_pipe[0], .events = POLLIN },
+    };
+    int64_t wait_us = until_time_us - mp_time_us();
+    int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
+
+    poll(fds, 2, timeout_ms);
+
+    if (fds[1].revents & POLLIN)
+        mp_flush_wakeup_pipe(x11->wakeup_pipe[0]);
+}
+
 static void xscreensaver_heartbeat(struct vo_x11_state *x11)
 {
     double time = mp_time_sec();
diff --git a/video/out/x11_common.h b/video/out/x11_common.h
index 812c309..213d517 100644
--- a/video/out/x11_common.h
+++ b/video/out/x11_common.h
@@ -45,6 +45,8 @@ struct vo_x11_state {
     struct mp_log *log;
     struct input_ctx *input_ctx;
     Display *display;
+    int event_fd;
+    int wakeup_pipe[2];
     Window window;
     Window rootwin;
     Window parent;  // embedded in this foreign window
@@ -57,6 +59,7 @@ struct vo_x11_state {
 
     struct xrandr_display displays[MAX_DISPLAYS];
     int num_displays;
+    int current_icc_screen;
 
     int xrandr_event;
 
@@ -125,12 +128,14 @@ struct vo_x11_state {
 
 int vo_x11_init(struct vo *vo);
 void vo_x11_uninit(struct vo *vo);
-int vo_x11_check_events(struct vo *vo);
+void vo_x11_check_events(struct vo *vo);
 bool vo_x11_screen_is_composited(struct vo *vo);
 bool vo_x11_create_vo_window(struct vo *vo, XVisualInfo *vis,
                              const char *classname);
 void vo_x11_config_vo_window(struct vo *vo);
 int vo_x11_control(struct vo *vo, int *events, int request, void *arg);
+void vo_x11_wakeup(struct vo *vo);
+void vo_x11_wait_events(struct vo *vo, int64_t until_time_us);
 
 void vo_x11_silence_xlib(int dir);
 
diff --git a/video/vaapi.c b/video/vaapi.c
index f8d0fab..604fffa 100644
--- a/video/vaapi.c
+++ b/video/vaapi.c
@@ -509,7 +509,7 @@ void va_surface_init_subformat(struct mp_image *mpi)
     if (status != VA_STATUS_SUCCESS)
         goto err;
 
-    mpi->params.hw_subfmt = va_image.format.fourcc;
+    mpi->params.hw_subfmt = va_fourcc_to_imgfmt(va_image.format.fourcc);
 
     status = vaDestroyImage(p->display, va_image.image_id);
     CHECK_VA_STATUS(p->ctx, "vaDestroyImage()");
diff --git a/wscript b/wscript
index 9269d95..4c49caf 100644
--- a/wscript
+++ b/wscript
@@ -154,7 +154,7 @@ main_dependencies = [
         'func': check_libs(['atomic'],
             check_statement('stdatomic.h',
                 'atomic_int_least64_t test = ATOMIC_VAR_INIT(123);'
-                'int test2 = atomic_load(&test)'))
+                'atomic_fetch_add(&test, 1)'))
     }, {
         'name': 'atomic-builtins',
         'desc': 'compiler support for __atomic built-ins',
@@ -175,6 +175,7 @@ main_dependencies = [
         'name': 'atomics',
         'desc': 'compiler support for usable thread synchronization built-ins',
         'func': check_true,
+        'req': True,
         'deps_any': ['stdatomic', 'atomic-builtins', 'sync-builtins'],
     }, {
         'name': 'c11-tls',
@@ -517,13 +518,11 @@ audio_output_features = [
     {
         'name': '--sdl2',
         'desc': 'SDL2',
-        'deps': ['atomics'],
         'func': check_pkg_config('sdl2'),
         'default': 'disable'
     }, {
         'name': '--sdl1',
         'desc': 'SDL (1.x)',
-        'deps': ['atomics'],
         'deps_neg': [ 'sdl2' ],
         'func': check_pkg_config('sdl'),
         'default': 'disable'
@@ -574,7 +573,6 @@ audio_output_features = [
     }, {
         'name': '--jack',
         'desc': 'JACK audio output',
-        'deps': ['atomics'],
         'func': check_pkg_config('jack'),
     }, {
         'name': '--openal',
@@ -592,14 +590,13 @@ audio_output_features = [
     }, {
         'name': '--coreaudio',
         'desc': 'CoreAudio audio output',
-        'deps': ['atomics'],
         'func': check_cc(
             fragment=load_fragment('coreaudio.c'),
             framework_name=['CoreFoundation', 'CoreAudio', 'AudioUnit', 'AudioToolbox'])
     }, {
         'name': '--wasapi',
         'desc': 'WASAPI audio output',
-        'deps': ['win32', 'atomics'],
+        'deps': ['win32'],
         'func': check_cc(fragment=load_fragment('wasapi.c')),
     }
 ]
@@ -942,6 +939,7 @@ _INSTALL_DIRS_LIST = [
     ('datadir', '${PREFIX}/share',    'data files'),
     ('mandir',  '${DATADIR}/man',     'man pages '),
     ('docdir',  '${DATADIR}/doc/mpv', 'documentation files'),
+    ('htmldir', '${DOCDIR}',          'html documentation files'),
     ('zshdir',  '${DATADIR}/zsh/site-functions', 'zsh completion functions'),
 
     ('confloaddir', '${CONFDIR}', 'configuration files load directory'),
diff --git a/wscript_build.py b/wscript_build.py
index 8f2b85c..9f30e74 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -19,7 +19,7 @@ def _build_html(ctx):
         target       = 'DOCS/man/mpv.html',
         source       = 'DOCS/man/mpv.rst',
         rule         = '${RST2HTML} ${SRC} ${TGT}',
-        install_path = ctx.env.DOCDIR)
+        install_path = ctx.env.HTMLDIR)
 
     _add_rst_manual_dependencies(ctx)
 
@@ -105,13 +105,11 @@ def build(ctx):
         ( "audio/chmap_sel.c" ),
         ( "audio/fmt-conversion.c" ),
         ( "audio/format.c" ),
-        ( "audio/mixer.c" ),
         ( "audio/decode/ad_lavc.c" ),
         ( "audio/decode/ad_spdif.c" ),
         ( "audio/decode/dec_audio.c" ),
         ( "audio/filter/af.c" ),
         ( "audio/filter/af_channels.c" ),
-        ( "audio/filter/af_delay.c" ),
         ( "audio/filter/af_drc.c" ),
         ( "audio/filter/af_equalizer.c" ),
         ( "audio/filter/af_format.c" ),
@@ -248,6 +246,7 @@ def build(ctx):
         ( "stream/stream_dvdnav.c",              "dvdnav" ),
         ( "stream/stream_edl.c" ),
         ( "stream/stream_file.c" ),
+        ( "stream/stream_cb.c" ),
         ( "stream/stream_lavf.c" ),
         ( "stream/stream_libarchive.c",          "libarchive" ),
         ( "stream/stream_memory.c" ),
@@ -514,7 +513,13 @@ def build(ctx):
                 "install_path": ctx.env.LIBDIR,
             }
 
-            if not ctx.dependency_satisfied('android'):
+            if shared and ctx.dependency_satisfied('android'):
+                # for Android we just add the linker flag without version
+                # as we still need the SONAME for proper linkage.
+                # (LINKFLAGS logic taken from waf's apply_vnum in ccroot.py)
+                v=ctx.env.SONAME_ST%'libmpv.so'
+                ctx.env.append_value('LINKFLAGS',v.split())
+            else:
                 # for all other configurations we want SONAME to be used
                 libmpv_kwargs["vnum"] = libversion
 
@@ -543,7 +548,7 @@ def build(ctx):
             PRIV_LIBS    = get_deps(),
         )
 
-        headers = ["client.h", "qthelper.hpp", "opengl_cb.h"]
+        headers = ["client.h", "qthelper.hpp", "opengl_cb.h", "stream_cb.h"]
         for f in headers:
             ctx.install_as(ctx.env.INCDIR + '/mpv/' + f, 'libmpv/' + f)
 

-- 
mpv packaging



More information about the pkg-multimedia-commits mailing list