summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-11-09 20:54:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-11-09 20:54:03 +0000
commitd7ed6f132228d867bde4a76eff3200a9c75557ba (patch)
tree1cd36f31a5b30a62ce593a0b6c294a06f1750437
parentReleasing progress-linux version 0.33.1-1~progress6+u1. (diff)
downloadmpv-d7ed6f132228d867bde4a76eff3200a9c75557ba.tar.xz
mpv-d7ed6f132228d867bde4a76eff3200a9c75557ba.zip
Merging upstream version 0.34.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.editorconfig10
-rw-r--r--.github/ISSUE_TEMPLATE/2_bug_report_linux.md2
-rw-r--r--.github/ISSUE_TEMPLATE/2_bug_report_macos.md2
-rw-r--r--.github/ISSUE_TEMPLATE/2_bug_report_windows.md2
-rw-r--r--.github/ISSUE_TEMPLATE/3_bug_report.md2
-rw-r--r--.github/ISSUE_TEMPLATE/4_bug_report_build.md2
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml8
-rw-r--r--.github/workflows/build.yml155
-rw-r--r--.travis.yml177
-rw-r--r--DOCS/compile-windows.md4
-rw-r--r--DOCS/contribute.md7
-rw-r--r--DOCS/edl-mpv.rst20
-rw-r--r--DOCS/interface-changes.rst24
-rw-r--r--DOCS/man/af.rst2
-rw-r--r--DOCS/man/ao.rst13
-rw-r--r--DOCS/man/console.rst78
-rw-r--r--DOCS/man/input.rst192
-rw-r--r--DOCS/man/ipc.rst2
-rw-r--r--DOCS/man/javascript.rst25
-rw-r--r--DOCS/man/libmpv.rst10
-rw-r--r--DOCS/man/lua.rst18
-rw-r--r--DOCS/man/mpv.rst53
-rw-r--r--DOCS/man/options.rst353
-rw-r--r--DOCS/man/osc.rst21
-rw-r--r--DOCS/man/stats.rst38
-rw-r--r--DOCS/man/vf.rst10
-rw-r--r--DOCS/man/vo.rst41
-rw-r--r--DOCS/mplayer-changes.rst2
-rw-r--r--DOCS/waf-buildsystem.rst2
-rw-r--r--LICENSE.GPL25
-rw-r--r--LICENSE.LGPL18
-rw-r--r--README.md24
-rw-r--r--RELEASE_NOTES196
-rwxr-xr-xTOOLS/appveyor-install.sh22
-rwxr-xr-xTOOLS/dylib-unhell.py2
-rw-r--r--TOOLS/lua/autocrop.lua29
-rw-r--r--TOOLS/lua/autoload.lua8
-rwxr-xr-xTOOLS/matroska.py8
-rwxr-xr-xTOOLS/osxbundle.py3
-rw-r--r--TOOLS/osxbundle/mpv.app/Contents/Info.plist5
-rwxr-xr-xTOOLS/travis-rebuild-website2
-rwxr-xr-xTOOLS/umpv4
-rw-r--r--VERSION2
-rw-r--r--appveyor.yml16
-rw-r--r--audio/aframe.c18
-rw-r--r--audio/decode/ad_lavc.c2
-rw-r--r--audio/decode/ad_spdif.c2
-rw-r--r--audio/filter/af_lavcac3enc.c2
-rw-r--r--audio/filter/af_scaletempo2_internals.c97
-rw-r--r--audio/out/ao.c4
-rw-r--r--audio/out/ao_audiotrack.c148
-rw-r--r--audio/out/ao_oss.c410
-rw-r--r--audio/out/ao_pulse.c25
-rwxr-xr-xbootstrap.py11
-rwxr-xr-xci/build-mingw64.sh6
-rw-r--r--common/av_common.c23
-rw-r--r--common/encode_lavc.c6
-rw-r--r--common/encode_lavc.h4
-rw-r--r--common/msg.c4
-rw-r--r--common/recorder.c69
-rw-r--r--common/recorder.h5
-rw-r--r--demux/demux.c47
-rw-r--r--demux/demux.h4
-rw-r--r--demux/demux_edl.c17
-rw-r--r--demux/demux_lavf.c35
-rw-r--r--demux/demux_mf.c8
-rw-r--r--demux/demux_mkv.c5
-rw-r--r--demux/demux_playlist.c2
-rw-r--r--demux/demux_timeline.c1
-rw-r--r--demux/stheader.h1
-rw-r--r--etc/_mpv.zsh8
-rw-r--r--etc/input.conf205
-rw-r--r--etc/mpv.bash-completion7
-rw-r--r--etc/mpv.desktop1
-rw-r--r--filters/f_auto_filters.c4
-rw-r--r--filters/f_auto_filters.h2
-rw-r--r--filters/f_decoder_wrapper.c19
-rw-r--r--input/cmd.c31
-rw-r--r--input/input.c5
-rw-r--r--options/m_config_frontend.c32
-rw-r--r--options/m_config_frontend.h9
-rw-r--r--options/m_option.c36
-rw-r--r--options/m_option.h40
-rw-r--r--options/options.c67
-rw-r--r--options/options.h8
-rw-r--r--osdep/macos/libmpv_helper.swift68
-rw-r--r--osdep/macos/mpv_helper.swift36
-rw-r--r--osdep/macos/swift_compat.swift27
-rw-r--r--osdep/macosx_application.h6
-rw-r--r--osdep/macosx_application.m2
-rw-r--r--osdep/macosx_menubar.m7
-rw-r--r--osdep/macosx_touchbar.h1
-rw-r--r--osdep/macosx_touchbar.m248
-rw-r--r--osdep/terminal-unix.c24
-rw-r--r--osdep/timer-win2.c61
-rw-r--r--osdep/timer.h8
-rw-r--r--osdep/win32/pthread.c4
-rw-r--r--player/audio.c33
-rw-r--r--player/command.c227
-rw-r--r--player/configfiles.c88
-rw-r--r--player/core.h9
-rw-r--r--player/external_files.c25
-rw-r--r--player/javascript.c87
-rw-r--r--player/javascript/defaults.js36
-rw-r--r--player/loadfile.c54
-rw-r--r--player/lua.c76
-rw-r--r--player/lua/auto_profiles.lua16
-rw-r--r--player/lua/console.lua48
-rw-r--r--player/lua/defaults.lua33
-rw-r--r--player/lua/options.lua6
-rw-r--r--player/lua/osc.lua118
-rw-r--r--player/lua/stats.lua269
-rw-r--r--player/lua/ytdl_hook.lua119
-rw-r--r--player/main.c13
-rw-r--r--player/playloop.c4
-rw-r--r--player/scripting.c5
-rw-r--r--player/sub.c10
-rw-r--r--player/video.c2
-rw-r--r--stream/dvbin.h2
-rw-r--r--stream/stream.h2
-rw-r--r--stream/stream_concat.c2
-rw-r--r--stream/stream_file.c13
-rw-r--r--stream/stream_lavf.c14
-rw-r--r--stream/stream_memory.c2
-rw-r--r--stream/stream_mf.c1
-rw-r--r--stream/stream_slice.c7
-rw-r--r--sub/dec_sub.c10
-rw-r--r--sub/dec_sub.h4
-rw-r--r--sub/filter_jsre.c137
-rw-r--r--sub/filter_regex.c28
-rw-r--r--sub/filter_sdh.c79
-rw-r--r--sub/lavc_conv.c2
-rw-r--r--sub/osd.c5
-rw-r--r--sub/osd_libass.c24
-rw-r--r--sub/sd.h17
-rw-r--r--sub/sd_ass.c37
-rw-r--r--sub/sd_lavc.c2
-rw-r--r--test/subtimes.js8
-rwxr-xr-xversion.sh2
-rw-r--r--video/csputils.c11
-rw-r--r--video/csputils.h1
-rw-r--r--video/decode/vd_lavc.c48
-rw-r--r--video/filter/vf_sub.c9
-rw-r--r--video/image_writer.c6
-rw-r--r--video/out/cocoa_cb_common.swift7
-rw-r--r--video/out/d3d11/context.c7
-rw-r--r--video/out/drm_atomic.c2
-rw-r--r--video/out/drm_common.c218
-rw-r--r--video/out/drm_common.h6
-rw-r--r--video/out/filter_kernels.c2
-rw-r--r--video/out/gpu/context.c42
-rw-r--r--video/out/gpu/context.h8
-rw-r--r--video/out/gpu/hwdec.c3
-rw-r--r--video/out/gpu/hwdec.h2
-rw-r--r--video/out/gpu/lcms.c84
-rw-r--r--video/out/gpu/shader_cache.c22
-rw-r--r--video/out/gpu/shader_cache.h4
-rw-r--r--video/out/gpu/video.c68
-rw-r--r--video/out/gpu/video_shaders.c88
-rw-r--r--video/out/hwdec/hwdec_cuda_vk.c2
-rw-r--r--video/out/hwdec/hwdec_vaapi.c2
-rw-r--r--video/out/hwdec/hwdec_vaapi.h2
-rw-r--r--video/out/hwdec/hwdec_vaapi_gl.c2
-rw-r--r--video/out/hwdec/hwdec_vaapi_vk.c19
-rw-r--r--video/out/mac/common.swift127
-rw-r--r--video/out/mac/title_bar.swift4
-rw-r--r--video/out/mac/window.swift49
-rw-r--r--video/out/opengl/common.c1
-rw-r--r--video/out/opengl/context.c43
-rw-r--r--video/out/opengl/context.h14
-rw-r--r--video/out/opengl/context_drm_egl.c41
-rw-r--r--video/out/opengl/context_glx.c115
-rw-r--r--video/out/opengl/context_wayland.c95
-rw-r--r--video/out/opengl/egl_helpers.c107
-rw-r--r--video/out/placebo/ra_pl.c125
-rw-r--r--video/out/placebo/utils.h1
-rw-r--r--video/out/vo.h1
-rw-r--r--video/out/vo_drm.c27
-rw-r--r--video/out/vo_gpu.c12
-rw-r--r--video/out/vo_rpi.c31
-rw-r--r--video/out/vo_sixel.c215
-rw-r--r--video/out/vo_tct.c89
-rw-r--r--video/out/vo_vdpau.c12
-rw-r--r--video/out/vo_wlshm.c52
-rw-r--r--video/out/vulkan/context.c21
-rw-r--r--video/out/vulkan/context.h3
-rw-r--r--video/out/vulkan/context_display.c395
-rw-r--r--video/out/vulkan/context_wayland.c90
-rw-r--r--video/out/w32_common.c127
-rw-r--r--video/out/wayland_common.c1949
-rw-r--r--video/out/wayland_common.h137
-rw-r--r--video/out/win_state.c23
-rw-r--r--video/out/win_state.h3
-rw-r--r--video/out/x11_common.c54
-rw-r--r--video/out/x11_common.h4
-rw-r--r--video/zimg.c1
-rw-r--r--waftools/checks/custom.py41
-rw-r--r--waftools/features.py1
-rw-r--r--waftools/inflector.py4
-rw-r--r--wscript39
-rw-r--r--wscript_build.py3
201 files changed, 6516 insertions, 3442 deletions
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f16717b
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,10 @@
+# To use this config on you editor, follow the instructions at:
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+insert_final_newline = true
+indent_style = space
+indent_size = 4
diff --git a/.github/ISSUE_TEMPLATE/2_bug_report_linux.md b/.github/ISSUE_TEMPLATE/2_bug_report_linux.md
index 46a5bf9..6faaf85 100644
--- a/.github/ISSUE_TEMPLATE/2_bug_report_linux.md
+++ b/.github/ISSUE_TEMPLATE/2_bug_report_linux.md
@@ -28,7 +28,7 @@ with --no-config try to first find out which option or script causes your issue.
Describe the reproduction steps as precise as possible. It's very likely that
the bug you experience wasn't reproduced by the developer because the workflow
-differes from your own.
+differs from your own.
### Expected behavior
diff --git a/.github/ISSUE_TEMPLATE/2_bug_report_macos.md b/.github/ISSUE_TEMPLATE/2_bug_report_macos.md
index 43620a3..809ea39 100644
--- a/.github/ISSUE_TEMPLATE/2_bug_report_macos.md
+++ b/.github/ISSUE_TEMPLATE/2_bug_report_macos.md
@@ -26,7 +26,7 @@ with --no-config try to first find out which option or script causes your issue.
Describe the reproduction steps as precise as possible. It's very likely that
the bug you experience wasn't reproduced by the developer because the workflow
-differes from your own.
+differs from your own.
### Expected behavior
diff --git a/.github/ISSUE_TEMPLATE/2_bug_report_windows.md b/.github/ISSUE_TEMPLATE/2_bug_report_windows.md
index cd19438..b42c385 100644
--- a/.github/ISSUE_TEMPLATE/2_bug_report_windows.md
+++ b/.github/ISSUE_TEMPLATE/2_bug_report_windows.md
@@ -26,7 +26,7 @@ with --no-config try to first find out which option or script causes your issue.
Describe the reproduction steps as precise as possible. It's very likely that
the bug you experience wasn't reproduced by the developer because the workflow
-differes from your own.
+differs from your own.
### Expected behavior
diff --git a/.github/ISSUE_TEMPLATE/3_bug_report.md b/.github/ISSUE_TEMPLATE/3_bug_report.md
index b42c254..60df58d 100644
--- a/.github/ISSUE_TEMPLATE/3_bug_report.md
+++ b/.github/ISSUE_TEMPLATE/3_bug_report.md
@@ -24,7 +24,7 @@ with --no-config try to first find out which option or script causes your issue.
Describe the reproduction steps as precise as possible. It's very likely that
the bug you experience wasn't reproduced by the developer because the workflow
-differes from your own.
+differs from your own.
### Expected behavior
diff --git a/.github/ISSUE_TEMPLATE/4_bug_report_build.md b/.github/ISSUE_TEMPLATE/4_bug_report_build.md
index 8fae34c..28f087e 100644
--- a/.github/ISSUE_TEMPLATE/4_bug_report_build.md
+++ b/.github/ISSUE_TEMPLATE/4_bug_report_build.md
@@ -16,7 +16,7 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases
Describe the reproduction steps as precise as possible. It's very likely that
the bug you experience wasn't reproduced by the developer because the workflow
-differes from your own.
+differs from your own.
### Expected behavior
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 163937a..536ac9c 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,9 +1,9 @@
blank_issues_enabled: false
contact_links:
- - name: mpv irc channel
+ - name: mpv IRC channel
url: https://mpv.io/community/
- about: Feel free to ask questions here irc://irc.freenode.net/mpv
- - name: mpv irc developer channel
+ about: Feel free to ask questions here irc://irc.libera.chat/mpv
+ - name: mpv IRC developer channel
url: https://mpv.io/community/
about: Ask questions related to the development of mpv here
- irc://irc.freenode.net/mpv-devel
+ irc://irc.libera.chat/mpv-devel
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..d6c1869
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,155 @@
+name: build
+
+on:
+ push:
+ branches:
+ - master
+ - ci
+ - 'release/**'
+ pull_request:
+ branches: [master]
+
+jobs:
+ mingw:
+ runs-on: ubuntu-20.04
+ strategy:
+ matrix:
+ target: [i686-w64-mingw32, x86_64-w64-mingw32]
+ steps:
+ - uses: actions/checkout@v2
+
+ # Increase -N suffix here to force full rebuild after changes
+ - uses: actions/cache@v2
+ with:
+ path: mingw_prefix/
+ key: "${{ matrix.target }}-1"
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y autoconf automake pkg-config g++-mingw-w64 gcc-multilib nasm yasm
+ ./bootstrap.py
+
+ - name: Build
+ run: |
+ ./ci/build-mingw64.sh
+ env:
+ TARGET: ${{ matrix.target }}
+
+ - name: Print configure log
+ if: ${{ failure() }}
+ run: |
+ cat ./build/config.log
+
+ macos:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ cc:
+ - "clang"
+ os:
+ - "macos-10.15"
+ - "macos-11"
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Override Xcode 13.0 if it's the default toolchain
+ run: |
+ XCODE_PATH="$(xcode-select -p)"
+ case "${XCODE_PATH}" in
+ *Xcode_13.0*)
+ sudo xcode-select -s "/Applications/Xcode_13.1.app"
+ echo "Updated Xcode path ${XCODE_PATH} -> $(xcode-select -p)"
+ ;;
+ esac
+
+ - name: Install dependencies
+ run: |
+ brew update
+ brew install autoconf automake pkg-config libtool python freetype fribidi little-cms2 luajit libass ffmpeg
+
+ - name: Build
+ run: |
+ ./ci/build-macos.sh
+ env:
+ CC: "${{ matrix.cc }}"
+ TRAVIS_OS_NAME: "${{ matrix.os }}"
+
+ - name: Print configure log
+ if: ${{ failure() }}
+ run: |
+ cat ./build/config.log
+
+ linux:
+ runs-on: "ubuntu-20.04"
+ container:
+ image: "registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps"
+ # Disable seccomp until a container manager in GitHub recognizes
+ # clone3() syscall,
+ # <https://github.com/actions/virtual-environments/issues/3812>.
+ options: --security-opt seccomp=unconfined
+ env:
+ CC: "${{ matrix.cc }}"
+ strategy:
+ matrix:
+ cc:
+ - "gcc"
+ - "clang"
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install dependencies
+ run: |
+ ./bootstrap.py
+
+ - name: Build
+ run: |
+ ./ci/build-tumbleweed.sh
+
+ - name: Print configure log
+ if: ${{ failure() }}
+ run: |
+ cat ./build/config.log
+
+ freebsd:
+ runs-on: macos-10.15 # until https://github.com/actions/runner/issues/385
+ steps:
+ - uses: actions/checkout@v2
+ - name: Test in FreeBSD VM
+ uses: vmactions/freebsd-vm@v0.1.5 # aka FreeBSD 13.0
+ with:
+ usesh: true
+ prepare: |
+ # Requested in ci/build-freebsd.sh
+ pkg install -y \
+ evdev-proto \
+ ffmpeg \
+ libplacebo \
+ libxkbcommon \
+ luajit \
+ openal-soft \
+ pkgconf \
+ python3 \
+ sdl2 \
+ vulkan-headers \
+ wayland-protocols \
+ #
+ # Optionally auto-enabled
+ pkg install -y \
+ alsa-lib \
+ jackit \
+ libXv \
+ libarchive \
+ libbluray \
+ libcaca \
+ libcdio-paranoia \
+ libdvdnav \
+ mujs \
+ pulseaudio \
+ rubberband \
+ sekrit-twc-zimg \
+ uchardet \
+ v4l_compat \
+ #
+ run: |
+ ./ci/build-freebsd.sh
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index dcffe94..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,177 +0,0 @@
-language: c
-
-_macbase:
- - &mac
- os: osx
- compiler: clang
- env:
- - HOMEBREW_NO_AUTO_UPDATE=1
- - HOMEBREW_NO_INSTALL_CLEANUP=1
- before_cache:
- - brew cleanup -s
- cache:
- directories:
- - $HOME/Library/Caches/Homebrew
- - /usr/local/Homebrew
-
-_mingwbase:
- - &mingw
- os: linux
- addons:
- apt:
- packages:
- - 'autoconf'
- - 'automake'
- - 'pkg-config'
- - 'gcc-mingw-w64'
- - 'gcc-multilib'
- - 'nasm'
- - 'yasm'
- cache:
- directories:
- - mingw_prefix/
-
-matrix:
- include:
- - <<: *mac
- osx_image: xcode11.6
- - <<: *mac
- osx_image: xcode10.1
- - <<: *mac
- osx_image: xcode9.2
- env:
- - HOMEBREW_NO_AUTO_UPDATE=1
- - HOMEBREW_NO_INSTALL_CLEANUP=1
- - CI_SWIFT_FLAGS="\-target x86_64-apple-macosx10.12"
- - os: freebsd
- compiler: clang
- - os: linux
- compiler: gcc
- env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
- - os: linux
- compiler: clang
- env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
- - <<: *mingw
- env: CI_SCRIPT=ci/build-mingw64.sh TARGET=i686-w64-mingw32
- - <<: *mingw
- env: CI_SCRIPT=ci/build-mingw64.sh TARGET=x86_64-w64-mingw32
- allow_failures:
- - os: osx
- osx_image: xcode9.2
- fast_finish: true
-
-dist: focal
-services:
- - docker
-
-env:
- global:
- # Coverity token
- - secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
- # Travis token for mpv.io
- - secure: "nlTVLJK6kRhtXvhKCoJ3YdFGHuKaq/eHowfPw25hqRWuBOZd+HjHY5KIYjV7SxuKFDpJE4GpNcvA3Q31nsqomxpkLYgrwjg6TSazN7ZP+x85ZgV1QGFebrPfGm2n5UR5CAPAwFoeF3pZheLi4bajVzwq1fWW+x3grS188P9OZso="
-
-branches:
- only:
- - master
- - ci
- - coverity_scan
- - /release\/.*$/
-
-before_install:
- - if [ "$TRAVIS_COMPILER" = "clang" ]; then export CXX="clang++"; fi
- - if [ "$TRAVIS_COMPILER" = "gcc" ]; then export CXX="g++"; fi
- - if [ -n "$CONTAINER" ]; then docker pull $CONTAINER; fi
- - |
- if [ "$TRAVIS_OS_NAME" = "freebsd" ]; then
- # Requested in ci/build-freebsd.sh
- sudo pkg install -y \
- evdev-proto \
- ffmpeg \
- libplacebo \
- libxkbcommon \
- luajit \
- openal-soft \
- pkgconf \
- python3 \
- sdl2 \
- vulkan-headers \
- wayland-protocols \
- $NULL
- # Optionally auto-enabled
- sudo pkg install -y \
- alsa-lib \
- jackit \
- libXv \
- libarchive \
- libbluray \
- libcaca \
- libcdio-paranoia \
- libdvdnav \
- mujs \
- pulseaudio \
- rubberband \
- sekrit-twc-zimg \
- uchardet \
- v4l_compat \
- $NULL
- fi
- - |
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
- remove=$(brew list)
- keep="gettext pcre2 git"
- install="autoconf automake pkg-config libtool python freetype fribidi little-cms2 luajit libass ffmpeg"
- for formula in ${keep[@]}; do remove=("${remove[@]/$formula}"); done
- for formula in ${install[@]}; do remove=("${remove[@]/$formula}"); done
- brew remove --force $remove --ignore-dependencies
- if [[ "$TRAVIS_OSX_IMAGE" == "xcode9.2" ]]; then
- brew untap caskroom/cask
- fi
- brew update
- if [[ "$TRAVIS_OSX_IMAGE" == "xcode9.2" ]]; then
- pushd "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core"
- git checkout --force 55e02323812604add9a69bab8730319b9255a697
- fi
- brew install $install
- brew link --overwrite python
- if [[ "$TRAVIS_OSX_IMAGE" == "xcode9.2" ]]; then
- git checkout master
- popd
- fi
- fi
-
-
-
-script:
- - ./bootstrap.py
- - |
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then
- if [ -n "$CONTAINER" ]; then
- docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"
- else
- $CI_SCRIPT
- fi
- fi
- - if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./ci/build-macos.sh; fi
- - if [ "$TRAVIS_OS_NAME" = "freebsd" ]; then ./ci/build-freebsd.sh; fi
-after_failure: cat ./build/config.log
-after_script: TOOLS/travis-rebuild-website
-
-notifications:
- email: false
- irc:
- if: fork = false
- channels:
- - "irc.freenode.org#mpv-devel"
- on_success: change
- on_failure: always
-
-addons:
- coverity_scan:
- project:
- name: "mpv-player/mpv"
- description: "Build submitted via Travis CI"
- notification_email: mpv-team@googlegroups.com
- build_command_prepend: "./bootstrap.py && ./waf configure"
- build_command: "./waf build"
- branch_pattern: coverity_scan
diff --git a/DOCS/compile-windows.md b/DOCS/compile-windows.md
index 2fd0119..58f0dae 100644
--- a/DOCS/compile-windows.md
+++ b/DOCS/compile-windows.md
@@ -22,7 +22,7 @@ When cross-compiling, you have to run mpv's configure with these arguments:
DEST_OS=win32 TARGET=i686-w64-mingw32 ./waf configure
```
-[MXE](http://mxe.cc) makes it very easy to bootstrap a complete MingGW-w64
+[MXE](https://mxe.cc) makes it very easy to bootstrap a complete MingGW-w64
environment from a Linux machine. See a working example below.
Alternatively, you can try [mingw-w64-cmake](https://github.com/lachs0r/mingw-w64-cmake),
@@ -38,7 +38,7 @@ Example with MXE
#
# Refer to
#
-# http://mxe.cc/#requirements
+# https://mxe.cc/#requirements
#
# Scroll down for disto/OS-specific instructions to install them.
diff --git a/DOCS/contribute.md b/DOCS/contribute.md
index cc30978..c33cab1 100644
--- a/DOCS/contribute.md
+++ b/DOCS/contribute.md
@@ -5,7 +5,7 @@ General
-------
The main contact for mpv development is IRC, specifically #mpv
-and #mpv-devel on Freenode. Github is used for code review and
+and #mpv-devel on Libera.chat. Github is used for code review and
long term discussions.
Sending patches
@@ -68,6 +68,11 @@ Write good commit messages
Having a prefix gives context, and is especially useful when trying to find
a specific change by looking at the history, or when running ``git blame``.
+
+ Sample prefixes: ``vo_gpu: ...``, ``command: ...``, ``DOCS/input: ...``,
+ ``TOOLS/osxbundle: ...``, ``osc.lua: ...``, etc. You can always check the git
+ log for commits which modify specific files to see which prefixes are used.
+
- The first word after the ``:`` is lower case.
- Don't end the subject line with a ``.``.
- Put an empty line between the subject line and the commit message.
diff --git a/DOCS/edl-mpv.rst b/DOCS/edl-mpv.rst
index 78da44a..0e4d2b4 100644
--- a/DOCS/edl-mpv.rst
+++ b/DOCS/edl-mpv.rst
@@ -239,6 +239,26 @@ title. The subtitle stream will use ``ducks`` as title.
The ``track_meta`` header is not part of the core EDL format. It may be changed
or removed at any time, depending on mpv's internal requirements.
+Global metadata
+===============
+
+The special ``global_tags`` header can set metadata fields (aka tags) of the EDL
+file. This metadata is supposed to be informational, much like for example ID3
+tags in audio files. Due to lack of separation of different kinds of metadata it
+is unspecified what names are allowed, how they are interpreted, and whether
+some of them affect playback functionally. (Much of this is unfortunately
+inherited from FFmpeg. Another consequence of this is that FFmpeg "normalized"
+tags are recognized, or stuff like replaygain tags.)
+
+Example::
+
+ !global_tags,title=bla,something_arbitrary=even_more_arbitrary
+
+Any parameter names are allowed. Repeated use of this adds to the tag list. If
+``!new_stream`` is used, the location doesn't matter.
+
+May possibly be ignored in some cases, such as delayed media opening.
+
Delayed media opening
=====================
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index 16f570a..84418c5 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -26,6 +26,30 @@ Interface changes
::
+ --- mpv 0.34.0 ---
+ - deprecate selecting by card number with `--drm-connector`, add
+ `--drm-device` which can be used instead
+ - add `--screen-name` and `--fs-screen-name` flags to allow selecting the
+ screen by its name instead of the index
+ - add `--macos-geometry-calculation` to change the rectangle used for screen
+ position and size calculation. the old behavior used the whole screen,
+ which didn't take the menu bar and Dock into account. The new default
+ behaviour includes both. To revert to the old behavior set this to
+ `whole`.
+ - add an additional optional `albumart` argument to the `video-add` command,
+ which tells mpv to load the given video as album art.
+ - undeprecate `--cache-secs` option
+ - remove `--icc-contrast` and introduce `--icc-force-contrast`. The latter
+ defaults to the equivalent of the old `--icc-contrast=inf`, and can
+ instead be used to specifically set the contrast to any value.
+ - add a `--watch-later-options` option to allow configuring which
+ options quit-watch-later saves
+ - make `current-window-scale` writeable and use it in the default input.conf
+ - add `--input-builtin-bindings` flag to control loading of built-in key
+ bindings during start-up (default: yes).
+ - add ``track-list/N/image`` sub-property
+ - remove `--opengl-restrict` option
+ - js custom-init: use filename ~~/init.js instead of ~~/.init.js (dot)
--- mpv 0.33.0 ---
- add `--d3d11-exclusive-fs` flag to enable D3D11 exclusive fullscreen mode
when the player enters fullscreen.
diff --git a/DOCS/man/af.rst b/DOCS/man/af.rst
index 55bf3d0..5ff7426 100644
--- a/DOCS/man/af.rst
+++ b/DOCS/man/af.rst
@@ -198,7 +198,7 @@ Available filters are:
for each option. The options are not documented here, because they are
merely passed to librubberband. Look at the librubberband documentation
to learn what each option does:
- http://breakfastquay.com/rubberband/code-doc/classRubberBand_1_1RubberBandStretcher.html
+ https://breakfastquay.com/rubberband/code-doc/classRubberBand_1_1RubberBandStretcher.html
(The mapping of the mpv rubberband filter sub-option names and values to
those of librubberband follows a simple pattern: ``"Option" + Name + Value``.)
diff --git a/DOCS/man/ao.rst b/DOCS/man/ao.rst
index cc42ec2..1c0b9e1 100644
--- a/DOCS/man/ao.rst
+++ b/DOCS/man/ao.rst
@@ -14,7 +14,7 @@ in the list.
See ``--ao=help`` for a list of compiled-in audio output drivers. The
driver ``--ao=alsa`` is preferred. ``--ao=pulse`` is preferred on systems
- where PulseAudio is used.
+ where PulseAudio is used. On BSD systems, ``--ao=oss`` is preferred.
Available audio output drivers are:
@@ -35,6 +35,9 @@ Available audio output drivers are:
with automatic upmixing with shared access, so playing stereo
and multichannel audio at the same time will work as expected.
+``oss``
+ OSS audio output driver
+
``jack``
JACK (Jack Audio Connection Kit) audio output driver.
@@ -65,8 +68,8 @@ Available audio output drivers are:
mode is probably not very useful, other than for debugging or when used
with fixed setups.
-``coreaudio`` (Mac OS X only)
- Native Mac OS X audio output driver using AudioUnits and the CoreAudio
+``coreaudio`` (macOS only)
+ Native macOS audio output driver using AudioUnits and the CoreAudio
sound server.
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
@@ -92,8 +95,8 @@ Available audio output drivers are:
extreme care.
-``coreaudio_exclusive`` (Mac OS X only)
- Native Mac OS X audio output driver using direct device access and
+``coreaudio_exclusive`` (macOS only)
+ Native macOS audio output driver using direct device access and
exclusive mode (bypasses the sound server).
``openal``
diff --git a/DOCS/man/console.rst b/DOCS/man/console.rst
index e9a829c..f883a14 100644
--- a/DOCS/man/console.rst
+++ b/DOCS/man/console.rst
@@ -14,51 +14,83 @@ Keybindings
ESC
Hide the console.
-ENTER
+ENTER, Ctrl+J and Ctrl+M
Run the typed command.
Shift+ENTER
Type a literal newline character.
-Ctrl+LEFT and Ctrl+RIGHT
- Move cursor to previous/next word.
+LEFT and Ctrl+B
+ Move the cursor to the previous character.
-UP and DOWN
- Navigate command history.
+RIGHT and Ctrl+F
+ Move the cursor to the next character.
+
+Ctrl+LEFT and Alt+B
+ Move the cursor to the beginning of the current word, or if between words,
+ to the beginning of the previous word.
+
+Ctrl+RIGHT and Alt+F
+ Move the cursor to the end of the current word, or if between words, to the
+ end of the next word.
+
+HOME and Ctrl+A
+ Move the cursor to the start of the current line.
+
+END and Ctrl+E
+ Move the cursor to the end of the current line.
+
+BACKSPACE and Ctrl+H
+ Delete the previous character.
+
+Ctrl+D
+ Hide the console if the current line is empty, otherwise delete the next
+ character.
+
+Ctrl+BACKSPACE and Ctrl+W
+ Delete text from the cursor to the beginning of the current word, or if
+ between words, to the beginning of the previous word.
+
+Ctrl+DEL and Alt+D
+ Delete text from the cursor to the end of the current word, or if between
+ words, to the end of the next word.
+
+Ctrl+U
+ Delete text from the cursor to the beginning of the current line.
+
+Ctrl+K
+ Delete text from the cursor to the end of the current line.
+
+Ctrl+C
+ Clear the current line.
+
+UP and Ctrl+P
+ Move back in the command history.
+
+DOWN and Ctrl+N
+ Move forward in the command history.
PGUP
Go to the first command in the history.
PGDN
- Stop navigating command history.
+ Stop navigating the command history.
INSERT
Toggle insert mode.
+Ctrl+V
+ Paste text (uses the clipboard on X11 and Wayland).
+
Shift+INSERT
- Paste text (uses the primary selection on X11).
+ Paste text (uses the primary selection on X11 and Wayland).
-TAB
+TAB and Ctrl+I
Complete the command or property name at the cursor.
-Ctrl+C
- Clear current line.
-
-Ctrl+K
- Delete text from the cursor to the end of the line.
-
Ctrl+L
Clear all log messages from the console.
-Ctrl+U
- Delete text from the cursor to the beginning of the line.
-
-Ctrl+V
- Paste text (uses the clipboard on X11).
-
-Ctrl+W
- Delete text from the cursor to the beginning of the current word.
-
Commands
--------
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index 1bcae75..5a733a5 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -62,8 +62,8 @@ character), or a symbolic name (as printed by ``--input-keylist``).
command.
``<command>`` is the command itself. It consists of the command name and
-multiple (or none) commands, all separated by whitespace. String arguments
-need to be quoted with ``"``. Details see ``Flat command syntax``.
+multiple (or none) arguments, all separated by whitespace. String arguments
+should be quoted, typically with ``"``. See ``Flat command syntax``.
You can bind multiple commands to one key. For example:
@@ -97,6 +97,12 @@ All key names can be combined with the modifiers ``Shift``, ``Ctrl``, ``Alt``,
``Meta``. They must be prefixed to the actual key name, where each modifier
is followed by a ``+`` (for example ``ctrl+q``).
+The ``Shift`` modifier requires some attention. For instance ``Shift+2`` should
+usually be specified as key-name ``@`` at ``input.conf``, and similarly the
+combination ``Alt+Shift+2`` is usually ``Alt+@``, etc. Special key names like
+``Shift+LEFT`` work as expected. If in doubt - use ``--input-test`` to check
+how a key/combination is seen by mpv.
+
Symbolic key names and modifier names are case-insensitive. Unicode key names
are case-sensitive because input bindings typically respect the shift key.
@@ -162,18 +168,36 @@ a number of other places.
|
| ``<command> ::= [<prefixes>] <command_name> (<argument>)*``
-| ``<argument> ::= (<string> | " <quoted_string> ")``
+| ``<argument> ::= (<unquoted> | " <double_quoted> " | ' <single_quoted> ' | `X <custom_quoted> X`)``
``command_name`` is an unquoted string with the command name itself. See
`List of Input Commands`_ for a list.
-Arguments are separated by whitespace. This applies even to string arguments.
-For this reason, string arguments should be quoted with ``"``. If a string
-argument contains spaces or certain special characters, quoting and possibly
-escaping is mandatory, or the command cannot be parsed correctly.
+Arguments are separated by whitespaces even if the command expects only one
+argument. Arguments with whitespaces or other special characters must be quoted,
+or the command cannot be parsed correctly.
+
+Double quotes interpret JSON/C-style escaping, like ``\t`` or ``\"`` or ``\\``.
+JSON escapes according to RFC 8259, minus surrogate pair escapes. This is the
+only form which allows newlines at the value - as ``\n``.
+
+Single quotes take the content literally, and cannot include the single-quote
+character at the value.
+
+Custom quotes also take the content literally, but are more flexible than single
+quotes. They start with ````` (back-quote) followed by any ASCII character,
+and end at the first occurance of the same pair in reverse order, e.g.
+```-foo-``` or ````bar````. The final pair sequence is not allowed at the
+value - in these examples ``-``` and `````` respectively. In the second
+example the last character of the value also can't be a back-quote.
-Inside quotes, C-style escaping can be used. JSON escapes according to RFC 8259,
-minus surrogate pair escapes, should be a safe subset that can be used.
+Mixed quoting at the same argument, like ``'foo'"bar"``, is not supported.
+
+Note that argument parsing and property expansion happen at different stages.
+First, arguments are determined as described above, and then, where applicable,
+properties are expanded - regardless of argument quoting. However, expansion
+can still be prevented with the ``raw`` prefix or ``$>``. See `Input Command
+Prefixes`_ and `Property Expansion`_.
Commands specified as arrays
----------------------------
@@ -196,6 +220,9 @@ quotes and escaping. The array command APIs mentioned above pass strings
directly to the argument parsers, or can sidestep them by the ability to pass
non-string values.
+Property expansion is disabled by default for these APIs. This can be changed
+with the ``expand-properties`` prefix. See `Input Command Prefixes`_.
+
Sometimes commands have string arguments, that in turn are actually parsed by
other components (e.g. filter strings with ``vf add``) - in these cases, you
you would have to double-escape in input.conf, but not with the array APIs.
@@ -222,6 +249,9 @@ to use APIs that pass arguments as arrays.
Named arguments are not supported in the "flat" input.conf syntax, which means
you cannot use them for key bindings in input.conf at all.
+Property expansion is disabled by default for these APIs. This can be changed
+with the ``expand-properties`` prefix. See `Input Command Prefixes`_.
+
List of Input Commands
----------------------
@@ -314,6 +344,10 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
the minimum, on underflow set it to the maximum. If ``up`` or ``down`` is
omitted, assume ``up``.
+ Whether or not key-repeat is enabled by default depends on the property.
+ Currently properties with continuous values are repeatable by default (like
+ ``volume``), while discrete values are not (like ``osd-level``).
+
``multiply <name> <value>``
Similar to ``add``, but multiplies the property or option with the numeric
value.
@@ -440,6 +474,10 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
Stop playback and replace the internal playlist with the new one.
<append>
Append the new playlist at the end of the current internal playlist.
+ <append-play>
+ Append the new playlist, and if nothing is currently playing, start
+ playback. (Always starts with the new playlist, even if the internal
+ playlist was not empty before running this command.)
``playlist-clear``
Clear the playlist, except the currently played file.
@@ -507,9 +545,9 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
argument list.
The first array entry is either an absolute path to the executable, or
- a filename with no path components, in which case the ``PATH``
- environment variable. On Unix, this is equivalent to ``posix_spawnp``
- and ``execvp`` behavior.
+ a filename with no path components, in which case the executable is
+ searched in the directories in the ``PATH`` environment variable. On
+ Unix, this is equivalent to ``posix_spawnp`` and ``execvp`` behavior.
``playback_only`` (``MPV_FORMAT_FLAG``)
Boolean indicating whether the process should be killed when playback
@@ -669,16 +707,30 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
This works by unloading and re-adding the subtitle track.
-``sub-step <skip>``
+``sub-step <skip> <flags>``
Change subtitle timing such, that the subtitle event after the next
``<skip>`` subtitle events is displayed. ``<skip>`` can be negative to step
backwards.
-``sub-seek <skip>``
+ Secondary argument:
+
+ primary (default)
+ Steps through the primary subtitles.
+ secondary
+ Steps through the secondary subtitles.
+
+``sub-seek <skip> <flags>``
Seek to the next (skip set to 1) or the previous (skip set to -1) subtitle.
This is similar to ``sub-step``, except that it seeks video and audio
instead of adjusting the subtitle delay.
+ Secondary argument:
+
+ primary (default)
+ Seeks through the primary subtitles.
+ secondary
+ Seeks through the secondary subtitles.
+
For embedded subtitles (like with Matroska), this works only with subtitle
events that have already been displayed, or are within a short prefetch
range.
@@ -791,8 +843,11 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
``audio-reload [<id>]``
Reload the given audio tracks. See ``sub-reload`` command.
-``video-add <url> [<flags> [<title> [<lang>]]]``
- Load the given video file. See ``sub-add`` command.
+``video-add <url> [<flags> [<title> [<lang> [<albumart>]]]]``
+ Load the given video file. See ``sub-add`` command for common options.
+
+ ``albumart`` (``MPV_FORMAT_FLAG``)
+ If enabled, mpv will load the given video as album art.
``video-remove [<id>]``
Remove the given video track. See ``sub-remove`` command.
@@ -801,9 +856,9 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
Reload the given video tracks. See ``sub-reload`` command.
``rescan-external-files [<mode>]``
- Rescan external files according to the current ``--sub-auto`` and
- ``--audio-file-auto`` settings. This can be used to auto-load external
- files *after* the file was loaded.
+ Rescan external files according to the current ``--sub-auto``,
+ ``--audio-file-auto`` and ``--cover-art-auto`` settings. This can be used
+ to auto-load external files *after* the file was loaded.
The ``mode`` argument is one of the following:
@@ -881,7 +936,7 @@ Input Commands that are Possibly Subject to Change
.. admonition:: Example for input.conf
- - ``a vf set flip`` turn video upside-down on the ``a`` key
+ - ``a vf set vflip`` turn the video upside-down on the ``a`` key
- ``b vf set ""`` remove all video filters on ``b``
- ``c vf toggle gradfun`` toggle debanding on ``c``
@@ -1161,7 +1216,8 @@ Input Commands that are Possibly Subject to Change
``script-message-to <target> [<arg1> [<arg2> [...]]]``
Same as ``script-message``, but send it only to the client named
``<target>``. Each client (scripts etc.) has a unique name. For example,
- Lua scripts can get their name via ``mp.get_script_name()``.
+ Lua scripts can get their name via ``mp.get_script_name()``. Note that
+ client names only consist of alphanumeric characters and ``_``.
This command has a variable number of arguments, and cannot be used with
named arguments.
@@ -1173,7 +1229,8 @@ 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``. Note that script
+ names only consist of alphanumeric characters and ``_``.
For completeness, here is how this command works internally. The details
could change any time. On any matching key event, ``script-message-to``
@@ -1649,7 +1706,9 @@ prefixes can be specified. They are separated by whitespace.
This is the default for ``input.conf`` commands.
``repeatable``
For some commands, keeping a key pressed doesn't run the command repeatedly.
- This prefix forces enabling key repeat in any case.
+ This prefix forces enabling key repeat in any case. For a list of commands:
+ the first command determines the repeatability of the whole list (up to and
+ including version 0.33 - a list was always repeatable).
``async``
Allow asynchronous execution (if possible). Note that only a few commands
will support this (usually this is explicitly documented). Some commands
@@ -1806,6 +1865,9 @@ Property list
.. note:: This is only an estimate. (It's computed from two unreliable
quantities: fps and possibly rounded timestamps.)
+``pid``
+ Process-id of mpv.
+
``path``
Full path of the currently played file. Usually this is exactly the same
string you pass on the mpv command line or the ``loadfile`` command, even
@@ -2469,6 +2531,15 @@ Property list
(or to be exact, the size the video filters output). ``2`` will set the
double size, ``0.5`` halves the size.
+ Note that setting a value identical to its previous value will not resize
+ the window. That's because this property mirrors the ``window-scale``
+ option, and setting an option to its previous value is ignored. If this
+ value is set while the window is in a fullscreen, the multiplier is not
+ applied until the window is taken out of that state. Writing this property
+ to a maximized window can unmaximize the window depending on the OS and
+ window manager. If the window does not unmaximize, the multiplier will be
+ applied if the user unmaximizes the window later.
+
See ``current-window-scale`` for the value derived from the actual window
size.
@@ -2477,15 +2548,21 @@ Property list
Before mpv 0.31.0, this returned what ``current-window-scale`` returns now,
after the window was created.
-``current-window-scale``
+``current-window-scale`` (RW)
The ``window-scale`` value calculated from the current window size. This
has the same value as ``window-scale`` if the window size was not changed
since setting the option, and the window size was not restricted in other
- ways. The property is unavailable if no video is active.
+ ways. If the window is fullscreened, this will return the scale value
+ calculated from the last non-fullscreen size of the window. The property
+ is unavailable if no video is active.
+
+ When setting this property in the fullscreen or maximized state, the behavior
+ is the same as window-scale. In all ther cases, setting the value of this
+ property will always resize the window. This does not affect the value of
+ ``window-scale``.
``focused``
- Whether the window has focus. Currently works only on X11, Wayland and
- macOS.
+ Whether the window has focus. Might not be supported by all VOs.
``display-names``
Names of the displays that the mpv window covers. On X11, these
@@ -2516,6 +2593,11 @@ Property list
``vsync-jitter``
Estimated deviation factor of the vsync duration.
+``display-width``, ``display-height``
+ The current display's horizontal and vertical resolution in pixels. Whether
+ or not these values update as the mpv window changes displays depends on
+ the windowing backend. It may not be available on all platforms.
+
``display-hidpi-scale``
The HiDPI scale factor as reported by the windowing backend. If no VO is
active, or if the VO does not report a value, this property is unavailable.
@@ -2531,8 +2613,8 @@ 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
- different from the window size in some cases.
+ ``overlay-add`` command. It gives you the actual OSD/window size (not
+ including decorations drawn by the OS window manager).
Alias to ``osd-dimensions/w`` and ``osd-dimensions/h``.
@@ -2602,17 +2684,26 @@ Property list
This property is experimental and might be removed in the future.
+``secondary-sub-text``
+ Same as ``sub-text``, but for the secondary subtitles.
+
``sub-start``
The current subtitle start time (in seconds). If there's multiple current
subtitles, returns the first start time. If no current subtitle is present
null is returned instead.
+``secondary-sub-start``
+ Same as ``sub-start``, but for the secondary subtitles.
+
``sub-end``
The current subtitle end time (in seconds). If there's multiple current
subtitles, return the last end time. If no current subtitle is present, or
if it's present but has unknown or incorrect duration, null is returned
instead.
+``secondary-sub-end``
+ Same as ``sub-end``, but for the secondary subtitles.
+
``playlist-pos`` (RW)
Current position on playlist. The first entry is on position 0. Writing to
this property may start playback at the new position.
@@ -2747,10 +2838,15 @@ Property list
``track-list/N/lang``
Track language as identified by the file. Not always available.
- ``track-list/N/albumart``
+ ``track-list/N/image``
``yes``/true if this is a video track that consists of a single
- picture, ``no``/false or unavailable otherwise. This is used for video
- tracks that are really attached pictures in audio files.
+ picture, ``no``/false or unavailable otherwise. The heuristic used to
+ determine if a stream is an image doesn't attempt to detect images in
+ codecs normally used for videos. Otherwise, it is reliable.
+
+ ``track-list/N/albumart``
+ ``yes``/true if this is an image embedded in an audio file or external
+ cover art, ``no``/false or unavailable otherwise.
``track-list/N/default``
``yes``/true if the track has the default flag set in the file,
@@ -2844,6 +2940,7 @@ Property list
"src-id" MPV_FORMAT_INT64
"title" MPV_FORMAT_STRING
"lang" MPV_FORMAT_STRING
+ "image" MPV_FORMAT_FLAG
"albumart" MPV_FORMAT_FLAG
"default" MPV_FORMAT_FLAG
"forced" MPV_FORMAT_FLAG
@@ -2879,15 +2976,8 @@ Property list
For example, ``current-tracks/audio/lang`` returns the current audio track's
language field (the same value as ``track-list/N/lang``).
- A sub-entry is accessible only if a track of that type is actually selected.
- Tracks selected via ``--lavfi-complex`` never appear under this property.
- ``current-tracks`` and ``current-tracks/`` are currently not accessible, and
- will not return anything.
-
- Scripts etc. should not use this. They should use ``track-list``, loop over
- all tracks, and inspect the ``selected`` field to test whether a track is
- selected (or compare the ``id`` field to the ``video`` / ``audio`` etc.
- options).
+ If tracks of the requested type are selected via ``--lavfi-complex``, the
+ first one is returned.
``chapter-list``
List of chapters, current entry marked. Currently, the raw property value
@@ -2971,13 +3061,14 @@ Property list
.. admonition:: Example
- - ``--osd-status-msg='This is ${osd-ass-cc/0}{\\b1}bold text'``
- - ``show-text "This is ${osd-ass-cc/0}{\b1}bold text"``
+ - ``--osd-msg3='This is ${osd-ass-cc/0}{\\b1}bold text'``
+ - ``show-text "This is ${osd-ass-cc/0}{\\b1}bold text"``
Any ASS override tags as understood by libass can be used.
Note that you need to escape the ``\`` character, because the string is
- processed for C escape sequences before passing it to the OSD code.
+ processed for C escape sequences before passing it to the OSD code. See
+ `Flat command syntax`_ for details.
A list of tags can be found here: http://docs.aegisub.org/latest/ASS_Tags/
@@ -3353,7 +3444,7 @@ You can access (almost) all options as properties, though there are some
caveats with some properties (due to historical reasons):
``vid``, ``aid``, ``sid``
- While playback is active, these result the actually active tracks. For
+ While playback is active, these return the actually active tracks. For
example, if you set ``aid=5``, and the currently played file contains no
audio track with ID 5, the ``aid`` property will return ``no``.
@@ -3371,8 +3462,8 @@ caveats with some properties (due to historical reasons):
the initial filter chain cannot be created.
This behavior changed in mpv 0.31.0. Before this, the new value was rejected
- *iff* video (for ``vf``) or audio (for ``af``) was active. If playback was
- not active, the behavior was the same as the current behavior.
+ *iff* a video (for ``vf``) or an audio (for ``af``) track was active. If
+ playback was not active, the behavior was the same as the current one.
``playlist``
The property is read-only and returns the current internal playlist. The
@@ -3401,8 +3492,11 @@ command is an exception and not a general rule.)
``i show-text "Filename: ${filename}"``
shows the filename of the current file when pressing the ``i`` key
-Within ``input.conf``, property expansion can be inhibited by putting the
-``raw`` prefix in front of commands.
+Whether property expansion is enabled by default depends on which API is used
+(see `Flat command syntax`_, `Commands specified as arrays`_ and `Named
+arguments`_), but it can always be enabled with the ``expand-properties``
+prefix or disabled with the ``raw`` prefix, as described in `Input Command
+Prefixes`_.
The following expansions are supported:
diff --git a/DOCS/man/ipc.rst b/DOCS/man/ipc.rst
index 4b808b9..615d3b6 100644
--- a/DOCS/man/ipc.rst
+++ b/DOCS/man/ipc.rst
@@ -116,7 +116,7 @@ can also be present. See `List of events`_ for a list of all supported events.
Because events can occur at any time, it may be difficult at times to determine
which response goes with which command. Commands may optionally include a
``request_id`` which, if provided in the command request, will be copied
-verbatim into the response. mpv does not intrepret the ``request_id`` in any
+verbatim into the response. mpv does not interpret the ``request_id`` in any
way; it is solely for the use of the requester. The only requirement is that
the ``request_id`` field must be an integer (a number without fractional parts
in the range ``-2^63..2^63-1``). Using other types is deprecated and will
diff --git a/DOCS/man/javascript.rst b/DOCS/man/javascript.rst
index 1e5ce99..e1b93d8 100644
--- a/DOCS/man/javascript.rst
+++ b/DOCS/man/javascript.rst
@@ -58,7 +58,7 @@ Language features - ECMAScript 5
The scripting backend which mpv currently uses is MuJS - a compatible minimal
ES5 interpreter. As such, ``String.substring`` is implemented for instance,
while the common but non-standard ``String.substr`` is not. Please consult the
-MuJS pages on language features and platform support - http://mujs.com .
+MuJS pages on language features and platform support - https://mujs.com .
Unsupported Lua APIs and their JS alternatives
----------------------------------------------
@@ -179,7 +179,8 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
``mp.utils.readdir(path [, filter])`` (LE)
-``mp.utils.file_info(path)`` (LE)
+``mp.utils.file_info(path)`` (LE) Note: like lua - this does NOT expand
+meta-paths like ``~~/foo`` (other JS file functions do expand meta paths).
``mp.utils.split_path(path)``
@@ -221,7 +222,8 @@ Additional utilities
``mp.utils.get_user_path(path)``
Expands (mpv) meta paths like ``~/x``, ``~~/y``, ``~~desktop/z`` etc.
- ``read_file``, ``write_file`` and ``require`` already use this internaly.
+ ``read_file``, ``write_file``, ``append_file`` and ``require`` already use
+ this internally.
``mp.utils.read_file(fname [,max])``
Returns the content of file ``fname`` as string. If ``max`` is provided and
@@ -232,7 +234,12 @@ Additional utilities
prefixed with ``file://`` as simple protection against accidental arguments
switch, e.g. ``mp.utils.write_file("file://~/abc.txt", "hello world")``.
-Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
+``mp.utils.append_file(fname, str)``
+ Same as ``mp.utils.write_file`` if the file ``fname`` does not exist. If it
+ does exist then append instead of overwrite.
+
+Note: ``read_file``, ``write_file`` and ``append_file`` throw on errors, allow
+text content only.
``mp.get_time_ms()``
Same as ``mp.get_time()`` but in ms instead of seconds.
@@ -329,10 +336,16 @@ Custom initialization
---------------------
After mpv initializes the JavaScript environment for a script but before it
-loads the script - it tries to run the file ``.init.js`` at the root of the mpv
+loads the script - it tries to run the file ``init.js`` at the root of the mpv
configuration directory. Code at this file can update the environment further
for all scripts. E.g. if it contains ``mp.module_paths.push("/foo")`` then
-``require`` at all scripts will search global module id's also at ``/foo``.
+``require`` at all scripts will search global module id's also at ``/foo``
+(do NOT do ``mp.module_paths = ["/foo"];`` because this will remove existing
+paths - like ``<script-dir>/modules`` for scripts which load from a directory).
+
+The custom-init file is ignored if mpv is invoked with ``--no-config``.
+
+Before mpv 0.34, the file name was ``.init.js`` (with dot) at the same dir.
The event loop
--------------
diff --git a/DOCS/man/libmpv.rst b/DOCS/man/libmpv.rst
index 909a2bb..53f3707 100644
--- a/DOCS/man/libmpv.rst
+++ b/DOCS/man/libmpv.rst
@@ -10,9 +10,9 @@ Since libmpv merely allows access to underlying mechanisms that can control
mpv, further documentation is spread over a few places:
- https://github.com/mpv-player/mpv/blob/master/libmpv/client.h
-- http://mpv.io/manual/master/#options
-- http://mpv.io/manual/master/#list-of-input-commands
-- http://mpv.io/manual/master/#properties
+- https://mpv.io/manual/master/#options
+- https://mpv.io/manual/master/#list-of-input-commands
+- https://mpv.io/manual/master/#properties
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
C PLUGINS
@@ -21,8 +21,8 @@ C PLUGINS
You can write C plugins for mpv. These use the libmpv API, although they do not
use the libmpv library itself.
-Currently, they must be explicitly enabled at build time with
-``--enable-cplugins``. They are available on Linux/BSD platforms only.
+They are available on Linux/BSD platforms only and enabled by default if the
+compiler supports linking with the ``-rdynamic`` flag.
C plugins location
------------------
diff --git a/DOCS/man/lua.rst b/DOCS/man/lua.rst
index 3fdb0b2..29fc67b 100644
--- a/DOCS/man/lua.rst
+++ b/DOCS/man/lua.rst
@@ -37,6 +37,12 @@ scripting backend to use for it. For Lua, it is ``.lua``. If the extension is
not recognized, an error is printed. (If an error happens, the extension is
either mistyped, or the backend was not compiled into your mpv binary.)
+mpv internally loads the script's name by stripping the ``.lua`` extension and
+replacing all nonalphanumeric characters with ``_``. E.g., ``my-tools.lua``
+becomes ``my_tools``. If there are several scripts with the same name, it is
+made unique by appending a number. This is the name returned by
+``mp.get_script_name()``.
+
Entries with ``.disable`` extension are always ignored.
If a script is a directory (either if a directory is passed to ``--script``,
@@ -474,7 +480,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
the timer callback function fn is run).
Note that these are methods, and you have to call them using ``:`` instead
- of ``.`` (Refer to http://www.lua.org/manual/5.2/manual.html#3.4.9 .)
+ of ``.`` (Refer to https://www.lua.org/manual/5.2/manual.html#3.4.9 .)
Example:
@@ -500,11 +506,11 @@ The ``mp`` module is preloaded, although it can be loaded manually with
Return the name of the current script. The name is usually made of the
filename of the script, with directory and file extension removed. If
there are several scripts which would have the same name, it's made unique
- by appending a number.
+ by appending a number. Any nonalphanumeric characters are replaced with ``_``.
.. admonition:: Example
- The script ``/path/to/fooscript.lua`` becomes ``fooscript``.
+ The script ``/path/to/foo-script.lua`` becomes ``foo_script``.
``mp.get_script_directory()``
Return the directory if this is a script packaged as directory (see
@@ -618,7 +624,7 @@ are useful only in special situations.
``mp.get_osd_size()``
Returns a tuple of ``osd_width, osd_height, osd_par``. The first two give
- the size of the OSD in pixels (for video ouputs like ``--vo=xv``, this may
+ the size of the OSD in pixels (for video outputs like ``--vo=xv``, this may
be "scaled" pixels). The third is the display pixel aspect ratio.
May return invalid/nonsense values if OSD is not initialized yet.
@@ -763,7 +769,7 @@ strictly part of the guaranteed API.
``mtime``
time of last modification
``ctime``
- time of last metadata change (Linux) / time of creation (Windows)
+ time of last metadata change
``is_file``
Whether ``path`` is a regular file (boolean)
``is_dir``
@@ -775,7 +781,7 @@ strictly part of the guaranteed API.
The booleans ``is_file`` and ``is_dir`` are provided as a convenience;
they can be and are derived from ``mode``.
- On error (eg. path does not exist), ``nil, error`` is returned.
+ On error (e.g. path does not exist), ``nil, error`` is returned.
``utils.split_path(path)``
Split a path into directory component and filename component, and return
diff --git a/DOCS/man/mpv.rst b/DOCS/man/mpv.rst
index be29a97..7a4c0b6 100644
--- a/DOCS/man/mpv.rst
+++ b/DOCS/man/mpv.rst
@@ -43,6 +43,9 @@ The following listings are not necessarily complete. See ``etc/input.conf`` for
a list of default bindings. User ``input.conf`` files and Lua scripts can
define additional key bindings.
+See also ``--input-test`` for interactive binding details by key, and the
+`stats`_ built-in script for key bindings list (including print to terminal).
+
Keyboard Control
----------------
@@ -227,7 +230,7 @@ i and I
`STATS`_ for more information.
del
- Cycles visibility between never / auto (mouse-move) / always
+ Cycle OSC visibility between never / auto (mouse-move) / always
\`
Show the console. (ESC closes it again. See `CONSOLE`_.)
@@ -247,16 +250,16 @@ corresponding adjustment.)
7 and 8
Adjust saturation.
-Alt+0 (and command+0 on OSX)
+Alt+0 (and command+0 on macOS)
Resize video window to half its original size.
-Alt+1 (and command+1 on OSX)
+Alt+1 (and command+1 on macOS)
Resize video window to its original size.
-Alt+2 (and command+2 on OSX)
+Alt+2 (and command+2 on macOS)
Resize video window to double its original size.
-command + f (OSX only)
+command + f (macOS only)
Toggle fullscreen (see also ``--fs``).
(The following keys are valid if you have a keyboard with multimedia keys.)
@@ -390,6 +393,9 @@ It is started with ``%`` and has the following format::
``mpv --vf=foo:option1=%`expr length "$NAME"`%"$NAME" test.avi``
+Note: where applicable with JSON-IPC, ``%n%`` is the length in UTF-8 bytes,
+after decoding the JSON data.
+
Suboptions passed to the client API are also subject to escaping. Using
``mpv_set_option_string()`` is exactly like passing ``--name=data`` to the
command line (but without shell processing of the string). Some options
@@ -427,23 +433,28 @@ need to escape special characters. To work this around, the path can be
additionally wrapped in the fixed-length syntax, e.g. ``%n%string_of_length_n``
(see above).
-Some mpv options interpret paths starting with ``~``. Currently, the prefix
-``~~/`` expands to the mpv configuration directory (usually ``~/.config/mpv/``).
+Some mpv options interpret paths starting with ``~``.
+Currently, the prefix ``~~home/`` expands to the mpv configuration directory
+(usually ``~/.config/mpv/``).
``~/`` expands to the user's home directory. (The trailing ``/`` is always
required.) The following paths are currently recognized:
================ ===============================================================
Name Meaning
================ ===============================================================
-``~~/`` mpv config dir (for example ``~/.config/mpv/``)
+``~~/`` If the subpath exists in any of the mpv's config directories
+ the path of the existing file/dir is returned. Otherwise this
+ is equivalent to ``~~home/``.
+ Note that if --no-config is used ``~~/foobar`` will resolve to
+ ``foobar`` which can be unexpected.
``~/`` user home directory root (similar to shell, ``$HOME``)
-``~~home/`` same as ``~~/``
+``~~home/`` mpv config dir (for example ``~/.config/mpv/``)
``~~global/`` the global config path, if available (not on win32)
-``~~osxbundle/`` the OSX bundle resource path (OSX only)
-``~~desktop/`` the path to the desktop (win32, OSX)
-``~~exe_dir`` win32 only: the path to the directory containing the exe (for
+``~~osxbundle/`` the macOS bundle resource path (macOS only)
+``~~desktop/`` the path to the desktop (win32, macOS)
+``~~exe_dir/`` win32 only: the path to the directory containing the exe (for
config file purposes; ``$MPV_HOME`` overrides it)
-``~~old_home`` do not use
+``~~old_home/`` do not use
================ ===============================================================
@@ -511,7 +522,7 @@ They support the following operations:
============= ===============================================
Suffix Meaning
============= ===============================================
--set Set a list of items (using the list separator, interprets escapes)
+-set Set a list of items (using the list separator, escaped with backslash)
-append Append single item (does not interpret escapes)
-add Append 1 or more items (same syntax as -set)
-pre Prepend 1 or more items (same syntax as -set)
@@ -781,9 +792,11 @@ ignored. This Lua code execution is not sandboxed.
Any variables in condition expressions can reference properties. If an
identifier is not already defined by Lua or mpv, it is interpreted as property.
-For example, ``pause`` would return the current pause status. If the variable
-name contains any ``_`` characters, they are turned into ``-``. For example,
-``playback_time`` would return the property ``playback-time``.
+For example, ``pause`` would return the current pause status. You cannot
+reference properties with ``-`` this way since that would denote a subtraction,
+but if the variable name contains any ``_`` characters, they are turned into
+``-``. For example, ``playback_time`` would return the property
+``playback-time``.
A more robust way to access properties is using ``p.property_name`` or
``get("property-name", default_value)``. The automatic variable to property
@@ -1001,7 +1014,7 @@ listed.
- ``AV:`` or ``V:`` (video only) or ``A:`` (audio only)
- The current time position in ``HH:MM:SS`` format (``playback-time`` property)
- The total file duration (absent if unknown) (``duration`` property)
-- Playback speed, e.g. `` x2.0``. Only visible if the speed is not normal. This
+- Playback speed, e.g. ``x2.0``. Only visible if the speed is not normal. This
is the user-requested speed, and not the actual speed (usually they should
be the same, unless playback is too slow). (``speed`` property.)
- Playback percentage, e.g. ``(13%)``. How much of the file has been played.
@@ -1260,7 +1273,7 @@ Currently this happens only in the following cases:
or file associations provided by desktop environments)
- if started from explorer.exe on Windows (technically, if it was started on
Windows, and all of the stdout/stderr/stdin handles are unset)
-- started out of the bundle on OSX
+- started out of the bundle on macOS
- if you manually use ``--player-operation-mode=pseudo-gui`` on the command line
This mode applies options from the builtin profile ``builtin-pseudo-gui``, but
@@ -1298,7 +1311,7 @@ Linux desktop issues
====================
This subsection describes common problems on the Linux desktop. None of these
-problems exist on systems like Windows or OSX.
+problems exist on systems like Windows or macOS.
Disabling Screensaver
---------------------
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 50ff3ec..3ade7f7 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -56,7 +56,7 @@ Track Selection
behavior tends to change around with each mpv release.
The track selection properties will return the option value outside of
- playback (as expected), but during playbac, the affective track
+ playback (as expected), but during playback, the affective track
selection is returned. For example, with ``--aid=auto``, the ``aid``
property will suddenly return ``2`` after playback initialization
(assuming the file has at least 2 audio tracks, and the second is the
@@ -213,7 +213,7 @@ Playback Control
Slow down or speed up playback by the factor given as parameter.
If ``--audio-pitch-correction`` (on by default) is used, playing with a
- speed higher than normal automatically inserts the ``scaletempo`` audio
+ speed higher than normal automatically inserts the ``scaletempo2`` audio
filter.
``--pause``
@@ -392,6 +392,13 @@ Playback Control
difference between the two option is that this option performs a seek on
loop, instead of reloading the file.
+ .. note::
+
+ ``--loop-file`` counts the number of times it causes the player to
+ seek to the beginning of the file, not the number of full playthroughs. This
+ means ``--loop-file=1`` will end up playing the file twice. Contrast with
+ ``--loop-playlist``, which counts the number of full playthroughs.
+
``--loop`` is an alias for this option.
``--ab-loop-a=<time>``, ``--ab-loop-b=<time>``
@@ -844,6 +851,29 @@ Program Behavior
- ``--reset-on-next-file=all``
Try to reset all settings that were changed during playback.
+``--watch-later-options=option1,option2,...``
+ The options that are saved in "watch later" files if they have been changed
+ since when mpv started. These values will be restored the next time the
+ files are played. The playback position is always saved as ``start``, so
+ adding ``start`` to this list has no effect.
+
+ When removing options, existing watch later data won't be modified and will
+ still be applied fully, but new watch later data won't contain these
+ options.
+
+ This is a string list option. See `List Options`_ for details.
+
+ .. admonition:: Examples
+
+ - ``--watch-later-options-remove=fullscreen``
+ The fullscreen state won't be saved to watch later files.
+ - ``--watch-later-options-remove=volume``
+ ``--watch-later-options-remove=mute``
+ The volume and mute state won't be saved to watch later files.
+ - ``--watch-later-options-clr``
+ No option will be saved to watch later files except the starting
+ position.
+
``--write-filename-in-watch-later-config``
Prepend the watch later config files with the name of the file they refer
to. This is simply written as comment on the top of the file.
@@ -965,9 +995,11 @@ Program Behavior
no). It's disabled ("no") by default for performance reasons.
``ytdl_path=youtube-dl``
- Configure path to youtube-dl executable or a compatible fork's.
- The default "youtube-dl" looks for the executable in PATH. In a Windows
- environment the suffix extension ".exe" is always appended.
+ Configure paths to youtube-dl's executable or a compatible fork's. The
+ paths should be separated by : on Unix and ; on Windows. mpv looks in
+ order for the configured paths in PATH and in mpv's config directory.
+ The defaults are "yt-dlp", "yt-dlp_x86" and "youtube-dl". On Windows
+ the suffix extension ".exe" is always appended.
.. admonition:: Why do the option names mix ``_`` and ``-``?
@@ -1196,9 +1228,9 @@ Video
:vdpau-copy: copies video back into system RAM (Linux with some GPUs only)
:vaapi: requires ``--vo=gpu`` or ``--vo=vaapi`` (Linux only)
:vaapi-copy: copies video back into system RAM (Linux with some GPUs only)
- :videotoolbox: requires ``--vo=gpu`` (OS X 10.8 and up),
+ :videotoolbox: requires ``--vo=gpu`` (macOS 10.8 and up),
or ``--vo=libmpv`` (iOS 9.0 and up)
- :videotoolbox-copy: copies video back into system RAM (OS X 10.8 or iOS 9.0 and up)
+ :videotoolbox-copy: copies video back into system RAM (macOS 10.8 or iOS 9.0 and up)
:dxva2: requires ``--vo=gpu`` with ``--gpu-context=d3d11``,
``--gpu-context=angle`` or ``--gpu-context=dxinterop``
(Windows only)
@@ -1477,11 +1509,14 @@ Video
This option is disabled if the ``--no-keepaspect`` option is used.
``--video-rotate=<0-359|no>``
- Rotate the video clockwise, in degrees. Currently supports 90° steps only.
- If ``no`` is given, the video is never rotated, even if the file has
- rotation metadata. (The rotation value is added to the rotation metadata,
- which means the value ``0`` would rotate the video according to the
- rotation metadata.)
+ Rotate the video clockwise, in degrees. If ``no`` is given, the video is
+ never rotated, even if the file has rotation metadata. (The rotation value
+ is added to the rotation metadata, which means the value ``0`` would rotate
+ the video according to the rotation metadata.)
+
+ When using hardware decoding without copy-back, only 90° steps work, while
+ software decoding and hardware decoding methods that copy the video back to
+ system memory support all values between 0 and 359.
``--video-zoom=<value>``
Adjust the video display scale factor by the given value. The parameter is
@@ -1606,7 +1641,7 @@ 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,hevc,vp9,av1``. Note that
+ By default, this is set to ``h264,vc1,hevc,vp8,vp9,av1``. 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.
@@ -1739,7 +1774,7 @@ Audio
``--audio-pitch-correction=<yes|no>``
If this is enabled (default), playing with a speed different from normal
- automatically inserts the ``scaletempo`` audio filter. For details, see
+ automatically inserts the ``scaletempo2`` audio filter. For details, see
audio filter section.
``--audio-device=<name>``
@@ -2003,14 +2038,17 @@ Audio
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-display=<no|attachment>``
- Setting this option to ``attachment`` (default) will display image
- attachments (e.g. album cover art) when playing audio files. It will
- display the first image found, and additional images are available as
- video tracks.
+``--audio-display=<no|embedded-first|external-first>``
+ Determines whether to display cover art when playing audio files and with
+ what priority. It will display the first image found, and additional images
+ are available as video tracks.
- Setting this option to ``no`` disables display of video entirely when
- playing audio files.
+ :no: Disable display of video entirely when playing audio
+ files.
+ :embedded-first: Display embedded images and external cover art, giving
+ priority to embedded images (default).
+ :external-first: Display embedded images and external cover art, giving
+ priority to external files.
This option has no influence on files with normal video tracks.
@@ -2091,7 +2129,7 @@ Audio
:no: Don't automatically load external audio files (default).
:exact: Load the media filename with audio file extension.
- :fuzzy: Load all audio files containing media filename.
+ :fuzzy: Load all audio files containing the media filename.
:all: Load all audio files in the current and ``--audio-file-paths``
directories.
@@ -2471,7 +2509,7 @@ Subtitles
:no: Don't automatically load external subtitle files.
:exact: Load the media filename with subtitle file extension and possibly
language suffixes (default).
- :fuzzy: Load all subs containing media filename.
+ :fuzzy: Load all subs containing the media filename.
:all: Load all subs in the current and ``--sub-file-paths`` directories.
``--sub-codepage=<codepage>``
@@ -2575,6 +2613,15 @@ Subtitles
Can be used to disable display of subtitles, but still select and decode
them.
+``--secondary-sub-visibility``, ``--no-secondary-sub-visibility``
+ Can be used to disable display of secondary subtitles, but still select and
+ decode them.
+
+ .. note::
+
+ If ``--sub-visibility=no``, secondary subtitles are hidden regardless of
+ ``--secondary-sub-visibility``.
+
``--sub-clear-on-seek``
(Obscure, rarely useful.) Can be used to play broken mkv files with
duplicate ReadOrder fields. ReadOrder is the first field in a
@@ -2587,6 +2634,13 @@ Subtitles
This works for ``dvb_teletext`` subtitle streams, and if FFmpeg has been
compiled with support for it.
+``--sub-past-video-end``
+ After the last frame of video, if this option is enabled, subtitles will
+ continue to update based on audio timestamps. Otherwise, the subtitles
+ for the last video frame will stay onscreen.
+
+ Default: disabled
+
``--sub-font=<name>``
Specify font to use for subtitles that do not themselves
specify a particular font. The default is ``sans-serif``.
@@ -2752,7 +2806,7 @@ Subtitles
List items are matched in order. If a regular expression matches, the
process is stopped, and the subtitle line is discarded. The text matched
- against is, currently, always the ``Text`` field of ASS events (if the
+ against is, by default, the ``Text`` field of ASS events (if the
subtitle format is different, it is always converted). This may include
formatting tags. Matching is case-insensitive, but how this is done depends
on the libc, and most likely works in ASCII only. It does not work on
@@ -2774,6 +2828,17 @@ Subtitles
include replacing the regexes with a very primitive and small subset of
sed, or some method to control case-sensitivity.
+``--sub-filter-jsre-...=...``
+ Same as ``--sub-filter-regex`` but with JavaScript regular expressions.
+ Shares/affected-by all ``--sub-filter-regex-*`` control options (see below),
+ and also experimental. Requires only JavaScript support.
+
+``--sub-filter-regex-plain=<yes|no>``
+ Whether to first convert the ASS "Text" field to plain-text (default: no).
+ This strips ASS tags and applies ASS directives, like ``\N`` to new-line.
+ If the result is multi-line then the regexp anchors ``^`` and ``$`` match
+ each line, but still any match discards all lines.
+
``--sub-filter-regex-warn=<yes|no>``
Log dropped lines with warning log level, instead of verbose (default: no).
Helpful for testing.
@@ -2799,7 +2864,7 @@ Subtitles
``--sub-font-provider=<auto|none|fontconfig>``
Which libass font provider backend to use (default: auto). ``auto`` will
attempt to use the native font provider: fontconfig on Linux, CoreText on
- OSX, DirectWrite on Windows. ``fontconfig`` forces fontconfig, if libass
+ macOS, DirectWrite on Windows. ``fontconfig`` forces fontconfig, if libass
was built with support (if not, it behaves like ``none``).
The ``none`` font provider effectively disables system fonts. It will still
@@ -2839,6 +2904,12 @@ Window
See also ``--fs-screen``.
+``--screen-name=<string>``
+ In multi-monitor configurations, this option tells mpv which screen to
+ display the video on based on the screen name from the video backend. The
+ same caveats in the ``--screen`` option also apply here. This option is
+ ignored and does nothing if ``--screen`` is explicitly set.
+
``--fullscreen``, ``--fs``
Fullscreen playback.
@@ -2853,12 +2924,18 @@ Window
This option works properly only with window managers which
understand the EWMH ``_NET_WM_FULLSCREEN_MONITORS`` hint.
- .. admonition:: Note (OS X)
+ .. admonition:: Note (macOS)
- ``all`` does not work on OS X and will behave like ``current``.
+ ``all`` does not work on macOS and will behave like ``current``.
See also ``--screen``.
+``--fs-screen-name=<string>``
+ In multi-monitor configurations, this option tells mpv which screen to go
+ fullscreen to based on the screen name from the video backend. The same
+ caveats in the ``--fs-screen`` option also apply here. This option is
+ ignored and does nothing if ``--fs-screen`` is explicitly set.
+
``--keep-open=<yes|no|always>``
Do not terminate when playing or seeking beyond the end of the file, and
there is not next file to be played (and ``--loop`` is not used).
@@ -2956,7 +3033,7 @@ Window
Manager.
``--ontop-level=<window|system|desktop|level>``
- (OS X only)
+ (macOS only)
Sets the level of an ontop window (default: window).
:window: On top of all other windows.
@@ -2973,14 +3050,8 @@ Window
Play video with window border and decorations. Since this is on by
default, use ``--no-border`` to disable the standard window decorations.
-``--fit-border``, ``--no-fit-border``
- (Windows only) Fit the whole window with border and decorations on the
- screen. Since this is on by default, use ``--no-fit-border`` to make mpv
- try to only fit client area with video on the screen. This behavior only
- applied to window/video with size exceeding size of the screen.
-
``--on-all-workspaces``
- (X11 only)
+ (X11 and macOS only)
Show the video window on all virtual desktops.
``--geometry=<[W[xH]][+-x+-y][/WS]>``, ``--geometry=<x:y>``
@@ -3006,9 +3077,9 @@ Window
Generally only supported by GUI VOs. Ignored for encoding.
- .. admonition: Note (OS X)
+ .. admonition: Note (macOS)
- On Mac OS X the origin of the screen coordinate system is located on the
+ On macOS, the origin of the screen coordinate system is located on the
bottom-left corner. For instance, ``0:0`` will place the window at the
bottom-left of the screen.
@@ -3176,14 +3247,14 @@ Window
- ``--monitoraspect=16:9`` or ``--monitoraspect=1.7777``
``--hidpi-window-scale``, ``--no-hidpi-window-scale``
- (OS X, Windows, X11, and Wayland only)
+ (macOS, Windows, X11, and Wayland only)
Scale the window size according to the backing scale factor (default: yes).
On regular HiDPI resolutions the window opens with double the size but appears
- as having the same size as on non-HiDPI resolutions. This is the default OS X
- behavior.
+ as having the same size as on non-HiDPI resolutions. This is enabled by
+ default on macOS.
``--native-fs``, ``--no-native-fs``
- (OS X only)
+ (macOS only)
Uses the native fullscreen mechanism of the OS (default: yes).
``--monitorpixelaspect=<ratio>``
@@ -3216,8 +3287,8 @@ Window
``intptr_t``. mpv will create its own window, and set the wid window as
parent, like with X11.
- On OSX/Cocoa, the ID is interpreted as ``NSView*``. Pass it as value cast
- to ``intptr_t``. mpv will create its own sub-view. Because OSX does not
+ On macOS/Cocoa, the ID is interpreted as ``NSView*``. Pass it as value cast
+ to ``intptr_t``. mpv will create its own sub-view. Because macOS does not
support window embedding of foreign processes, this works only with libmpv,
and will crash when used from the command line.
@@ -3699,15 +3770,17 @@ Demuxer
``--prefetch-playlist=<yes|no>``
Prefetch next playlist entry while playback of the current entry is ending
- (default: no). This merely opens the URL of the next playlist entry as soon
- as the current URL is fully read.
+ (default: no).
+
+ This does not prefill the cache with the video data of the next URL.
+ Prefetching video data is supported only for the current playlist entry,
+ and depends on the demuxer cache settings (on by default). This merely
+ opens the URL of the next playlist entry as soon the current URL is fully
+ read.
This does **not** work with URLs resolved by the ``youtube-dl`` wrapper,
and it won't.
- This does not affect HLS (``.m3u8`` URLs) - HLS prefetching depends on the
- demuxer cache settings and is on by default.
-
This can give subtly wrong results if per-file options are used, or if
options are changed in the time window between prefetching start and next
file played.
@@ -3764,7 +3837,15 @@ Input
configuration directory (usually ``~/.config/mpv/input.conf``).
``--no-input-default-bindings``
- Disable mpv default (built-in) key bindings.
+ Disable default-level ("weak") key bindings. These are bindings which config
+ files like ``input.conf`` can override. It currently affects the builtin key
+ bindings, and keys which scripts bind using ``mp.add_key_binding`` (but not
+ ``mp.add_forced_key_binding`` because this overrides ``input.conf``).
+
+``--no-input-builtin-bindings``
+ Disable loading of built-in key bindings during start-up. This option is
+ applied only during (lib)mpv initialization, and if used then it will not
+ be not possible to enable them later. May be useful to libmpv clients.
``--input-cmdlist``
Prints all commands that can be bound to keys.
@@ -3852,8 +3933,15 @@ Input
Support depends on the VO in use.
``--input-media-keys=<yes|no>``
- (OS X and Windows only)
- Enable/disable media keys support. Enabled by default (except for libmpv).
+ On systems where mpv can choose between receiving media keys or letting
+ the system handle them - this option controls whether mpv should receive
+ them.
+
+ Default: yes (except for libmpv). macOS and Windows only, because elsewhere
+ mpv doesn't have a choice - the system decides whether to send media keys
+ to mpv. For instance, on X11 or Wayland, system-wide media keys are not
+ implemented. Whether media keys work when the mpv window is focused is
+ implementation-defined.
``--input-right-alt-gr``, ``--no-input-right-alt-gr``
(Cocoa and Windows only)
@@ -4555,12 +4643,6 @@ Cache
Turn off input stream caching. See ``--cache``.
``--cache-secs=<seconds>``
- Deprecated. Once this option is removed, there will be no way to limit the
- cache size by time (only by size with ``--demuxer-max-bytes``). This option
- is considered useless, since there is no good reason to limit the cache by
- time, and the default value of this option is already something very high.
- The interaction with the other cache options is also confusing.
-
How many seconds of audio/video to prefetch if the cache is active. This
overrides the ``--demuxer-readahead-secs`` option if and only if the cache
is enabled and the value is larger. The default value is set to something
@@ -5159,18 +5241,29 @@ The following video options are currently all specific to ``--vo=gpu`` and
``--interpolation-threshold=<0..1,-1>``
Threshold below which frame ratio interpolation gets disabled (default:
- ``0.0001``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
+ ``0.01``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
where ``vfps`` is the speed-adjusted video FPS, and ``disphz`` the
display refresh rate. (The speed-adjusted video FPS is roughly equal to
the normal video FPS, but with slowdown and speedup applied. This matters
if you use ``--video-sync=display-resample`` to make video run synchronously
to the display FPS, or if you change the ``speed`` property.)
- The default is intended to almost always enable interpolation if the
- playback rate is even slightly different from the display refresh rate. But
- note that if you use e.g. ``--video-sync=display-vdrop``, small deviations
- in the rate can disable interpolation and introduce a discontinuity every
- other minute.
+ The default is intended to enable interpolation in scenarios where
+ retiming with the ``--video-sync=display-*`` cannot adjust the speed of
+ the video sufficiently for smooth playback. For example if a video is
+ 60.00 FPS and your display refresh rate is 59.94 Hz, interpolation will
+ never be activated, since the mismatch is within 1% of the refresh
+ rate. The default also handles the scenario when mpv cannot determine the
+ container FPS, such as during certain live streams, and may dynamically
+ toggle interpolation on and off. In this scenario, the default would be to
+ not use interpolation but rather to allow ``--video-sync=display-*`` to
+ retime the video to match display refresh rate. See
+ ``--video-sync-max-video-change`` for more information about how mpv
+ will retime video.
+
+ Also note that if you use e.g. ``--video-sync=display-vdrop``, small
+ deviations in the rate can disable interpolation and introduce a
+ discontinuity every other minute.
Set this to ``-1`` to disable this logic.
@@ -5263,6 +5356,12 @@ The following video options are currently all specific to ``--vo=gpu`` and
results, as can missing or incorrect display FPS information (see
``--override-display-fps``).
+``--vulkan-device=<device name>``
+ The name of the Vulkan device to use for rendering and presentation. Use
+ ``--vulkan-device=help`` to see the list of available devices and their
+ names. If left unspecified, the first enumerated hardware Vulkan device will
+ be used.
+
``--vulkan-swap-mode=<mode>``
Controls the presentation mode of the vulkan swapchain. This is similar
to the ``--opengl-swapinterval`` option.
@@ -5307,6 +5406,25 @@ The following video options are currently all specific to ``--vo=gpu`` and
with some older drivers / vulkan portability layers that don't provide
working VkEvent support.
+``--vulkan-display-display=<n>``
+ The index of the display, on the selected Vulkan device, to present on when
+ using the ``displayvk`` GPU context. Use ``--vulkan-display-display=help``
+ to see the list of available displays. If left unspecified, the first
+ enumerated display will be used.
+
+
+``--vulkan-display-mode=<n>``
+ The index of the display mode, of the selected Vulkan display, to use when
+ using the ``displayvk`` GPU context. Use ``--vulkan-display-mode=help``
+ to see the list of available modes. If left unspecified, the first
+ enumerated mode will be used.
+
+``--vulkan-display-plane=<n>``
+ The index of the plane, on the selected Vulkan device, to present on when
+ using the ``displayvk`` GPU context. Use ``--vulkan-display-plane=help``
+ to see the list of available planes. If left unspecified, the first
+ enumerated plane will be used.
+
``--d3d11-exclusive-fs=<yes|no>``
Switches the D3D11 swap chain fullscreen state to 'fullscreen' when
fullscreen video is requested. Also known as "exclusive fullscreen" or
@@ -5388,8 +5506,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
Currently only relevant for ``--gpu-api=d3d11``.
``--wayland-app-id=<string>``
- Set the client app id for Wayland-based video output methods. By default, "mpv"
- is used.
+ Set the client app id for Wayland-based video output methods (default: ``mpv``).
``--wayland-disable-vsync=<yes|no>``
Disable vsync for the wayland contexts (default: no). Useful for benchmarking
@@ -5403,7 +5520,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
there are no server side decorations from the compositor.
``--wayland-edge-pixels-touch=<value>``
- Defines the size of an edge border (default: 64) to initiate client side
+ Defines the size of an edge border (default: 32) to initiate client side
resizes events in the wayland contexts with touch events.
``--spirv-compiler=<compiler>``
@@ -5551,7 +5668,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
Specifies that this shader should be treated as a compute shader, with
the block size bw and bh. The compute shader will be dispatched with
however many blocks are necessary to completely tile over the output.
- Within each block, there will bw tw*th threads, forming a single work
+ Within each block, there will be tw*th threads, forming a single work
group. In other words: tw and th specify the work group size, which can
be different from the block size. So for example, a compute shader with
bw, bh = 32 and tw, th = 8 running on a 500x500 texture would dispatch
@@ -5684,7 +5801,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
``--deband-threshold=<0..4096>``
The debanding filter's cut-off threshold. Higher numbers increase the
debanding strength dramatically but progressively diminish image details.
- (Default 64)
+ (Default 32)
``--deband-range=<1..64>``
The debanding filter's initial radius. The radius increases linearly for
@@ -5794,7 +5911,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
Deactivates the automatic graphics switching and forces the dedicated GPU.
(default: no)
- OS X only.
+ macOS only.
``--cocoa-cb-sw-renderer=<yes|no|auto>``
Use the Apple Software Renderer when using cocoa-cb (default: auto). If set
@@ -5803,14 +5920,14 @@ The following video options are currently all specific to ``--vo=gpu`` and
software renderer, and ``auto`` only falls back to the software renderer
when the usual pixel format couldn't be created.
- OS X only.
+ macOS only.
``--cocoa-cb-10bit-context=<yes|no>``
Creates a 10bit capable pixel format for the context creation (default: yes).
Instead of 8bit integer framebuffer a 16bit half-float framebuffer is
requested.
- OS X only.
+ macOS only.
``--macos-title-bar-appearance=<appearance>``
Sets the appearance of the title bar (default: auto). Not all combinations
@@ -5891,7 +6008,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
1000ms since it's possible that Apple or the user changes the system
defaults. Anything higher than 1000ms though seems too long and shouldn't be
set anyway.
- OS X and cocoa-cb only
+ (macOS and cocoa-cb only)
``--macos-app-activation-policy=<regular|accessory|prohibited>``
@@ -5900,6 +6017,16 @@ The following video options are currently all specific to ``--vo=gpu`` and
macOS only.
+``--macos-geometry-calculation=<visible|whole>``
+ This changes the rectangle which is used to calculate the screen position
+ and size of the window (default: visible). ``visible`` takes the the menu
+ bar and Dock into account and the window is only positioned/sized within the
+ visible screen frame rectangle, ``whole`` takes the whole screen frame
+ rectangle and ignores the menu bar and Dock. Other previous restrictions
+ still apply, like the window can't be placed on top of the menu bar etc.
+
+ macOS only.
+
``--android-surface-size=<WxH>``
Set dimensions of the rendering surface used by the Android gpu context.
Needs to be set by the embedding application if the dimensions change during
@@ -5918,7 +6045,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
auto
auto-select (default)
cocoa
- Cocoa/OS X (deprecated, use --vo=libmpv instead)
+ Cocoa/macOS (deprecated, use --vo=libmpv instead)
win
Win32/WGL
winvk
@@ -5943,6 +6070,10 @@ The following video options are currently all specific to ``--vo=gpu`` and
VK_KHR_wayland_surface
drm
DRM/EGL
+ displayvk
+ VK_KHR_display. This backend is roughly the Vukan equivalent of
+ DRM/EGL, allowing for direct rendering via Vulkan without a display
+ manager.
x11egl
X11/EGL
android
@@ -5970,13 +6101,6 @@ The following video options are currently all specific to ``--vo=gpu`` and
no
Only allow desktop/core GL
-``--opengl-restrict=<version>``
- Restricts all OpenGL versions above a certain version. Versions are encoded
- in hundreds, i.e. OpenGL 4.5 -> 450. As an example, --opengl-restrict=300
- would restrict OpenGL 3.0 and higher, effectively only allowing 2.x
- contexts. Note that this only imposes a limit on context creation APIs, the
- actual OpenGL context may still have a higher OpenGL version. (Default: 0)
-
``--fbo-format=<fmt>``
Selects the internal format of textures used for FBOs. The format can
influence performance and quality of the video output. ``fmt`` can be one
@@ -6013,7 +6137,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
With ambient illuminance of 16 lux, mpv will pick the 1.0 gamma value (no
boost), and slightly increase the boost up until 1.2 for 256 lux.
- NOTE: Only implemented on OS X.
+ NOTE: Only implemented on macOS.
``--target-prim=<value>``
Specifies the primaries of the display. Video colors will be adapted to
@@ -6322,14 +6446,15 @@ The following video options are currently all specific to ``--vo=gpu`` and
Size of the 3D LUT generated from the ICC profile in each dimension.
Default is 64x64x64. Sizes may range from 2 to 512.
-``--icc-contrast=<0-1000000|inf>``
- Specifies an upper limit on the target device's contrast ratio. This is
- detected automatically from the profile if possible, but for some profiles
- it might be missing, causing the contrast to be assumed as infinite. As a
- result, video may appear darker than intended. This only affects BT.1886
- content. The default of 0 means no limit if the detected contrast is less
- than 100000, and limits to 1000 otherwise. Use ``--icc-contrast=inf`` to
- preserve the infinite contrast (most likely when using OLED displays).
+``--icc-force-contrast=<no|0-1000000|inf>``
+ Override the target device's detected contrast ratio by a specific value.
+ This is detected automatically from the profile if possible, but for some
+ profiles it might be missing, causing the contrast to be assumed as
+ infinite. As a result, video may appear darker than intended. If this is
+ the case, setting this option might help. This only affects BT.1886
+ content. The default of ``no`` means to use the profile values. The special
+ value ``inf`` causes the BT.1886 curve to be treated as a pure power gamma
+ 2.4 function.
``--blend-subtitles=<yes|video|no>``
Blend subtitles directly onto upscaled video frames, before interpolation
@@ -6362,13 +6487,13 @@ The following video options are currently all specific to ``--vo=gpu`` and
black).
yes
Try to create a framebuffer with alpha component. This only makes sense
- if the video contains alpha information (which is extremely rare). May
- not be supported on all platforms. If alpha framebuffers are
- unavailable, it silently falls back on a normal framebuffer. Note that
- if you set the ``--fbo-format`` option to a non-default value, a
- format with alpha must be specified, or this won't work.
- Whether this really works depends on the windowing system and desktop
- environment.
+ if the video contains alpha information (which is extremely rare) or if
+ you make the background color transparent. May not be supported on all
+ platforms. If alpha framebuffers are unavailable, it silently falls
+ back on a normal framebuffer. Note that if you set the ``--fbo-format``
+ option to a non-default value, a format with alpha must be specified,
+ or this won't work. Whether this really works depends on the windowing
+ system and desktop environment.
no
Ignore alpha component.
@@ -6395,7 +6520,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
flipping GL front and backbuffers immediately (i.e. it doesn't call it
in display-sync mode).
- On OSX this is always deactivated because it only causes performance
+ On macOS this is always deactivated because it only causes performance
problems and other regressions.
``--gpu-dumb-mode=<yes|no|auto>``
@@ -6530,11 +6655,10 @@ Miscellaneous
video. (Although it should have the same effects as
``audio``, the implementation is very different.)
:display-adrop: Drop or repeat audio data to compensate desyncing
- video. See ``--video-sync-adrop-size``. This mode will
- cause severe audio artifacts if the real monitor
- refresh rate is too different from the reported or
- forced rate. Since mpv 0.33.0, this acts on entire audio
- frames, instead of single samples.
+ video. This mode will cause severe audio artifacts if
+ the real monitor refresh rate is too different from
+ the reported or forced rate. Since mpv 0.33.0, this
+ acts on entire audio frames, instead of single samples.
:display-desync: Sync video to display, and let audio play on its own.
:desync: Sync video according to system clock, and let audio play
on its own.
@@ -6547,6 +6671,9 @@ Miscellaneous
multiple of the display FPS, as long as the speed change does not exceed
the value set by ``--video-sync-max-video-change``.
+ See ``--interpolation-threshold`` for how this option affects
+ interpolation.
+
This is mostly for testing, and the option may be randomly changed in the
future without notice.
@@ -6654,13 +6781,16 @@ Miscellaneous
CLI/config file only alias for ``--cover-art-files-append``. Each use of this
option will add a new external file.
-``--cover-art-auto=<no|fuzzy>``
- Whether to load _external_ cover art automatically (default: fuzzy). Similar
- to ``--sub-auto`` and ``--audio-file-auto``. However, it's currently limited
- to picking up a whitelist of "album art" filenames (such as ``cover.jpg``),
- so currently only the ``fuzzy`` choice is available. In addition, if a video
- already has tracks (which are not marked as cover art), external cover art
- will not be loaded.
+``--cover-art-auto=<no|exact|fuzzy|all>``
+ Whether to load _external_ cover art automatically. Similar to
+ ``--sub-auto`` and ``--audio-file-auto``. If a video already has tracks
+ (which are not marked as cover art), external cover art will not be loaded.
+
+ :no: Don't automatically load cover art.
+ :exact: Load the media filename with an image file extension.
+ :fuzzy: Load all cover art containing the media filename and filenames
+ in an internal whitelist, such as ``cover.jpg`` (default).
+ :all: Load all images in the current directory.
See ``--cover-art-files`` for details about what constitutes cover art.
@@ -6671,8 +6801,9 @@ Miscellaneous
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-files``), they will not be auto-selected.
+ by ``--sub-auto``, ``--audio-file-auto`` and ``--cover-art-auto``. If
+ external files are forcibly added (like with ``--sub-files``), they will
+ not be auto-selected.
This does not affect playlist expansion, redirection, or other loading of
referenced files like with ordered chapters.
diff --git a/DOCS/man/osc.rst b/DOCS/man/osc.rst
index eabc230..71af9d2 100644
--- a/DOCS/man/osc.rst
+++ b/DOCS/man/osc.rst
@@ -43,7 +43,8 @@ pl next
============= ================================================
title
- | Displays current media-title, filename, or custom title
+ | Displays current media-title, filename, custom title, or target chapter
+ name while hovering the seekbar.
============= ================================================
left-click show playlist position and length and full title
@@ -179,12 +180,12 @@ Configurable Options
``seekbarkeyframes``
Default: yes
- Controls the mode used to seek when dragging the seekbar (default: true). If
- set to true, default seeking mode is used (usually keyframes, but player
- defaults and heuristics can change it to exact). If set to false, exact
- seeking on mouse drags will be used instead. Keyframes are preferred, but
- exact seeks may be useful in cases where keyframes cannot be found. Note
- that using exact seeks can potentially make mouse dragging much slower.
+ Controls the mode used to seek when dragging the seekbar. If set to ``yes``,
+ default seeking mode is used (usually keyframes, but player defaults and
+ heuristics can change it to exact). If set to ``no``, exact seeking on
+ mouse drags will be used instead. Keyframes are preferred, but exact seeks
+ may be useful in cases where keyframes cannot be found. Note that using
+ exact seeks can potentially make mouse dragging much slower.
``seekrangestyle``
Default: inverted
@@ -375,6 +376,12 @@ Configurable Options
Set to ``yes`` to reduce festivity (i.e. disable santa hat in December.)
+``livemarkers``
+ Default: yes
+
+ Update chapter markers positions on duration changes, e.g. live streams.
+ The updates are unoptimized - consider disabling it on very low-end systems.
+
Script Commands
~~~~~~~~~~~~~~~
diff --git a/DOCS/man/stats.rst b/DOCS/man/stats.rst
index 2cf730f..be46fe6 100644
--- a/DOCS/man/stats.rst
+++ b/DOCS/man/stats.rst
@@ -24,7 +24,8 @@ stats:
1 Show usual stats
2 Show frame timings (scroll)
3 Input cache stats
-4 Internal stuff (scroll)
+4 Active key bindings (scroll)
+0 Internal stuff (scroll)
==== ==================
On pages which support scroll, these key bindings are also active:
@@ -51,13 +52,6 @@ option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
Configurable Options
~~~~~~~~~~~~~~~~~~~~
-``key_oneshot``
- Default: i
-``key_toggle``
- Default: I
-
- Key bindings to display stats.
-
``key_page_1``
Default: 1
``key_page_2``
@@ -66,6 +60,8 @@ Configurable Options
Default: 3
``key_page_4``
Default: 4
+``key_page_0``
+ Default: 0
Key bindings for page switching while stats are displayed.
@@ -171,18 +167,36 @@ Note: colors are given as hexadecimal values and use ASS tag order: BBGGRR
Different key bindings
~~~~~~~~~~~~~~~~~~~~~~
-A different key binding can be defined with the aforementioned options
-``key_oneshot`` and ``key_toggle`` but also with commands in ``input.conf``,
-for example::
+Additional keys can be configured in ``input.conf`` to display the stats::
e script-binding stats/display-stats
E script-binding stats/display-stats-toggle
-Using ``input.conf``, it is also possible to directly display a certain page::
+And to display a certain page directly::
i script-binding stats/display-page-1
e script-binding stats/display-page-2
+Active key bindings page
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Lists the active key bindings and the commands they're bound to, excluding the
+interactive keys of the stats script itself. See also ``--input-test`` for more
+detailed view of each binding.
+
+The keys are grouped automatically using a simple analysis of the command
+string, and one should not expect documentation-level grouping accuracy,
+however, it should still be reasonably useful.
+
+Using ``--idle --script-opts=stats-bindlist=yes`` will print the list to the
+terminal and quit immediately. By default long lines are shortened to 79 chars,
+and terminal escape sequences are enabled. A different length limit can be
+set by changing ``yes`` to a number (at least 40), and escape sequences can be
+disabled by adding ``-`` before the value, e.g. ``...=-yes`` or ``...=-120``.
+
+Like with ``--input-test``, the list includes bindings from ``input.conf`` and
+from user scripts. Use ``--no-config`` to list only built-in bindings.
+
Internal stuff page
~~~~~~~~~~~~~~~~~~~
diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst
index 3797ad2..d80a8aa 100644
--- a/DOCS/man/vf.rst
+++ b/DOCS/man/vf.rst
@@ -60,7 +60,7 @@ libavfilter bridge.
.. note::
To get a full list of available video filters, see ``--vf=help`` and
- http://ffmpeg.org/ffmpeg-filters.html .
+ https://ffmpeg.org/ffmpeg-filters.html .
Also, keep in mind that most actual filters are available via the ``lavfi``
wrapper, which gives you access to most of libavfilter's filters. This
@@ -310,7 +310,7 @@ Available mpv-only filters are:
``<stereo-in>``
Set the stereo mode the video is assumed to be encoded in. Use
- ``--vf format:stereo-in=help`` to list all available modes. Check with
+ ``--vf=format:stereo-in=help`` to list all available modes. Check with
the ``stereo3d`` filter documentation to see what the names mean.
``<stereo-out>``
@@ -389,7 +389,7 @@ Available mpv-only filters are:
option gives the flags which should be passed to libswscale. This
option is numeric and takes a bit-wise combination of ``SWS_`` flags.
- See ``http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libswscale/swscale.h``.
+ See ``https://git.videolan.org/?p=ffmpeg.git;a=blob;f=libswscale/swscale.h``.
``<o>``
Set AVFilterGraph options. These should be documented by FFmpeg.
@@ -598,7 +598,7 @@ Available mpv-only filters are:
``reversal-bug=<yes|no>``
:no: Use the API as it was interpreted by older Mesa drivers. While
- this interpretation was more obvious and inuitive, it was
+ this interpretation was more obvious and intuitive, it was
apparently wrong, and not shared by Intel driver developers.
:yes: Use Intel interpretation of surface forward and backwards
references (default). This is what Intel drivers and newer Mesa
@@ -738,7 +738,7 @@ Available mpv-only filters are:
which leads to lost frames.
``print=yes|no``
- Print computed fingerprints the the terminal (default: no). This is
+ Print computed fingerprints to the terminal (default: no). This is
mostly for testing and such. Scripts should use ``vf-metadata`` to
read information from this filter instead.
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 833fbdd..7632f3c 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -253,7 +253,7 @@ Available video output drivers are:
quality or performance by changing the ``--fbo-format`` option to
``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include Mesa/Intel not
accepting ``rgb16``, Mesa sometimes not being compiled with float texture
- support, and some OS X setups being very slow with ``rgb16`` but fast
+ support, and some macOS setups being very slow with ``rgb16`` but fast
with ``rgb32f``. If you have problems, you can also try enabling the
``--gpu-dumb-mode=yes`` option.
@@ -336,7 +336,7 @@ Available video output drivers are:
``tct``
Color Unicode art video output driver that works on a text console.
By default depends on support of true color by modern terminals to display
- the images at full color range, but 256-colors outout is also supported (see
+ the images at full color range, but 256-colors output is also supported (see
below). On Windows it requires an ansi terminal such as mintty.
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
@@ -413,18 +413,22 @@ Available video output drivers are:
to take into account padding at the report - this only works correctly
when the overall padding per axis is smaller than the number of cells.
+ ``--vo-sixel-exit-clear=<yes|no>`` (default: yes)
+ Whether or not to clear the terminal on quit. When set to no - the last
+ sixel image stays on screen after quit, with the cursor following it.
+
Sixel image quality options:
``--vo-sixel-dither=<algo>``
Selects the dither algorithm which libsixel should apply.
Can be one of the below list as per libsixel's documentation.
- auto
- Choose diffuse type automatically
+ auto (Default)
+ Let libsixel choose the dithering method.
none
Don't diffuse
atkinson
- Diffuse with Bill Atkinson's method. (Default)
+ Diffuse with Bill Atkinson's method.
fs
Diffuse with Floyd-Steinberg method
jajuni
@@ -444,20 +448,18 @@ Available video output drivers are:
using ``no`` (at the time of writing) will slow down ``xterm``.
``--vo-sixel-reqcolors=<colors>`` (default: 256)
- Set up libsixel to use required number of colors for dynamic palette.
- This value depends on the terminal emulator as well. Xterm supports
- 256 colors. Can set this to a lower value for faster performance.
- This option has no effect if fixed palette is used.
+ Has no effect with fixed palette. Set up libsixel to use required
+ number of colors for dynamic palette. This value depends on the
+ terminal emulator as well. Xterm supports 256 colors. Can set this to
+ a lower value for faster performance.
``--vo-sixel-threshold=<threshold>`` (default: -1)
- When using a dynamic palette, defines the threshold to change the
+ Has no effect with fixed palette. Defines the threshold to change the
palette - as percentage of the number of colors, e.g. 20 will change
the palette when the number of colors changed by 20%. It's a simple
measure to reduce the number of palette changes, because it can be slow
- in some terminals (``xterm``), however, it seems that in ``mlterm`` it
- causes image corruption. The default (-1) will change the palette
- on every frame and will have better quality, and no corruption in
- ``mlterm``.
+ in some terminals (``xterm``). The default (-1) will choose a palette
+ on every frame and will have better quality.
``image``
Output each frame into an image file in the current directory. Each file
@@ -496,7 +498,7 @@ Available video output drivers are:
Specify the directory to save the image files to (default: ``./``).
``libmpv``
- For use with libmpv direct embedding. As a special case, on OS X it
+ For use with libmpv direct embedding. As a special case, on macOS it
is used like a normal VO within mpv (cocoa-cb). Otherwise useless in any
other contexts.
(See ``<mpv/render.h>``.)
@@ -550,8 +552,13 @@ Available video output drivers are:
Select the connector to use (usually this is a monitor.) If ``<name>``
is empty or ``auto``, mpv renders the output on the first available
connector. Use ``--drm-connector=help`` to get a list of available
- connectors. When using multiple graphic cards, use the ``<gpu_number>``
- argument to disambiguate.
+ connectors. The ``<gpu_number>`` argument can be used to disambiguate
+ multiple graphic cards, but is deprecated in favor of ``--drm-device``.
+ (default: empty)
+
+ ``--drm-device=<path>``
+ Select the DRM device file to use. If specified this overrides automatic
+ card selection and any card number specified ``--drm-connector``.
(default: empty)
``--drm-mode=<preferred|highest|N|WxH[@R]>``
diff --git a/DOCS/mplayer-changes.rst b/DOCS/mplayer-changes.rst
index d00b520..ad5211a 100644
--- a/DOCS/mplayer-changes.rst
+++ b/DOCS/mplayer-changes.rst
@@ -52,7 +52,7 @@ Input
* Classic LIRC support was removed. Install remotes as input devices instead.
This way they will send X11 key events to the mpv window, which can be bound
using the normal ``input.conf``.
- Also see: http://github.com/mpv-player/mpv/wiki/IR-remotes
+ Also see: https://github.com/mpv-player/mpv/wiki/IR-remotes
* Joystick support was removed. It was considered useless and was the cause
of some problems (e.g. a laptop's accelerator being recognized as joystick).
* Support for relative seeking by percentage.
diff --git a/DOCS/waf-buildsystem.rst b/DOCS/waf-buildsystem.rst
index 6222c5d..7949d09 100644
--- a/DOCS/waf-buildsystem.rst
+++ b/DOCS/waf-buildsystem.rst
@@ -46,7 +46,7 @@ mpv's custom configure step on top of waf
To some extents mpv has a custom build system written on top of waf. This
document will not go over the standard waf behaviour as that is documented in
-the `Waf book <http://docs.waf.googlecode.com/git/book_17/single.html>`_.
+the `Waf book <https://waf.io/book/>`_.
All of the configuration process is handled with a declarative approach. Lists
of dictionaries define the checks, and some custom Python code traverses these
diff --git a/LICENSE.GPL b/LICENSE.GPL
index 7b845ee..d159169 100644
--- a/LICENSE.GPL
+++ b/LICENSE.GPL
@@ -1,8 +1,8 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.
- 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
-the GNU Library General Public License instead.) You can apply it to
+the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -55,7 +55,7 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
-
+
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -278,7 +278,7 @@ PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
-
+
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
@@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found.
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 this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
@@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names:
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Library General
+library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
diff --git a/LICENSE.LGPL b/LICENSE.LGPL
index e5ab03e..4362b49 100644
--- a/LICENSE.LGPL
+++ b/LICENSE.LGPL
@@ -55,7 +55,7 @@ modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
-
+
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
@@ -111,7 +111,7 @@ modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
-
+
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
@@ -158,7 +158,7 @@ Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
-
+
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
@@ -216,7 +216,7 @@ instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
-
+
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
@@ -267,7 +267,7 @@ Library will still fall under Section 6.)
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
-
+
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
@@ -329,7 +329,7 @@ restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
-
+
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
@@ -370,7 +370,7 @@ subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
-
+
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
@@ -422,7 +422,7 @@ conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
-
+
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
@@ -456,7 +456,7 @@ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
-
+
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
diff --git a/README.md b/README.md
index 8cc3dc3..7bbf636 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@
* [Wiki](https://github.com/mpv-player/mpv/wiki)
* [FAQ][FAQ]
-* [Manual](http://mpv.io/manual/master/)
+* [Manual](https://mpv.io/manual/master/)
## Overview
@@ -53,7 +53,7 @@ Releases can be found on the [release list][releases].
For semi-official builds and third-party packages please see
-[mpv.io/installation](http://mpv.io/installation/).
+[mpv.io/installation](https://mpv.io/installation/).
## Changelog
@@ -85,10 +85,10 @@ detect it, the file `build/config.log` may contain information about the
reasons for the failure.
NOTE: To avoid cluttering the output with unreadable spam, `--help` only shows
-one of the two switches for each option. If the option is autodetected by
-default, the `--disable-***` switch is printed; if the option is disabled by
-default, the `--enable-***` switch is printed. Either way, you can use
-`--enable-***` or `--disable-**` regardless of what is printed by `--help`.
+one of the two switches for each option. If the option is autodetected or
+enabled by default, the `--disable-***` switch is printed; if the option is
+disabled by default, the `--enable-***` switch is printed. Either way, you can
+use `--enable-***` or `--disable-**` regardless of what is printed by `--help`.
To build the software you can use `./waf build`: the result of the compilation
will be located in `build/mpv`. You can use `./waf install` to install mpv
@@ -121,9 +121,9 @@ Libass dependencies (when building libass):
- gcc or clang, yasm on x86 and x86_64
- fribidi, freetype, fontconfig development headers (for libass)
-- harfbuzz (optional, required for correct rendering of combining characters,
- particularly for correct rendering of non-English text on OSX, and
- Arabic/Indic scripts on any platform)
+- harfbuzz (required for correct rendering of combining characters, particularly
+ for correct rendering of non-English text on OSX, and Arabic/Indic scripts on
+ any platform)
FFmpeg dependencies (when building FFmpeg):
@@ -181,7 +181,7 @@ changes come and talk to us on IRC before you start working on them. It will
make code review easier for both parties later on.
You can check [the wiki](https://github.com/mpv-player/mpv/wiki/Stuff-to-do)
-or the [issue tracker](https://github.com/mpv-player/mpv/issues?q=is%3Aopen+is%3Aissue+label%3A%22feature+request%22)
+or the [issue tracker](https://github.com/mpv-player/mpv/issues?q=is%3Aopen+is%3Aissue+label%3Ameta%3Afeature-request)
for ideas on what you could contribute with.
## License
@@ -201,8 +201,8 @@ see the [FAQ][FAQ].
Most activity happens on the IRC channel and the github issue tracker.
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
-- **User IRC Channel**: `#mpv` on `irc.freenode.net`
-- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
+- **User IRC Channel**: `#mpv` on `irc.libera.chat`
+- **Developer IRC Channel**: `#mpv-devel` on `irc.libera.chat`
[FAQ]: https://github.com/mpv-player/mpv/wiki/FAQ
[releases]: https://github.com/mpv-player/mpv/releases
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index 6c1d643..36e82f3 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -1,16 +1,4 @@
-Release 0.33.1
-==============
-
-Security fix release for the 0.30 branch. Users are advised to upgrade immediately.
-
-
-Fixes and Minor Enhancements
-----------------------------
-
-- demux_mf: improve format string processing
-
-
-Release 0.33.0
+Release 0.34.0
==============
This release requires FFmpeg 4.0 or newer.
@@ -26,53 +14,34 @@ Features
Added
~~~~~
-- scripting: load scripts from directories
-- mac: activate logging when started from the bundle
-- ytdl_hook.lua: delay load subtitles
-- sub: add an option to filter subtitles by regex
-- scripting: add a way to run subprocesses as "scripts"
-- command: implement asynchronous commands and support for named arguments
-- player: add optional separate video decoding thread
-- vo_gpu: d3d11: add support for exclusive fullscreen
-- w32_common: Support HiDPI on Windows
-- vo_x11: add 10 bit support
-- vo_gpu: add BT.2390 tone-mapping
-- client API: add software rendering API
-- audio: add scaletempo2 filter based on chromium
-- auto_profiles: add this script
-- stream: Implement slice:// for reading slices of streams
-- player: add automatic loading of external cover art files
-- vo_sixel: implement terminal video output using sixel
+- player: allow vo to be switched at runtime
+- input.conf syntax: support custom quotes in `XstringX` form & single quotes
+- ao_ass: readd OSSv4 audio output, default on *BSD
+- player: load cover art with the media filename
+- vo_gpu: vulkan: implement a VkDisplayKHR backed context
+- osc: seekbar hover/drag: display target chapter at the OSC title
+- stats.lua: add page 4 with a list of active key bindings
+- sub: add --sub-filter-jsre (JS regex)
+- vo_rpi: restore fullscreen handling
+- vo_tct: add resize capability
+- ytdl_hook.lua: search for yt-dlp by default (preferred over youtube-dl)
Changed
~~~~~~~
-- sws_utils: use zimg by default if available
-- build: pick up Lua 5.2 by default (preferred over 5.1)
-- build: disable RPI vendor blob auto-detection in favor of open-source stack
-- build: make C11 atomics mandatory
-- build: make libass non-optional
-- player: stricter filename matching for external subtitle auto-loading
-- lua: support Unicode paths in script loading and IO library
-- vo_direct3d: rip out texture video rendering path and dumb down OSD rendering
-- audio: rewrite internal audio handling and AO API
-- build: disable GLX by default
+- vo_sixel: many fixes and improvements
+- filters: switch from scaletempo to scaletempo2, used for `speed` option
+- audio/vo_pulse: fix various edge cases and issues
+- stats.lua: move internal performance info from page 4 to page 0
+- command: don't hardcode command lists to be repeatable
Removed
~~~~~~~
-- stream_libarchive: disable tar support due to bugs
-- Remove remains of Libav compatibility
-- stream_smb: remove due to lack of thread safety and the abundance
- of alternatives, FFmpeg still includes SMB support
-- command: remove legacy hook API (has been deprecated for a long time)
-- client API: remove deprecated qthelper.hpp header
-- removed audio outputs: sndio, rsound, oss
-- x11: remove xdg-screensaver invocations that supported dbus based idle inhibit
-- client API: deactivate the opengl_cb API
-- build system: drop Python 2 compatbility
+- mac: drop build support for swift versions earlier than 4.1
+- vo_gpu: drop support for libplacebo older than v3.104.0
Options and Commands
@@ -81,90 +50,97 @@ Options and Commands
Added
~~~~~
-- demux: add option to disable cache "sharing" between back and forward buffers
-- player: add ab-loop-count option/property
-- ytdl_hook: add a way to use ytdl's default formats
-- ytdl_hook: add all_formats option that loads all formats that were found
-- demux_mkv: document probe-start-time option and enable it by default
-- command: extend osd-overlay command with bounds reporting
-- player: a number of new playlist contol commands/properties
-- ipc: add --input-ipc-client option
-- options: add option to control display-sync factor
-- vo_gpu: add better gamut clipping option
-- vo_gpu: vulkan: add ability to disable events
-- x11: add option to make window appear on a specific workspace
-- wayland: expose wayland-app-id as a user option
-- player: add --subs-with-matching-audio option
-- command: add read-only focused property
-- screenshot: option to use software rendering for screenshots
-- command: add delete-watch-later-config
-- command: new property mouse-pos with current position and hover state
+- vo_sixel: add --vo-sixel-exit-clear
+- player: add --screen-name and --fs-screen-name
+- player/command: add albumart argument to video-add
+- command: add pid (process id), display-width and display-height properties
+- sub: add secondary-sub-text, -start, and -end properties; secondary-sub-visibility option
+- vo_gpu: add --icc-force-contrast
+- player: add append-play flag to loadlist
+- command: add support for secondary subs to sub-seek and sub-step
+- options: add watch-later-options to configure which options are saved
+- player: add track-list/N/image sub-property
+- input: add --no-input-builtin-bindings option
+- drm_common: add --drm-device option
Changed
~~~~~~~
-- options: remove deprecation warning for "-foo bar" syntax
-- player: make audio hr-seek default
-- ad_lavc: disable decoder downmix by default
-- command: support save-position-on-quit for "stop" command too
-- command: extend subprocess command
-- options: do not accept ":" as separator anymore in key/value lists
+- vo_sixel: change default dither to "auto"
+- vd_lavc: add VP8 to the default allowed hwdec codec list
+- vo_gpu: lower default deband threshold to preserve more detail
+- vo_gpu: adjust interpolation-threshold's default
+- demux: undeprecate --cache-secs
+- options: add `Uploader`, `Channel_URL` to --display-tags defaults
+- options: --audio-display determines cover priority
+- command: make current-window-scale writeable
Deprecated
~~~~~~~~~~
-- demux: deprecate --cache-secs
+- win32: ignore and deprecate --fit-border
+- drm_common: deprecate selecting by card number in --drm-connector
Removed
~~~~~~~
-- wayland: remove wayland-frame-wait-offset option
-- input: remove deprecated --input-file option
-- vo_vdpau: remove deprecated/inactive --vo-vdpau-deint option
+- vo_gpu: remove --icc-contrast
+- stats.lua: remove script-opts for the main keys (i/I)
+- vo_gpu: opengl: remove --opengl-restrict
Fixes and Minor Enhancements
----------------------------
-- options: stop hiding deprecated options from --help output
-- lua, js: add mp.get_script_directory() function
-- lua: fix security relevant loading order issue with scripts
-- player: make screenshot each-frame mode more accurate
-- bash completion: complete ao/af/vo/vf options
-- zimg: add alpha support
-- wayland: make resizing better
-- edl: add mechanism for delay loading streams
-- stream_file: fix caching-related performance regression on CIFS
-- cocoa-cb: fix crashes and issues with UI updates
-- sub, demux: improve behavior with negative subtitle delay/muxed subs
-- demux: make seek ranges work for static images + audio
-- cocoa-cb: support maximize/minimize on startup
-- umpv: change from legacy FIFO to socket
-- stats: move input speed to cache page, make it a graph
-- stats: add fourth page with performance graphs
-- command: print edition title to OSD when cycling
-- zimg: add support for big endian input and output
-- demux_mkv: add png intra support
-- build: detect VT_GETMODE on FreeBSD and DragonFly
-- win32: use windows 10 native virtual-terminal if available
-- vo_gpu: enable frame caching for still frames
-- command: add property to return text subtitles in ASS
-- vo_gpu: ra_pl: add timers support
-- build: allow vo_wlshm on more Wayland platforms (e.g. FreeBSD)
-- zimg: add slice threading and use it by default
-- command: add a way to access properties of a current track
-- vo_gpu: EGL: fix transparency on X11/EGL/Mesa
-- vd_lavc: add AV1 to the default allowed hwdec codec list
+- terminal: correctly process input when foregrounded
+- vo_wlshm: support presentation time
+- mac: fix a window positioning bug when exiting fullscreen
+- csputils: add mappings for DCI-P3 (ST.431-2) and P3-D65 (ST.432-1)
+- player: make resetting of track selection to "auto" work
+- stream_lavf: support rtsps
+- vo_wlshm: support big endian systems
+- demux_mf: add support for more image codecs
+- msg: fix really-quiet option to only affect terminal output
+- mac: fix traditional fullscreen on macOS 11
+- ao_pulse: fix misbehavior with PipeWire when setting volume or mute
+- stats.lua: include a filter's @label when displaying filters on page 1
+- wayland, win32: support the display-hidpi-scale property
+- win32: keep the window title-bar inside the screen
+- filter_kernels: fix incorrect constant for quadric window
+- command: add display-width/display-height property
+- edl: add a way to add tags
+- stream_file: disable readahead for remote files on macOS to fix stutter
+- osxbundle: fix slow and wasteful memory allocation that caused stutter
+- vo_gpu: fix extreme clipping with --gamut-clipping for HDR outputs
+- af_scaletempo2: speed up using vector calculations
+- recorder: fix muxing of certain codecs
+- win32: support the focused property
+- js: add mp.utils.append_file
+- subs: fix missing subtitles on last frame
+- stats.lua: show scaled resolution
+- osc: expose osc-visibility via shared-script-properties
+- terminal-unix: fix ^Z identification and ignore unknown CSI sequences
+- context_drm_egl: allow autoprobe selection
+- wayland: improve behavior with touch events
+- command: make current-window-scale writeable
+- vo_vdpau: don't treat preemption as an error to fix VT switching
+- wayland: read XCURSOR_THEME to get cursor theme
+- demux_playlist: extend maximum line size (again) to 2M
+- win32: call timeBeginPeriod on demand on Windows 10
+- build: allow easier selection of lua version/package
+- vo_gpu: fix distortion with certain rotated videos
+- drm_common: skip non-primary devices during automatic choosing
+- drm_common: support USB, SPI, Writeback and unknown connector types (future proofing)
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.32.0..v0.33.1`
+A complete changelog can be seen by running `git log v0.33.0..v0.34.0`
in the git repository or by visiting either
-https://github.com/mpv-player/mpv/compare/v0.32.0...v0.33.1 or
-https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.32.0..v0.33.1
+https://github.com/mpv-player/mpv/compare/v0.33.0...v0.34.0 or
+https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.33.0..v0.34.0
diff --git a/TOOLS/appveyor-install.sh b/TOOLS/appveyor-install.sh
index 7b2473f..95e4f7b 100755
--- a/TOOLS/appveyor-install.sh
+++ b/TOOLS/appveyor-install.sh
@@ -23,6 +23,8 @@ pacman -S --noconfirm --needed \
$MINGW_PACKAGE_PREFIX-lua51 \
$MINGW_PACKAGE_PREFIX-ninja \
$MINGW_PACKAGE_PREFIX-rubberband \
+ $MINGW_PACKAGE_PREFIX-shaderc \
+ $MINGW_PACKAGE_PREFIX-spirv-cross \
$MINGW_PACKAGE_PREFIX-uchardet \
$MINGW_PACKAGE_PREFIX-vulkan
@@ -50,23 +52,3 @@ pacman -Sc --noconfirm
--enable-schannel
make -j4 install
)
-
-# Compile shaderc
-(
- git clone --depth=1 https://github.com/google/shaderc && cd shaderc
- "$PYTHON" utils/git-sync-deps
-
- mkdir build && cd build
- cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DSHADERC_SKIP_TESTS=ON \
- -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
- ninja install
-)
-
-# Compile SPIRV-Cross
-(
- git clone --depth=1 https://github.com/KhronosGroup/SPIRV-Cross && cd SPIRV-Cross
-
- mkdir build && cd build
- cmake -GNinja -DSPIRV_CROSS_SHARED=ON -DCMAKE_INSTALL_PREFIX=$MINGW_PREFIX ..
- ninja install
-)
diff --git a/TOOLS/dylib-unhell.py b/TOOLS/dylib-unhell.py
index 19ef411..6752e73 100755
--- a/TOOLS/dylib-unhell.py
+++ b/TOOLS/dylib-unhell.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
import re
import os
diff --git a/TOOLS/lua/autocrop.lua b/TOOLS/lua/autocrop.lua
index af6021b..3683b9f 100644
--- a/TOOLS/lua/autocrop.lua
+++ b/TOOLS/lua/autocrop.lua
@@ -56,6 +56,9 @@ detect_min_ratio: number[0.0-1.0] - The ratio of the minimum clip
detect_seconds: seconds - How long to gather cropdetect data.
Increasing this may be desirable to allow cropdetect more
time to collect data.
+
+suppress_osd: bool - Whether the OSD shouldn't be used when filters
+ are applied and removed.
--]]
require "mp.msg"
@@ -67,7 +70,8 @@ local options = {
detect_limit = "24/255",
detect_round = 2,
detect_min_ratio = 0.5,
- detect_seconds = 1
+ detect_seconds = 1,
+ suppress_osd = false,
}
read_options(options)
@@ -82,6 +86,8 @@ timers = {
detect_crop = nil
}
+local command_prefix = options.suppress_osd and 'no-osd' or ''
+
function is_filter_present(label)
local filters = mp.get_property_native("vf")
for index, filter in pairs(filters) do
@@ -102,17 +108,18 @@ function is_enough_time(seconds)
end
function is_cropable()
- local vid = mp.get_property_native("vid")
- local is_album = vid and mp.get_property_native(
- string.format("track-list/%d/albumart", vid)
- ) or false
+ for _, track in pairs(mp.get_property_native('track-list')) do
+ if track.type == 'video' and track.selected then
+ return not track.albumart
+ end
+ end
- return vid and not is_album
+ return false
end
function remove_filter(label)
if is_filter_present(label) then
- mp.command(string.format('vf remove @%s', label))
+ mp.command(string.format('%s vf remove @%s', command_prefix, label))
return true
end
return false
@@ -156,8 +163,8 @@ function detect_crop()
mp.command(
string.format(
- 'vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
- labels.cropdetect, limit, round
+ '%s vf pre @%s:cropdetect=limit=%s:round=%d:reset=0',
+ command_prefix, labels.cropdetect, limit, round
)
)
@@ -248,8 +255,8 @@ function apply_crop(meta)
-- Apply crop.
mp.command(
- string.format("vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
- labels.crop, meta.w, meta.h, meta.x, meta.y
+ string.format("%s vf pre @%s:lavfi-crop=w=%s:h=%s:x=%s:y=%s",
+ command_prefix, labels.crop, meta.w, meta.h, meta.x, meta.y
)
)
end
diff --git a/TOOLS/lua/autoload.lua b/TOOLS/lua/autoload.lua
index 7150abb..aa7e46d 100644
--- a/TOOLS/lua/autoload.lua
+++ b/TOOLS/lua/autoload.lua
@@ -16,6 +16,7 @@ disabled=no
images=no
videos=yes
audio=yes
+ignore_hidden=yes
--]]
@@ -29,7 +30,8 @@ o = {
disabled = false,
images = true,
videos = true,
- audio = true
+ audio = true,
+ ignore_hidden = true
}
options.read_options(o)
@@ -155,7 +157,9 @@ function find_and_add_entries()
return
end
table.filter(files, function (v, k)
- if string.match(v, "^%.") then
+ -- The current file could be a hidden file, ignoring it doesn't load other
+ -- files from the current directory.
+ if (o.ignore_hidden and not (v == filename) and string.match(v, "^%.")) then
return false
end
local ext = get_extension(v)
diff --git a/TOOLS/matroska.py b/TOOLS/matroska.py
index 2c1b751..d75c45a 100755
--- a/TOOLS/matroska.py
+++ b/TOOLS/matroska.py
@@ -21,8 +21,6 @@ Can also be used to directly parse Matroska files and display their contents.
# License along with mpv. If not, see <http://www.gnu.org/licenses/>.
#
-# for compatibility with Python 2.x
-from __future__ import print_function
elements_ebml = (
'EBML, 1a45dfa3, sub', (
@@ -300,8 +298,8 @@ parse_elems(elements_ebml, 'EBML')
parse_elems(elements_matroska, 'MATROSKA')
def printf(out, *args):
- out.write(u' '.join([str(x) for x in args]))
- out.write(u'\n')
+ out.write(' '.join(str(x) for x in args))
+ out.write('\n')
def generate_C_header(out):
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
@@ -466,8 +464,6 @@ if __name__ == "__main__":
elif sys.argv[1] == '--generate-definitions':
generate_C_definitions(sys.stdout)
else:
- if sys.version_info.major < 3:
- raise Exception("Dumping requires Python 3.")
s = open(sys.argv[1], "rb")
while 1:
start = s.tell()
diff --git a/TOOLS/osxbundle.py b/TOOLS/osxbundle.py
index f164b2b..bf08061 100755
--- a/TOOLS/osxbundle.py
+++ b/TOOLS/osxbundle.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python
-from __future__ import print_function
+#!/usr/bin/env python3
import os
import shutil
import sys
diff --git a/TOOLS/osxbundle/mpv.app/Contents/Info.plist b/TOOLS/osxbundle/mpv.app/Contents/Info.plist
index 8635a87..5fde0ac 100644
--- a/TOOLS/osxbundle/mpv.app/Contents/Info.plist
+++ b/TOOLS/osxbundle/mpv.app/Contents/Info.plist
@@ -188,6 +188,11 @@
<string>${VERSION}</string>
<key>NSHighResolutionCapable</key>
<true/>
+ <key>LSEnvironment</key>
+ <dict>
+ <key>MallocNanoZone</key>
+ <string>0</string>
+ </dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
diff --git a/TOOLS/travis-rebuild-website b/TOOLS/travis-rebuild-website
index ecf3dae..802dded 100755
--- a/TOOLS/travis-rebuild-website
+++ b/TOOLS/travis-rebuild-website
@@ -1,6 +1,6 @@
#!/bin/sh
-if [ "x$TARGET" != "xx86_64-w64-mingw32" ]; then
+if [ "$TRAVIS_OS_NAME" != "linux" ] || [ "$CC" != "clang" ]; then
# trigger build only on one of the matrix nodes
exit;
fi
diff --git a/TOOLS/umpv b/TOOLS/umpv
index 762e73a..2044e3e 100755
--- a/TOOLS/umpv
+++ b/TOOLS/umpv
@@ -31,8 +31,6 @@ import os
import socket
import errno
import subprocess
-import fcntl
-import stat
import string
files = sys.argv[1:]
@@ -52,7 +50,7 @@ def make_abs(filename):
if not is_url(filename):
return os.path.abspath(filename)
return filename
-files = [make_abs(f) for f in files]
+files = (make_abs(f) for f in files)
SOCK = os.path.join(os.getenv("HOME"), ".umpv_socket")
diff --git a/VERSION b/VERSION
index 8df3f45..85e60ed 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.33.1
+0.34.0
diff --git a/appveyor.yml b/appveyor.yml
index 92f229a..a385ac0 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,3 +1,5 @@
+image: Visual Studio 2019
+
branches:
only:
- master
@@ -15,18 +17,8 @@ shallow_clone: true
test: off
install:
- # Work around age/brokenness of Appveyor's msys2 by adding the new
- # maintainer's keyring.
- - bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz"
- - bash -lc "curl -O http://repo.msys2.org/msys/x86_64/msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig"
- - bash -lc "pacman-key --verify msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz.sig"
- - bash -lc "pacman -U --noconfirm msys2-keyring-r21.b39fb11-1-any.pkg.tar.xz"
- # Support for Ada and Objective-C was removed from MSYS2. GCC won't update if
- # these packages are installed.
- - >-
- C:\msys64\usr\bin\pacman -R --noconfirm --noprogressbar
- mingw-w64-i686-gcc-ada mingw-w64-i686-gcc-objc mingw-w64-x86_64-gcc-ada
- mingw-w64-x86_64-gcc-objc
+ # Disable checking disk space to speed up install time
+ - C:\msys64\usr\bin\sed -i 's/^CheckSpace/#CheckSpace/g' /etc/pacman.conf
# Update core packages
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm --noprogressbar --ask=20
# Explicitly kill any remaining msys2 processes after core update
diff --git a/audio/aframe.c b/audio/aframe.c
index fc5d73b..c2c0df7 100644
--- a/audio/aframe.c
+++ b/audio/aframe.c
@@ -635,18 +635,24 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
AVFrame *av_frame = frame->av_frame;
if (av_frame->extended_data != av_frame->data)
av_freep(&av_frame->extended_data); // sigh
- av_frame->extended_data =
- av_mallocz_array(planes, sizeof(av_frame->extended_data[0]));
- if (!av_frame->extended_data)
- abort();
+ if (planes > AV_NUM_DATA_POINTERS) {
+ av_frame->extended_data =
+ av_mallocz_array(planes, sizeof(av_frame->extended_data[0]));
+ if (!av_frame->extended_data)
+ abort();
+ } else {
+ av_frame->extended_data = av_frame->data;
+ }
av_frame->buf[0] = av_buffer_pool_get(pool->avpool);
if (!av_frame->buf[0])
return -1;
av_frame->linesize[0] = samples * sstride;
for (int n = 0; n < planes; n++)
av_frame->extended_data[n] = av_frame->buf[0]->data + n * plane_size;
- for (int n = 0; n < MPMIN(planes, AV_NUM_DATA_POINTERS); n++)
- av_frame->data[n] = av_frame->extended_data[n];
+ if (planes > AV_NUM_DATA_POINTERS) {
+ for (int n = 0; n < AV_NUM_DATA_POINTERS; n++)
+ av_frame->data[n] = av_frame->extended_data[n];
+ }
av_frame->nb_samples = samples;
return 0;
diff --git a/audio/decode/ad_lavc.c b/audio/decode/ad_lavc.c
index eaaf072..228054e 100644
--- a/audio/decode/ad_lavc.c
+++ b/audio/decode/ad_lavc.c
@@ -85,7 +85,7 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
struct ad_lavc_params *opts =
mp_get_config_group(ctx, da->global, &ad_lavc_conf);
AVCodecContext *lavc_context;
- AVCodec *lavc_codec;
+ const AVCodec *lavc_codec;
ctx->codec_timebase = mp_get_codec_timebase(codec);
diff --git a/audio/decode/ad_spdif.c b/audio/decode/ad_spdif.c
index 0b19777..520803c 100644
--- a/audio/decode/ad_spdif.c
+++ b/audio/decode/ad_spdif.c
@@ -116,7 +116,7 @@ static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
if (profile != FF_PROFILE_UNKNOWN || spdif_ctx->codec_id != AV_CODEC_ID_DTS)
return;
- AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
+ const AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
if (!codec)
goto done;
diff --git a/audio/filter/af_lavcac3enc.c b/audio/filter/af_lavcac3enc.c
index 38f93a1..45e0aa6 100644
--- a/audio/filter/af_lavcac3enc.c
+++ b/audio/filter/af_lavcac3enc.c
@@ -68,7 +68,7 @@ struct priv {
struct mp_aframe *in_frame;
struct mp_aframe_pool *out_pool;
- struct AVCodec *lavc_acodec;
+ const struct AVCodec *lavc_acodec;
struct AVCodecContext *lavc_actx;
int bit_rate;
int out_samples; // upper bound on encoded output per AC3 frame
diff --git a/audio/filter/af_scaletempo2_internals.c b/audio/filter/af_scaletempo2_internals.c
index e348cb3..1cee7e4 100644
--- a/audio/filter/af_scaletempo2_internals.c
+++ b/audio/filter/af_scaletempo2_internals.c
@@ -4,6 +4,8 @@
#include "audio/chmap.h"
#include "audio/filter/af_scaletempo2_internals.h"
+#include "config.h"
+
// Algorithm overview (from chromium):
// Waveform Similarity Overlap-and-add (WSOLA).
//
@@ -104,6 +106,10 @@ static float multi_channel_similarity_measure(
return similarity_measure;
}
+#if HAVE_VECTOR
+
+typedef float v8sf __attribute__ ((vector_size (32), aligned (1)));
+
// Dot-product of channels of two AudioBus. For each AudioBus an offset is
// given. |dot_product[k]| is the dot-product of channel |k|. The caller should
// allocate sufficient space for |dot_product|.
@@ -116,16 +122,79 @@ static void multi_channel_dot_product(
assert(frame_offset_a >= 0);
assert(frame_offset_b >= 0);
- memset(dot_product, 0, sizeof(*dot_product) * channels);
for (int k = 0; k < channels; ++k) {
const float* ch_a = a[k] + frame_offset_a;
const float* ch_b = b[k] + frame_offset_b;
- for (int n = 0; n < num_frames; ++n) {
- dot_product[k] += *ch_a++ * *ch_b++;
+ float sum = 0.0;
+ if (num_frames < 32)
+ goto rest;
+
+ const v8sf *va = (const v8sf *) ch_a;
+ const v8sf *vb = (const v8sf *) ch_b;
+ v8sf vsum[4] = {
+ // Initialize to product of first 32 floats
+ va[0] * vb[0],
+ va[1] * vb[1],
+ va[2] * vb[2],
+ va[3] * vb[3],
+ };
+ va += 4;
+ vb += 4;
+
+ // Process `va` and `vb` across four vertical stripes
+ for (int n = 1; n < num_frames / 32; n++) {
+ vsum[0] += va[0] * vb[0];
+ vsum[1] += va[1] * vb[1];
+ vsum[2] += va[2] * vb[2];
+ vsum[3] += va[3] * vb[3];
+ va += 4;
+ vb += 4;
}
+
+ // Vertical sum across `vsum` entries
+ vsum[0] += vsum[1];
+ vsum[2] += vsum[3];
+ vsum[0] += vsum[2];
+
+ // Horizontal sum across `vsum[0]`, could probably be done better but
+ // this section is not super performance critical
+ float *vf = (float *) &vsum[0];
+ sum = vf[0] + vf[1] + vf[2] + vf[3] + vf[4] + vf[5] + vf[6] + vf[7];
+ ch_a = (const float *) va;
+ ch_b = (const float *) vb;
+
+rest:
+ // Process the remainder
+ for (int n = 0; n < num_frames % 32; n++)
+ sum += *ch_a++ * *ch_b++;
+
+ dot_product[k] = sum;
+ }
+}
+
+#else // !HAVE_VECTOR
+
+static void multi_channel_dot_product(
+ float **a, int frame_offset_a,
+ float **b, int frame_offset_b,
+ int channels,
+ int num_frames, float *dot_product)
+{
+ assert(frame_offset_a >= 0);
+ assert(frame_offset_b >= 0);
+
+ for (int k = 0; k < channels; ++k) {
+ const float* ch_a = a[k] + frame_offset_a;
+ const float* ch_b = b[k] + frame_offset_b;
+ float sum = 0.0;
+ for (int n = 0; n < num_frames; n++)
+ sum += *ch_a++ * *ch_b++;
+ dot_product[k] = sum;
}
}
+#endif // HAVE_VECTOR
+
// Fit the curve f(x) = a * x^2 + b * x + c such that
// f(-1) = y[0]
// f(0) = y[1]
@@ -389,10 +458,8 @@ static int write_completed_frames_to(struct mp_scaletempo2 *p,
static bool can_perform_wsola(struct mp_scaletempo2 *p)
{
- const int search_block_size = p->num_candidate_blocks
- + (p->ola_window_size - 1);
return p->target_block_index + p->ola_window_size <= p->input_buffer_frames
- && p->search_block_index + search_block_size <= p->input_buffer_frames;
+ && p->search_block_index + p->search_block_size <= p->input_buffer_frames;
}
// number of frames needed until a wsola iteration can be performed
@@ -403,6 +470,14 @@ static int frames_needed(struct mp_scaletempo2 *p)
p->search_block_index + p->search_block_size - p->input_buffer_frames));
}
+static void resize_input_buffer(struct mp_scaletempo2 *p, int size)
+{
+ if (size > p->input_buffer_size) {
+ p->input_buffer_size = size;
+ p->input_buffer = realloc_2d(p->input_buffer, p->channels, size);
+ }
+}
+
int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
uint8_t **planes, int frame_size, bool final)
{
@@ -411,7 +486,8 @@ int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
int total_fill = final ? needed : read;
if (total_fill == 0) return 0;
- assert(total_fill + p->input_buffer_frames <= p->input_buffer_size);
+ int required_size = total_fill + p->input_buffer_frames;
+ resize_input_buffer(p, required_size);
for (int i = 0; i < p->channels; ++i) {
memcpy(p->input_buffer[i] + p->input_buffer_frames,
@@ -427,11 +503,9 @@ int mp_scaletempo2_fill_input_buffer(struct mp_scaletempo2 *p,
static bool target_is_within_search_region(struct mp_scaletempo2 *p)
{
- const int search_block_size = p->num_candidate_blocks + (p->ola_window_size - 1);
-
return p->target_block_index >= p->search_block_index
&& p->target_block_index + p->ola_window_size
- <= p->search_block_index + search_block_size;
+ <= p->search_block_index + p->search_block_size;
}
@@ -715,8 +789,7 @@ void mp_scaletempo2_init(struct mp_scaletempo2 *p, int channels, int rate)
p->search_block = realloc_2d(p->search_block, p->channels, p->search_block_size);
p->target_block = realloc_2d(p->target_block, p->channels, p->ola_window_size);
- p->input_buffer_size = 4 * MPMAX(p->ola_window_size, p->search_block_size);
- p->input_buffer = realloc_2d(p->input_buffer, p->channels, p->input_buffer_size);
+ resize_input_buffer(p, 4 * MPMAX(p->ola_window_size, p->search_block_size));
p->input_buffer_frames = 0;
p->energy_candidate_blocks = realloc(p->energy_candidate_blocks,
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 52a38b6..7c347cb 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -35,6 +35,7 @@
#include "common/common.h"
#include "common/global.h"
+extern const struct ao_driver audio_out_oss;
extern const struct ao_driver audio_out_audiotrack;
extern const struct ao_driver audio_out_audiounit;
extern const struct ao_driver audio_out_coreaudio;
@@ -71,6 +72,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_WASAPI
&audio_out_wasapi,
#endif
+#if HAVE_OSS_AUDIO
+ &audio_out_oss,
+#endif
// wrappers:
#if HAVE_JACK
&audio_out_jack,
diff --git a/audio/out/ao_audiotrack.c b/audio/out/ao_audiotrack.c
index 7a1715d..fbbdaa1 100644
--- a/audio/out/ao_audiotrack.c
+++ b/audio/out/ao_audiotrack.c
@@ -82,6 +82,7 @@ struct JNIByteBuffer {
struct JNIAudioTrack {
jclass clazz;
jmethodID ctor;
+ jmethodID ctorV21;
jmethodID release;
jmethodID getState;
jmethodID getPlayState;
@@ -94,6 +95,7 @@ struct JNIAudioTrack {
jmethodID writeV23;
jmethodID writeShortV23;
jmethodID writeBufferV21;
+ jmethodID getBufferSizeInFramesV23;
jmethodID getPlaybackHeadPosition;
jmethodID getTimestamp;
jmethodID getLatency;
@@ -114,6 +116,7 @@ struct JNIAudioTrack {
#define OFFSET(member) offsetof(struct JNIAudioTrack, member)
{"android/media/AudioTrack", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
{"android/media/AudioTrack", "<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1},
+ {"android/media/AudioTrack", "<init>", "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;III)V", MP_JNI_METHOD, OFFSET(ctorV21), 0},
{"android/media/AudioTrack", "release", "()V", MP_JNI_METHOD, OFFSET(release), 1},
{"android/media/AudioTrack", "getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1},
{"android/media/AudioTrack", "getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1},
@@ -126,6 +129,7 @@ struct JNIAudioTrack {
{"android/media/AudioTrack", "write", "([BIII)I", MP_JNI_METHOD, OFFSET(writeV23), 0},
{"android/media/AudioTrack", "write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0},
{"android/media/AudioTrack", "write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1},
+ {"android/media/AudioTrack", "getBufferSizeInFrames", "()I", MP_JNI_METHOD, OFFSET(getBufferSizeInFramesV23), 0},
{"android/media/AudioTrack", "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1},
{"android/media/AudioTrack", "getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1},
{"android/media/AudioTrack", "getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1},
@@ -145,6 +149,38 @@ struct JNIAudioTrack {
#undef OFFSET
}};
+struct JNIAudioAttributes {
+ jclass clazz;
+ jint CONTENT_TYPE_MOVIE;
+ jint USAGE_MEDIA;
+ struct MPJniField mapping[];
+} AudioAttributes = {.mapping = {
+ #define OFFSET(member) offsetof(struct JNIAudioAttributes, member)
+ {"android/media/AudioAttributes", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
+ {"android/media/AudioAttributes", "CONTENT_TYPE_MOVIE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CONTENT_TYPE_MOVIE), 0},
+ {"android/media/AudioAttributes", "USAGE_MEDIA", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(USAGE_MEDIA), 0},
+ {0}
+ #undef OFFSET
+}};
+
+struct JNIAudioAttributesBuilder {
+ jclass clazz;
+ jmethodID ctor;
+ jmethodID setUsage;
+ jmethodID setContentType;
+ jmethodID build;
+ struct MPJniField mapping[];
+} AudioAttributesBuilder = {.mapping = {
+ #define OFFSET(member) offsetof(struct JNIAudioAttributesBuilder, member)
+ {"android/media/AudioAttributes$Builder", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
+ {"android/media/AudioAttributes$Builder", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0},
+ {"android/media/AudioAttributes$Builder", "setUsage", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setUsage), 0},
+ {"android/media/AudioAttributes$Builder", "setContentType", "(I)Landroid/media/AudioAttributes$Builder;", MP_JNI_METHOD, OFFSET(setContentType), 0},
+ {"android/media/AudioAttributes$Builder", "build", "()Landroid/media/AudioAttributes;", MP_JNI_METHOD, OFFSET(build), 0},
+ {0}
+ #undef OFFSET
+}};
+
struct JNIAudioFormat {
jclass clazz;
jint ENCODING_PCM_8BIT;
@@ -189,6 +225,27 @@ struct JNIAudioFormat {
#undef OFFSET
}};
+struct JNIAudioFormatBuilder {
+ jclass clazz;
+ jmethodID ctor;
+ jmethodID setEncoding;
+ jmethodID setSampleRate;
+ jmethodID setChannelMask;
+ jmethodID build;
+ struct MPJniField mapping[];
+} AudioFormatBuilder = {.mapping = {
+ #define OFFSET(member) offsetof(struct JNIAudioFormatBuilder, member)
+ {"android/media/AudioFormat$Builder", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 0},
+ {"android/media/AudioFormat$Builder", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 0},
+ {"android/media/AudioFormat$Builder", "setEncoding", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setEncoding), 0},
+ {"android/media/AudioFormat$Builder", "setSampleRate", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setSampleRate), 0},
+ {"android/media/AudioFormat$Builder", "setChannelMask", "(I)Landroid/media/AudioFormat$Builder;", MP_JNI_METHOD, OFFSET(setChannelMask), 0},
+ {"android/media/AudioFormat$Builder", "build", "()Landroid/media/AudioFormat;", MP_JNI_METHOD, OFFSET(build), 0},
+ {0}
+ #undef OFFSET
+}};
+
+
struct JNIAudioManager {
jclass clazz;
jint ERROR_DEAD_OBJECT;
@@ -219,22 +276,64 @@ struct JNIAudioTimestamp {
#undef OFFSET
}};
+#define MP_JNI_DELETELOCAL(o) (*env)->DeleteLocalRef(env, o)
+
static int AudioTrack_New(struct ao *ao)
{
struct priv *p = ao->priv;
JNIEnv *env = MP_JNI_GET_ENV(ao);
+ jobject audiotrack = NULL;
- jobject audiotrack = MP_JNI_NEW(
- AudioTrack.clazz,
- AudioTrack.ctor,
- AudioManager.STREAM_MUSIC,
- p->samplerate,
- p->channel_config,
- p->format,
- p->size,
- AudioTrack.MODE_STREAM,
- p->cfg_session_id
- );
+ if (AudioTrack.ctorV21) {
+ MP_VERBOSE(ao, "Using API21 initializer\n");
+ jobject tmp = NULL;
+
+ jobject format_builder = MP_JNI_NEW(AudioFormatBuilder.clazz, AudioFormatBuilder.ctor);
+ MP_JNI_EXCEPTION_LOG(ao);
+ tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setEncoding, p->format);
+ MP_JNI_DELETELOCAL(tmp);
+ tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setSampleRate, p->samplerate);
+ MP_JNI_DELETELOCAL(tmp);
+ tmp = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.setChannelMask, p->channel_config);
+ MP_JNI_DELETELOCAL(tmp);
+ jobject format = MP_JNI_CALL_OBJECT(format_builder, AudioFormatBuilder.build);
+ MP_JNI_DELETELOCAL(format_builder);
+
+ jobject attr_builder = MP_JNI_NEW(AudioAttributesBuilder.clazz, AudioAttributesBuilder.ctor);
+ MP_JNI_EXCEPTION_LOG(ao);
+ tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setUsage, AudioAttributes.USAGE_MEDIA);
+ MP_JNI_DELETELOCAL(tmp);
+ tmp = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.setContentType, AudioAttributes.CONTENT_TYPE_MOVIE);
+ MP_JNI_DELETELOCAL(tmp);
+ jobject attr = MP_JNI_CALL_OBJECT(attr_builder, AudioAttributesBuilder.build);
+ MP_JNI_DELETELOCAL(attr_builder);
+
+ audiotrack = MP_JNI_NEW(
+ AudioTrack.clazz,
+ AudioTrack.ctorV21,
+ attr,
+ format,
+ p->size,
+ AudioTrack.MODE_STREAM,
+ p->cfg_session_id
+ );
+
+ MP_JNI_DELETELOCAL(format);
+ MP_JNI_DELETELOCAL(attr);
+ } else {
+ MP_VERBOSE(ao, "Using legacy initializer\n");
+ audiotrack = MP_JNI_NEW(
+ AudioTrack.clazz,
+ AudioTrack.ctor,
+ AudioManager.STREAM_MUSIC,
+ p->samplerate,
+ p->channel_config,
+ p->format,
+ p->size,
+ AudioTrack.MODE_STREAM,
+ p->cfg_session_id
+ );
+ }
if (!audiotrack || MP_JNI_EXCEPTION_LOG(ao) < 0) {
MP_FATAL(ao, "AudioTrack Init failed\n");
return -1;
@@ -248,6 +347,14 @@ static int AudioTrack_New(struct ao *ao)
return -1;
}
+ if (AudioTrack.getBufferSizeInFramesV23) {
+ int bufferSize = MP_JNI_CALL_INT(audiotrack, AudioTrack.getBufferSizeInFramesV23);
+ if (bufferSize > 0) {
+ MP_VERBOSE(ao, "AudioTrack.getBufferSizeInFrames = %d\n", bufferSize);
+ ao->device_buffer = bufferSize;
+ }
+ }
+
p->audiotrack = (*env)->NewGlobalRef(env, audiotrack);
(*env)->DeleteLocalRef(env, audiotrack);
if (!p->audiotrack)
@@ -372,12 +479,12 @@ static double AudioTrack_getLatency(struct ao *ao)
if (!p->timestamp_set &&
p->format != AudioFormat.ENCODING_IEC61937)
delay += (double)MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getLatency)/1000.0;
- if (delay > 1.0) {
+ if (delay > 2.0) {
//MP_WARN(ao, "getLatency: written=%u playhead=%u diff=%u delay=%f\n", p->written_frames, playhead, diff, delay);
p->timestamp_fetched = 0;
return 0;
}
- return MPCLAMP(delay, 0.0, 1.0);
+ return MPCLAMP(delay, 0.0, 2.0);
}
static int AudioTrack_write(struct ao *ao, int len)
@@ -427,6 +534,9 @@ static void uninit_jni(struct ao *ao)
mp_jni_reset_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log);
mp_jni_reset_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log);
mp_jni_reset_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log);
+ mp_jni_reset_jfields(env, &AudioFormatBuilder, AudioFormatBuilder.mapping, 1, ao->log);
+ mp_jni_reset_jfields(env, &AudioAttributes, AudioAttributes.mapping, 1, ao->log);
+ mp_jni_reset_jfields(env, &AudioAttributesBuilder, AudioAttributesBuilder.mapping, 1, ao->log);
mp_jni_reset_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log);
}
@@ -437,6 +547,9 @@ static int init_jni(struct ao *ao)
mp_jni_init_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log) < 0 ||
mp_jni_init_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log) < 0 ||
mp_jni_init_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log) < 0 ||
+ mp_jni_init_jfields(env, &AudioAttributes, AudioAttributes.mapping, 1, ao->log) < 0 ||
+ mp_jni_init_jfields(env, &AudioAttributesBuilder, AudioAttributesBuilder.mapping, 1, ao->log) < 0 ||
+ mp_jni_init_jfields(env, &AudioFormatBuilder, AudioFormatBuilder.mapping, 1, ao->log) < 0 ||
mp_jni_init_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log) < 0) {
uninit_jni(ao);
return -1;
@@ -607,15 +720,16 @@ static int init(struct ao *ao)
MP_FATAL(ao, "AudioTrack.getMinBufferSize returned an invalid size: %d", buffer_size);
return -1;
}
- p->chunksize = buffer_size;
- p->chunk = malloc(buffer_size);
- int min = 0.200 * p->samplerate * af_fmt_to_bytes(ao->format);
- int max = min * 3 / 2;
+ int min = 0.075 * p->samplerate * af_fmt_to_bytes(ao->format) * ao->channels.num;
+ int max = min * 2;
p->size = MPCLAMP(buffer_size * 2, min, max);
MP_VERBOSE(ao, "Setting bufferSize = %d (driver=%d, min=%d, max=%d)\n", p->size, buffer_size, min, max);
ao->device_buffer = p->size / af_fmt_to_bytes(ao->format);
+ p->chunksize = p->size;
+ p->chunk = malloc(p->size);
+
jobject timestamp = MP_JNI_NEW(AudioTimestamp.clazz, AudioTimestamp.ctor);
if (!timestamp || MP_JNI_EXCEPTION_LOG(ao) < 0) {
MP_FATAL(ao, "AudioTimestamp could not be created\n");
diff --git a/audio/out/ao_oss.c b/audio/out/ao_oss.c
new file mode 100644
index 0000000..11b182e
--- /dev/null
+++ b/audio/out/ao_oss.c
@@ -0,0 +1,410 @@
+/*
+ * OSS audio output driver
+ *
+ * Original author: A'rpi
+ * Support for >2 output channels added 2001-11-25
+ * - Steve Davies <steve@daviesfam.org>
+ * Rozhuk Ivan <rozhuk.im@gmail.com> 2020
+ *
+ * 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 <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+#include <sys/stat.h>
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+#include <sys/sysctl.h>
+#endif
+#include <sys/types.h>
+
+#include "config.h"
+#include "audio/format.h"
+#include "common/msg.h"
+#include "options/options.h"
+#include "osdep/endian.h"
+#include "osdep/io.h"
+#include "ao.h"
+#include "internal.h"
+
+#ifndef AFMT_AC3
+#define AFMT_AC3 -1
+#endif
+
+#define PATH_DEV_DSP "/dev/dsp"
+
+struct priv {
+ int dsp_fd;
+ bool playing;
+ double bps; /* Bytes per second. */
+};
+
+/* like alsa except for 6.1 and 7.1, from pcm/matrix_map.h */
+static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
+ {0}, /* empty */
+ MP_CHMAP_INIT_MONO, /* mono */
+ MP_CHMAP2(FL, FR), /* stereo */
+ MP_CHMAP3(FL, FR, LFE), /* 2.1 */
+ MP_CHMAP4(FL, FR, BL, BR), /* 4.0 */
+ MP_CHMAP5(FL, FR, BL, BR, FC), /* 5.0 */
+ MP_CHMAP6(FL, FR, BL, BR, FC, LFE), /* 5.1 */
+ MP_CHMAP7(FL, FR, BL, BR, FC, LFE, BC), /* 6.1 */
+ MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), /* 7.1 */
+};
+
+#if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
+#define AFMT_S32_NE AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
+#endif
+
+static const int format_table[][2] = {
+ {AFMT_U8, AF_FORMAT_U8},
+ {AFMT_S16_NE, AF_FORMAT_S16},
+#ifdef AFMT_S32_NE
+ {AFMT_S32_NE, AF_FORMAT_S32},
+#endif
+#ifdef AFMT_FLOAT
+ {AFMT_FLOAT, AF_FORMAT_FLOAT},
+#endif
+#ifdef AFMT_MPEG
+ {AFMT_MPEG, AF_FORMAT_S_MP3},
+#endif
+ {-1, -1}
+};
+
+#define MP_WARN_IOCTL_ERR(__ao) \
+ MP_WARN((__ao), "%s: ioctl() fail, err = %i: %s\n", \
+ __FUNCTION__, errno, strerror(errno))
+
+
+static void uninit(struct ao *ao);
+
+
+static void device_descr_get(size_t dev_idx, char *buf, size_t buf_size)
+{
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+ char dev_path[32];
+ size_t tmp = (buf_size - 1);
+
+ snprintf(dev_path, sizeof(dev_path), "dev.pcm.%zu.%%desc", dev_idx);
+ if (sysctlbyname(dev_path, buf, &tmp, NULL, 0) != 0) {
+ tmp = 0;
+ }
+ buf[tmp] = 0x00;
+#elif defined(SOUND_MIXER_INFO)
+ size_t tmp = 0;
+ char dev_path[32];
+ mixer_info mi;
+
+ snprintf(dev_path, sizeof(dev_path), PATH_DEV_MIXER"%zu", dev_idx);
+ int fd = open(dev_path, O_RDONLY);
+ if (ioctl(fd, SOUND_MIXER_INFO, &mi) == 0) {
+ strncpy(buf, mi.name, buf_size);
+ tmp = (buf_size - 1);
+ }
+ close(fd);
+ buf[tmp] = 0x00;
+#else
+ buf[0] = 0x00;
+#endif
+}
+
+static int format2oss(int format)
+{
+ for (size_t i = 0; format_table[i][0] != -1; i++) {
+ if (format_table[i][1] == format)
+ return format_table[i][0];
+ }
+ return -1;
+}
+
+static bool try_format(struct ao *ao, int *format)
+{
+ struct priv *p = ao->priv;
+ int oss_format = format2oss(*format);
+
+ if (oss_format == -1 && af_fmt_is_spdif(*format))
+ oss_format = AFMT_AC3;
+
+ if (oss_format == -1) {
+ MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n",
+ af_fmt_to_str(*format));
+ *format = 0;
+ return false;
+ }
+
+ return (ioctl(p->dsp_fd, SNDCTL_DSP_SETFMT, &oss_format) != -1);
+}
+
+static int init(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ struct mp_chmap channels = ao->channels;
+ audio_buf_info info;
+ size_t i;
+ int format, samplerate, nchannels, reqchannels, trig = 0;
+ int best_sample_formats[AF_FORMAT_COUNT + 1];
+ const char *device = ((ao->device) ? ao->device : PATH_DEV_DSP);
+
+ /* Opening device. */
+ MP_VERBOSE(ao, "Using '%s' audio device.\n", device);
+ p->dsp_fd = open(device, (O_WRONLY | O_CLOEXEC));
+ if (p->dsp_fd < 0) {
+ MP_ERR(ao, "Can't open audio device %s: %s.\n",
+ device, mp_strerror(errno));
+ goto err_out;
+ }
+
+ /* Selecting sound format. */
+ format = af_fmt_from_planar(ao->format);
+ af_get_best_sample_formats(format, best_sample_formats);
+ for (i = 0; best_sample_formats[i]; i++) {
+ format = best_sample_formats[i];
+ if (try_format(ao, &format))
+ break;
+ }
+ if (!format) {
+ MP_ERR(ao, "Can't set sample format.\n");
+ goto err_out;
+ }
+ MP_VERBOSE(ao, "Sample format: %s\n", af_fmt_to_str(format));
+
+ /* Channels count. */
+ if (af_fmt_is_spdif(format)) {
+ /* Probably could be fixed by setting number of channels;
+ * needs testing. */
+ if (channels.num != 2) {
+ MP_ERR(ao, "Format %s not implemented.\n", af_fmt_to_str(format));
+ goto err_out;
+ }
+ } else {
+ struct mp_chmap_sel sel = {0};
+ for (i = 0; i < MP_ARRAY_SIZE(oss_layouts); i++) {
+ mp_chmap_sel_add_map(&sel, &oss_layouts[i]);
+ }
+ if (!ao_chmap_sel_adjust(ao, &sel, &channels))
+ goto err_out;
+ nchannels = reqchannels = channels.num;
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_CHANNELS, &nchannels) == -1) {
+ MP_ERR(ao, "Failed to set audio device to %d channels.\n",
+ reqchannels);
+ goto err_out_ioctl;
+ }
+ if (nchannels != reqchannels) {
+ /* Update number of channels to OSS suggested value. */
+ if (!ao_chmap_sel_get_def(ao, &sel, &channels, nchannels))
+ goto err_out;
+ }
+ MP_VERBOSE(ao, "Using %d channels (requested: %d).\n",
+ channels.num, reqchannels);
+ }
+
+ /* Sample rate. */
+ samplerate = ao->samplerate;
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
+ goto err_out_ioctl;
+ MP_VERBOSE(ao, "Using %d Hz samplerate.\n", samplerate);
+
+ /* Get buffer size. */
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_GETOSPACE, &info) == -1)
+ goto err_out_ioctl;
+ /* See ao.c ao->sstride initializations and get_state(). */
+ ao->device_buffer = ((info.fragstotal * info.fragsize) /
+ af_fmt_to_bytes(format));
+ if (!af_fmt_is_planar(format)) {
+ ao->device_buffer /= channels.num;
+ }
+
+ /* Do not start playback after data written. */
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1)
+ goto err_out_ioctl;
+
+ /* Update sound params. */
+ ao->format = format;
+ ao->samplerate = samplerate;
+ ao->channels = channels;
+ p->bps = (channels.num * samplerate * af_fmt_to_bytes(format));
+ p->playing = false;
+
+ return 0;
+
+err_out_ioctl:
+ MP_WARN_IOCTL_ERR(ao);
+err_out:
+ uninit(ao);
+ return -1;
+}
+
+static void uninit(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+
+ if (p->dsp_fd == -1)
+ return;
+ ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL);
+ close(p->dsp_fd);
+ p->dsp_fd = -1;
+ p->playing = false;
+}
+
+static int control(struct ao *ao, enum aocontrol cmd, void *arg)
+{
+ struct priv *p = ao->priv;
+ ao_control_vol_t *vol = (ao_control_vol_t *)arg;
+ int v;
+
+ if (p->dsp_fd < 0)
+ return CONTROL_ERROR;
+
+ switch (cmd) {
+ case AOCONTROL_GET_VOLUME:
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_GETPLAYVOL, &v) == -1) {
+ MP_WARN_IOCTL_ERR(ao);
+ return CONTROL_ERROR;
+ }
+ vol->right = ((v & 0xff00) >> 8);
+ vol->left = (v & 0x00ff);
+ return CONTROL_OK;
+ case AOCONTROL_SET_VOLUME:
+ v = ((int)vol->right << 8) | (int)vol->left;
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_SETPLAYVOL, &v) == -1) {
+ MP_WARN_IOCTL_ERR(ao);
+ return CONTROL_ERROR;
+ }
+ return CONTROL_OK;
+ }
+
+ return CONTROL_UNKNOWN;
+}
+
+static void reset(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ int trig = 0;
+
+ /* Clear buf and do not start playback after data written. */
+ p->playing = false;
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_HALT, NULL) == -1 ||
+ ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1)
+ {
+ MP_WARN_IOCTL_ERR(ao);
+ MP_WARN(ao, "Force reinitialize audio device.\n");
+ uninit(ao);
+ init(ao);
+ }
+}
+
+static void start(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ int trig = PCM_ENABLE_OUTPUT;
+
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
+ MP_WARN_IOCTL_ERR(ao);
+ return;
+ }
+ p->playing = true;
+}
+
+static bool audio_write(struct ao *ao, void **data, int samples)
+{
+ struct priv *p = ao->priv;
+ ssize_t rc;
+ const size_t size = (samples * ao->sstride);
+
+ if (size == 0)
+ return true;
+
+ while ((rc = write(p->dsp_fd, data[0], size)) == -1) {
+ if (errno == EINTR)
+ continue;
+ MP_WARN(ao, "audio_write: write() fail, err = %i: %s.\n",
+ errno, strerror(errno));
+ p->playing = false;
+ return false;
+ }
+ if ((size_t)rc != size) {
+ MP_WARN(ao, "audio_write: unexpected partial write: required: %zu, written: %zu.\n",
+ size, (size_t)rc);
+ p->playing = false;
+ return false;
+ }
+
+ return true;
+}
+
+static void get_state(struct ao *ao, struct mp_pcm_state *state)
+{
+ struct priv *p = ao->priv;
+ audio_buf_info info;
+ int odelay;
+
+ if (ioctl(p->dsp_fd, SNDCTL_DSP_GETOSPACE, &info) == -1 ||
+ ioctl(p->dsp_fd, SNDCTL_DSP_GETODELAY, &odelay) == -1)
+ {
+ MP_WARN_IOCTL_ERR(ao);
+ p->playing = false;
+ memset(state, 0x00, sizeof(struct mp_pcm_state));
+ state->delay = 0.0;
+ return;
+ }
+ state->free_samples = (info.bytes / ao->sstride);
+ state->queued_samples = (ao->device_buffer - state->free_samples);
+ state->delay = (odelay / p->bps);
+ state->playing = p->playing;
+}
+
+static void list_devs(struct ao *ao, struct ao_device_list *list)
+{
+ struct stat st;
+ char dev_path[32] = PATH_DEV_DSP, dev_descr[256] = "Default";
+ struct ao_device_desc dev = {.name = dev_path, .desc = dev_descr};
+
+ if (stat(PATH_DEV_DSP, &st) == 0) {
+ ao_device_list_add(list, ao, &dev);
+ }
+
+ /* Auto detect. */
+ for (size_t i = 0, fail_cnt = 0; fail_cnt < 8; i ++, fail_cnt ++) {
+ snprintf(dev_path, sizeof(dev_path), PATH_DEV_DSP"%zu", i);
+ if (stat(dev_path, &st) != 0)
+ continue;
+ device_descr_get(i, dev_descr, sizeof(dev_descr));
+ ao_device_list_add(list, ao, &dev);
+ fail_cnt = 0; /* Reset fail counter. */
+ }
+}
+
+const struct ao_driver audio_out_oss = {
+ .name = "oss",
+ .description = "OSS/ioctl audio output",
+ .init = init,
+ .uninit = uninit,
+ .control = control,
+ .reset = reset,
+ .start = start,
+ .write = audio_write,
+ .get_state = get_state,
+ .list_devs = list_devs,
+ .priv_size = sizeof(struct priv),
+ .priv_defaults = &(const struct priv) {
+ .dsp_fd = -1,
+ .playing = false,
+ },
+};
diff --git a/audio/out/ao_pulse.c b/audio/out/ao_pulse.c
index b09d262..6c536b0 100644
--- a/audio/out/ao_pulse.c
+++ b/audio/out/ao_pulse.c
@@ -535,6 +535,7 @@ static void reset(struct ao *ao)
cork(ao, true);
struct priv *priv = ao->priv;
pa_threaded_mainloop_lock(priv->mainloop);
+ priv->playing = false;
priv->retval = 0;
if (!waitop(priv, pa_stream_flush(priv->stream, success_cb, ao)) ||
!priv->retval)
@@ -696,9 +697,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
case AOCONTROL_SET_MUTE:
case AOCONTROL_SET_VOLUME: {
- pa_operation *o;
-
pa_threaded_mainloop_lock(priv->mainloop);
+ priv->retval = 0;
uint32_t stream_index = pa_stream_get_index(priv->stream);
if (cmd == AOCONTROL_SET_VOLUME) {
const ao_control_vol_t *vol = arg;
@@ -711,27 +711,26 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
volume.values[0] = VOL_MP2PA(vol->left);
volume.values[1] = VOL_MP2PA(vol->right);
}
- o = pa_context_set_sink_input_volume(priv->context, stream_index,
- &volume, NULL, NULL);
- if (!o) {
- pa_threaded_mainloop_unlock(priv->mainloop);
+ if (!waitop(priv, pa_context_set_sink_input_volume(priv->context,
+ stream_index,
+ &volume,
+ context_success_cb, ao)) ||
+ !priv->retval) {
GENERIC_ERR_MSG("pa_context_set_sink_input_volume() failed");
return CONTROL_ERROR;
}
} else if (cmd == AOCONTROL_SET_MUTE) {
const bool *mute = arg;
- o = pa_context_set_sink_input_mute(priv->context, stream_index,
- *mute, NULL, NULL);
- if (!o) {
- pa_threaded_mainloop_unlock(priv->mainloop);
+ if (!waitop(priv, pa_context_set_sink_input_mute(priv->context,
+ stream_index,
+ *mute,
+ context_success_cb, ao)) ||
+ !priv->retval) {
GENERIC_ERR_MSG("pa_context_set_sink_input_mute() failed");
return CONTROL_ERROR;
}
} else
abort();
- /* We don't wait for completion here */
- pa_operation_unref(o);
- pa_threaded_mainloop_unlock(priv->mainloop);
return CONTROL_OK;
}
diff --git a/bootstrap.py b/bootstrap.py
index 9949183..51b81f5 100755
--- a/bootstrap.py
+++ b/bootstrap.py
@@ -2,8 +2,8 @@
# This script simply downloads waf to the current directory
-from __future__ import print_function
import os, sys, stat, hashlib, subprocess
+from urllib.request import urlopen, URLError
WAFRELEASE = "waf-2.0.20"
WAFURLS = ["https://waf.io/" + WAFRELEASE,
@@ -20,11 +20,6 @@ if "--no-download" in sys.argv[1:]:
print("Did not find {} and no download was requested.".format(WAFRELEASE))
sys.exit(1)
-try:
- from urllib.request import urlopen, URLError
-except:
- from urllib2 import urlopen, URLError
-
waf = None
for WAFURL in WAFURLS:
@@ -32,8 +27,8 @@ for WAFURL in WAFURLS:
print("Downloading {}...".format(WAFURL))
waf = urlopen(WAFURL).read()
break
- except URLError:
- print("Download failed.")
+ except URLError as err:
+ print("Download failed! ({})".format(err))
if not waf:
print("Could not download {}.".format(WAFRELEASE))
diff --git a/ci/build-mingw64.sh b/ci/build-mingw64.sh
index 574c5c3..f663345 100755
--- a/ci/build-mingw64.sh
+++ b/ci/build-mingw64.sh
@@ -99,7 +99,7 @@ fi
## freetype2
if [ ! -e "$prefix_dir/lib/libfreetype.dll.a" ]; then
- ver=2.10.2
+ ver=2.11.0
gettar "https://download.savannah.gnu.org/releases/freetype/freetype-${ver}.tar.gz"
builddir freetype-${ver}
ZLIB_LIBS="-L'$prefix_dir/lib' -lz" \
@@ -111,7 +111,7 @@ fi
## fribidi
if [ ! -e "$prefix_dir/lib/libfribidi.dll.a" ]; then
- ver=1.0.9
+ ver=1.0.11
gettar "https://github.com/fribidi/fribidi/releases/download/v${ver}/fribidi-${ver}.tar.xz"
builddir fribidi-${ver}
../configure --host=$TARGET $commonflags
@@ -121,7 +121,7 @@ fi
## harfbuzz
if [ ! -e "$prefix_dir/lib/libharfbuzz.dll.a" ]; then
- ver=2.7.2
+ ver=3.0.0
gettar "https://github.com/harfbuzz/harfbuzz/releases/download/${ver}/harfbuzz-${ver}.tar.xz"
builddir harfbuzz-${ver}
../configure --host=$TARGET $commonflags --with-icu=no
diff --git a/common/av_common.c b/common/av_common.c
index 01428ff..bd82d75 100644
--- a/common/av_common.c
+++ b/common/av_common.c
@@ -80,12 +80,23 @@ AVCodecParameters *mp_codec_params_to_av(struct mp_codec_params *c)
avp->codec_id = mp_codec_to_av_codec_id(c->codec);
avp->codec_tag = c->codec_tag;
if (c->extradata_size) {
- avp->extradata =
- av_mallocz(c->extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
+ uint8_t *extradata = c->extradata;
+ int size = c->extradata_size;
+
+ if (avp->codec_id == AV_CODEC_ID_FLAC) {
+ // ffmpeg expects FLAC extradata to be just the STREAMINFO,
+ // so grab only that (and assume it'll be the first block)
+ if (size >= 8 && !memcmp(c->extradata, "fLaC", 4)) {
+ extradata += 8;
+ size = MPMIN(34, size - 8); // FLAC_STREAMINFO_SIZE
+ }
+ }
+
+ avp->extradata = av_mallocz(size + AV_INPUT_BUFFER_PADDING_SIZE);
if (!avp->extradata)
goto error;
- avp->extradata_size = c->extradata_size;
- memcpy(avp->extradata, c->extradata, avp->extradata_size);
+ avp->extradata_size = size;
+ memcpy(avp->extradata, extradata, size);
}
avp->bits_per_coded_sample = c->bits_per_coded_sample;
@@ -270,7 +281,7 @@ int mp_codec_to_av_codec_id(const char *codec)
if (desc)
id = desc->id;
if (id == AV_CODEC_ID_NONE) {
- AVCodec *avcodec = avcodec_find_decoder_by_name(codec);
+ const AVCodec *avcodec = avcodec_find_decoder_by_name(codec);
if (avcodec)
id = avcodec->id;
}
@@ -285,7 +296,7 @@ const char *mp_codec_from_av_codec_id(int codec_id)
if (desc)
name = desc->name;
if (!name) {
- AVCodec *avcodec = avcodec_find_decoder(codec_id);
+ const AVCodec *avcodec = avcodec_find_decoder(codec_id);
if (avcodec)
name = avcodec->name;
}
diff --git a/common/encode_lavc.c b/common/encode_lavc.c
index 2af93d2..cf033e0 100644
--- a/common/encode_lavc.c
+++ b/common/encode_lavc.c
@@ -735,7 +735,7 @@ static void encoder_destroy(void *ptr)
free_stream(p->twopass_bytebuffer);
}
-static AVCodec *find_codec_for(struct encode_lavc_context *ctx,
+static const AVCodec *find_codec_for(struct encode_lavc_context *ctx,
enum stream_type type, bool *used_auto)
{
char *codec_name = type == STREAM_VIDEO
@@ -746,7 +746,7 @@ static AVCodec *find_codec_for(struct encode_lavc_context *ctx,
*used_auto = !(codec_name && codec_name[0]);
- AVCodec *codec;
+ const AVCodec *codec;
if (*used_auto) {
codec = avcodec_find_encoder(av_guess_codec(ctx->oformat, NULL,
ctx->options->file, NULL,
@@ -797,7 +797,7 @@ struct encoder_context *encoder_context_alloc(struct encode_lavc_context *ctx,
};
bool auto_codec;
- AVCodec *codec = find_codec_for(ctx, type, &auto_codec);
+ const AVCodec *codec = find_codec_for(ctx, type, &auto_codec);
const char *tname = stream_type_name(type);
if (!codec) {
diff --git a/common/encode_lavc.h b/common/encode_lavc.h
index 06e7254..cc4030a 100644
--- a/common/encode_lavc.h
+++ b/common/encode_lavc.h
@@ -41,7 +41,7 @@ struct encode_lavc_context {
struct encode_opts *options;
struct mp_log *log;
struct encode_priv *priv;
- AVOutputFormat *oformat;
+ const AVOutputFormat *oformat;
const char *filename;
// All entry points must be guarded with the lock. Functions called by
@@ -71,7 +71,7 @@ struct encoder_context {
struct mpv_global *global;
struct encode_opts *options;
struct mp_log *log;
- AVOutputFormat *oformat;
+ const AVOutputFormat *oformat;
// (avoid using this)
struct encode_lavc_context *encode_lavc_ctx;
diff --git a/common/msg.c b/common/msg.c
index 7e271d1..81c7f65 100644
--- a/common/msg.c
+++ b/common/msg.c
@@ -126,6 +126,8 @@ static void update_loglevel(struct mp_log *log)
struct mp_log_root *root = log->root;
pthread_mutex_lock(&root->lock);
log->level = MSGL_STATUS + root->verbose; // default log level
+ if (root->really_quiet)
+ log->level = -1;
for (int n = 0; root->msg_levels && root->msg_levels[n * 2 + 0]; n++) {
if (match_mod(log->verbose_prefix, root->msg_levels[n * 2 + 0]))
log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]);
@@ -143,8 +145,6 @@ static void update_loglevel(struct mp_log *log)
if (log->root->stats_file)
log->level = MPMAX(log->level, MSGL_STATS);
log->level = MPMIN(log->level, log->max_level);
- if (root->really_quiet)
- log->level = -1;
atomic_store(&log->reload_counter, atomic_load(&log->root->reload_counter));
pthread_mutex_unlock(&root->lock);
}
diff --git a/common/recorder.c b/common/recorder.c
index 58bf257..0cbc81d 100644
--- a/common/recorder.c
+++ b/common/recorder.c
@@ -23,6 +23,7 @@
#include "common/common.h"
#include "common/global.h"
#include "common/msg.h"
+#include "demux/demux.h"
#include "demux/packet.h"
#include "demux/stheader.h"
@@ -91,6 +92,12 @@ static int add_stream(struct mp_recorder *priv, struct sh_stream *sh)
if (!avp)
return -1;
+ // Check if we get the same codec_id for the output format;
+ // otherwise clear it to have a chance at muxing
+ if (av_codec_get_id(priv->mux->oformat->codec_tag,
+ avp->codec_tag) != avp->codec_id)
+ avp->codec_tag = 0;
+
// We don't know the delay, so make something up. If the format requires
// DTS, the result will probably be broken. FFmpeg provides nothing better
// yet (unless you demux with libavformat, which contains tons of hacks
@@ -113,7 +120,9 @@ static int add_stream(struct mp_recorder *priv, struct sh_stream *sh)
struct mp_recorder *mp_recorder_create(struct mpv_global *global,
const char *target_file,
struct sh_stream **streams,
- int num_streams)
+ int num_streams,
+ struct demux_attachment **attachments,
+ int num_attachments)
{
struct mp_recorder *priv = talloc_zero(NULL, struct mp_recorder);
@@ -147,6 +156,35 @@ struct mp_recorder *mp_recorder_create(struct mpv_global *global,
}
}
+ if (!strcmp(priv->mux->oformat->name, "matroska")) {
+ // Only attach attachments (fonts) to matroska - mp4, nut, mpegts don't
+ // like them, and we find that out too late in the muxing process.
+ AVStream *a_stream = NULL;
+ for (int i = 0; i < num_attachments; ++i) {
+ a_stream = avformat_new_stream(priv->mux, NULL);
+ if (!a_stream) {
+ MP_ERR(priv, "Can't mux one of the attachments.\n");
+ goto error;
+ }
+ struct demux_attachment *attachment = attachments[i];
+
+ a_stream->codecpar->codec_type = AVMEDIA_TYPE_ATTACHMENT;
+
+ a_stream->codecpar->extradata = av_mallocz(
+ attachment->data_size + AV_INPUT_BUFFER_PADDING_SIZE
+ );
+ if (!a_stream->codecpar->extradata) {
+ goto error;
+ }
+ memcpy(a_stream->codecpar->extradata,
+ attachment->data, attachment->data_size);
+ a_stream->codecpar->extradata_size = attachment->data_size;
+
+ av_dict_set(&a_stream->metadata, "filename", attachment->name, 0);
+ av_dict_set(&a_stream->metadata, "mimetype", attachment->type, 0);
+ }
+ }
+
// Not sure how to write this in a "standard" way. It appears only mkv
// and mp4 support this directly.
char version[200];
@@ -217,30 +255,19 @@ static void mux_packet(struct mp_recorder_sink *rst,
MP_ERR(priv, "Failed writing packet.\n");
}
-// Write all packets that currently can be written.
-static void mux_packets(struct mp_recorder_sink *rst, bool force)
+// Write all packets available in the stream queue
+static void mux_packets(struct mp_recorder_sink *rst)
{
struct mp_recorder *priv = rst->owner;
if (!priv->muxing || !rst->num_packets)
return;
- int safe_count = 0;
for (int n = 0; n < rst->num_packets; n++) {
- if (rst->packets[n]->keyframe)
- safe_count = n;
- }
- if (force)
- safe_count = rst->num_packets;
-
- for (int n = 0; n < safe_count; n++) {
mux_packet(rst, rst->packets[n]);
talloc_free(rst->packets[n]);
}
- // Remove packets[0..safe_count]
- memmove(&rst->packets[0], &rst->packets[safe_count],
- (rst->num_packets - safe_count) * sizeof(rst->packets[0]));
- rst->num_packets -= safe_count;
+ rst->num_packets = 0;
}
// If there was a discontinuity, check whether we can resume muxing (and from
@@ -291,7 +318,7 @@ void mp_recorder_destroy(struct mp_recorder *priv)
if (priv->opened) {
for (int n = 0; n < priv->num_streams; n++) {
struct mp_recorder_sink *rst = priv->streams[n];
- mux_packets(rst, true);
+ mux_packets(rst);
}
if (av_write_trailer(priv->mux) < 0)
@@ -312,15 +339,15 @@ void mp_recorder_destroy(struct mp_recorder *priv)
// This is called on a seek, or when recording was started mid-stream.
void mp_recorder_mark_discontinuity(struct mp_recorder *priv)
{
- flush_packets(priv);
for (int n = 0; n < priv->num_streams; n++) {
struct mp_recorder_sink *rst = priv->streams[n];
- mux_packets(rst, true);
+ mux_packets(rst);
rst->discont = true;
rst->proper_eof = false;
}
+ flush_packets(priv);
priv->muxing = false;
priv->muxing_from_start = false;
}
@@ -350,7 +377,7 @@ void mp_recorder_feed_packet(struct mp_recorder_sink *rst,
if (!pkt) {
rst->proper_eof = true;
check_restart(priv);
- mux_packets(rst, false);
+ mux_packets(rst);
return;
}
@@ -359,7 +386,7 @@ void mp_recorder_feed_packet(struct mp_recorder_sink *rst,
// No, FFmpeg doesn't tell us which formats need DTS at all.
// No, we can not shut up the FFmpeg warning, which will follow.
MP_WARN(priv, "Source stream misses DTS on at least some packets!\n"
- "If the target file format requires DTS, the written\n"
+ "If the target file format requires DTS, the written "
"file will be invalid.\n");
priv->dts_warning = true;
}
@@ -380,5 +407,5 @@ void mp_recorder_feed_packet(struct mp_recorder_sink *rst,
MP_TARRAY_APPEND(rst, rst->packets, rst->num_packets, pkt);
check_restart(priv);
- mux_packets(rst, false);
+ mux_packets(rst);
}
diff --git a/common/recorder.h b/common/recorder.h
index c0b1e36..e86d978 100644
--- a/common/recorder.h
+++ b/common/recorder.h
@@ -5,12 +5,15 @@ struct mp_recorder;
struct mpv_global;
struct demux_packet;
struct sh_stream;
+struct demux_attachment;
struct mp_recorder_sink;
struct mp_recorder *mp_recorder_create(struct mpv_global *global,
const char *target_file,
struct sh_stream **streams,
- int num_streams);
+ int num_streams,
+ struct demux_attachment **demux_attachments,
+ int num_attachments);
void mp_recorder_destroy(struct mp_recorder *r);
void mp_recorder_mark_discontinuity(struct mp_recorder *r);
diff --git a/demux/demux.c b/demux/demux.c
index e4dda43..fdec805 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -121,8 +121,7 @@ const struct m_sub_options demux_conf = {
M_RANGE(0, M_MAX_MEM_BYTES)},
{"demuxer-donate-buffer", OPT_FLAG(donate_fw)},
{"force-seekable", OPT_FLAG(force_seekable)},
- {"cache-secs", OPT_DOUBLE(min_secs_cache), M_RANGE(0, DBL_MAX),
- .deprecation_message = "will use unlimited time"},
+ {"cache-secs", OPT_DOUBLE(min_secs_cache), M_RANGE(0, DBL_MAX)},
{"access-references", OPT_FLAG(access_references)},
{"demuxer-seekable-cache", OPT_CHOICE(seekable_cache,
{"auto", -1}, {"no", 0}, {"yes", 1})},
@@ -1945,9 +1944,18 @@ static struct mp_recorder *recorder_create(struct demux_internal *in,
if (stream->ds->selected)
MP_TARRAY_APPEND(NULL, streams, num_streams, stream);
}
+
+ struct demuxer *demuxer = in->d_thread;
+ struct demux_attachment **attachments = talloc_array(NULL, struct demux_attachment*, demuxer->num_attachments);
+ for (int n = 0; n < demuxer->num_attachments; n++) {
+ attachments[n] = &demuxer->attachments[n];
+ }
+
struct mp_recorder *res = mp_recorder_create(in->d_thread->global, dst,
- streams, num_streams);
+ streams, num_streams,
+ attachments, demuxer->num_attachments);
talloc_free(streams);
+ talloc_free(attachments);
return res;
}
@@ -2827,7 +2835,7 @@ done:
return out_pkt;
}
-void demuxer_help(struct mp_log *log)
+int demuxer_help(struct mp_log *log, const m_option_t *opt, struct bstr name)
{
int i;
@@ -2837,6 +2845,9 @@ void demuxer_help(struct mp_log *log)
mp_info(log, "%10s %s\n",
demuxer_list[i]->name, demuxer_list[i]->desc);
}
+ mp_info(log, "\n");
+
+ return M_OPT_EXIT;
}
static const char *d_level(enum demux_check level)
@@ -3970,6 +3981,26 @@ void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
pthread_mutex_unlock(&in->lock);
}
+// Execute a refresh seek on the given stream.
+// ref_pts has the same meaning as with demuxer_select_track()
+void demuxer_refresh_track(struct demuxer *demuxer, struct sh_stream *stream,
+ double ref_pts)
+{
+ struct demux_internal *in = demuxer->in;
+ struct demux_stream *ds = stream->ds;
+ pthread_mutex_lock(&in->lock);
+ ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
+ if (ds->selected) {
+ MP_VERBOSE(in, "refresh track %d\n", stream->index);
+ update_stream_selection_state(in, ds);
+ if (in->back_demuxing)
+ ds->back_seek_pos = ref_pts;
+ if (!in->after_seek)
+ initiate_refresh_seek(in, ds, ref_pts);
+ }
+ pthread_mutex_unlock(&in->lock);
+}
+
// This is for demuxer implementations only. demuxer_select_track() sets the
// logical state, while this function returns the actual state (in case the
// demuxer attempts to cache even unselected packets for track switching - this
@@ -4106,10 +4137,10 @@ static void update_cache(struct demux_internal *in)
stream_control(stream, STREAM_CTRL_GET_METADATA, &stream_metadata);
}
- update_bytes_read(in);
-
pthread_mutex_lock(&in->lock);
+ update_bytes_read(in);
+
if (do_update)
in->stream_size = stream_size;
if (stream_metadata) {
@@ -4144,8 +4175,8 @@ static void dumper_close(struct demux_internal *in)
static int range_time_compare(const void *p1, const void *p2)
{
- struct demux_cached_range *r1 = (void *)p1;
- struct demux_cached_range *r2 = (void *)p2;
+ struct demux_cached_range *r1 = *((struct demux_cached_range **)p1);
+ struct demux_cached_range *r2 = *((struct demux_cached_range **)p2);
if (r1->seek_start == r2->seek_start)
return 0;
diff --git a/demux/demux.h b/demux/demux.h
index f49e6e2..ed414f1 100644
--- a/demux/demux.h
+++ b/demux/demux.h
@@ -290,8 +290,10 @@ void demux_block_reading(struct demuxer *demuxer, bool block);
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
double ref_pts, bool selected);
+void demuxer_refresh_track(struct demuxer *demuxer, struct sh_stream *stream,
+ double ref_pts);
-void demuxer_help(struct mp_log *log);
+int demuxer_help(struct mp_log *log, const m_option_t *opt, struct bstr name);
int demuxer_add_attachment(struct demuxer *demuxer, char *name,
char *type, void *data, size_t data_size);
diff --git a/demux/demux_edl.c b/demux/demux_edl.c
index 256b304..356b7ee 100644
--- a/demux/demux_edl.c
+++ b/demux/demux_edl.c
@@ -32,6 +32,7 @@
#include "options/path.h"
#include "misc/bstr.h"
#include "common/common.h"
+#include "common/tags.h"
#include "stream/stream.h"
#define HEADER "# mpv EDL v0\n"
@@ -60,6 +61,7 @@ struct tl_parts {
struct tl_root {
struct tl_parts **pars;
int num_pars;
+ struct mp_tags *tags;
};
struct priv {
@@ -171,6 +173,7 @@ static struct sh_stream *get_meta(struct tl_parts *tl, int index)
static struct tl_root *parse_edl(bstr str, struct mp_log *log)
{
struct tl_root *root = talloc_zero(NULL, struct tl_root);
+ root->tags = talloc_zero(root, struct mp_tags);
struct tl_parts *tl = add_part(root);
while (str.len) {
if (bstr_eatstart0(&str, "#")) {
@@ -273,6 +276,12 @@ static struct tl_root *parse_edl(bstr str, struct mp_log *log)
sh->codec->fps = get_param_int(&ctx, "fps", 0);
sh->codec->samplerate = get_param_int(&ctx, "samplerate", 0);
tl->delay_open = true;
+ } else if (bstr_equals0(f_type, "global_tags")) {
+ for (int n = 0; n < ctx.num_params; n++) {
+ mp_tags_set_bstr(root->tags, ctx.param_names[n],
+ ctx.param_vals[n]);
+ }
+ ctx.num_params = 0;
} else {
mp_err(log, "Unknown header: '%.*s'\n", BSTR_P(f_type));
goto error;
@@ -389,6 +398,7 @@ static void resolve_timestamps(struct tl_part *part, struct demuxer *demuxer)
}
static struct timeline_par *build_timeline(struct timeline *root,
+ struct tl_root *edl_root,
struct tl_parts *parts)
{
struct timeline_par *tl = talloc_zero(root, struct timeline_par);
@@ -542,6 +552,11 @@ static struct timeline_par *build_timeline(struct timeline *root,
if (!root->meta)
root->meta = tl->track_layout;
+ // Not very sane, since demuxer fields are supposed to be treated read-only
+ // from outside, but happens to work in this case, so who cares.
+ if (root->meta)
+ mp_tags_merge(root->meta->metadata, edl_root->tags);
+
assert(tl->num_parts == parts->num_parts);
return tl;
@@ -581,7 +596,7 @@ static void build_mpv_edl_timeline(struct timeline *tl)
for (int n = 0; n < root->num_pars; n++) {
struct tl_parts *parts = root->pars[n];
fix_filenames(parts, tl->demuxer->filename);
- struct timeline_par *par = build_timeline(tl, parts);
+ struct timeline_par *par = build_timeline(tl, root, parts);
if (!par)
break;
all_dash &= par->dash;
diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c
index 1bc8699..775f355 100644
--- a/demux/demux_lavf.c
+++ b/demux/demux_lavf.c
@@ -138,7 +138,6 @@ struct format_hack {
bool use_stream_ids : 1; // has a meaningful native stream IDs (export it)
bool fully_read : 1; // set demuxer.fully_read flag
bool detect_charset : 1; // format is a small text file, possibly not UTF8
- bool image_format : 1; // expected to contain exactly 1 frame
// Do not confuse player's position estimation (position is into external
// segment, with e.g. HLS, player knows about the playlist main file only).
bool clear_filepos : 1;
@@ -205,8 +204,6 @@ static const struct format_hack format_hacks[] = {
BLACKLIST("bin"),
// Useless, does not work with custom streams.
BLACKLIST("image2"),
- // Image demuxers ("<name>_pipe" is detected explicitly)
- {"image2pipe", .image_format = true},
{0}
};
@@ -227,7 +224,7 @@ typedef struct lavf_priv {
bool own_stream;
char *filename;
struct format_hack format_hack;
- AVInputFormat *avif;
+ const AVInputFormat *avif;
int avif_flags;
AVFormatContext *avfc;
AVIOContext *pb;
@@ -260,22 +257,20 @@ typedef struct lavf_priv {
static void update_read_stats(struct demuxer *demuxer)
{
+#if !HAVE_FFMPEG_AVIOCONTEXT_BYTES_READ
+ return;
+#else
lavf_priv_t *priv = demuxer->priv;
for (int n = 0; n < priv->num_nested; n++) {
struct nested_stream *nest = &priv->nested[n];
-#if !HAVE_FFMPEG_STRICT_ABI
- // Note: accessing the bytes_read field is not allowed by FFmpeg's API.
- // This is fully intentional - there is no other way to get this
- // information (not even by custom I/O, because the connection reuse
- // mechanism by the HLS demuxer would get disabled).
int64_t cur = nest->id->bytes_read;
int64_t new = cur - nest->last_bytes;
nest->last_bytes = cur;
demux_report_unbuffered_read_bytes(demuxer, new);
-#endif
}
+#endif
}
// At least mp4 has name="mov,mp4,m4a,3gp,3g2,mj2", so we split the name
@@ -443,7 +438,7 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
if (!lavfdopts->allow_mimetype || !mime_type)
mime_type = "";
- AVInputFormat *forced_format = NULL;
+ const AVInputFormat *forced_format = NULL;
const char *format = lavfdopts->format;
if (!format)
format = s->lavf_type;
@@ -528,11 +523,6 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
return -1;
}
- if (bstr_endswith0(bstr0(priv->avif->name), "_pipe")) {
- MP_VERBOSE(demuxer, "Assuming this is an image format.\n");
- priv->format_hack.image_format = true;
- }
-
if (lavfdopts->hacks)
priv->avif_flags = priv->avif->flags | priv->format_hack.if_flags;
@@ -714,8 +704,17 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
sh->codec->disp_h = codec->height;
if (st->avg_frame_rate.num)
sh->codec->fps = av_q2d(st->avg_frame_rate);
- if (priv->format_hack.image_format)
+ if (st->nb_frames <= 1 && (
+ sh->attached_picture ||
+ bstr_endswith0(bstr0(priv->avif->name), "_pipe") ||
+ strcmp(priv->avif->name, "alias_pix") == 0 ||
+ strcmp(priv->avif->name, "gif") == 0 ||
+ strcmp(priv->avif->name, "image2pipe") == 0
+ )) {
+ MP_VERBOSE(demuxer, "Assuming this is an image format.\n");
+ sh->image = true;
sh->codec->fps = priv->mf_fps;
+ }
sh->codec->par_w = st->sample_aspect_ratio.num;
sh->codec->par_h = st->sample_aspect_ratio.den;
@@ -1354,6 +1353,8 @@ static void demux_close_lavf(demuxer_t *demuxer)
}
if (priv->own_stream)
free_stream(priv->stream);
+ if (priv->av_opts)
+ av_dict_free(&priv->av_opts);
talloc_free(priv);
demuxer->priv = NULL;
}
diff --git a/demux/demux_mf.c b/demux/demux_mf.c
index ec16447..69fa0fa 100644
--- a/demux/demux_mf.c
+++ b/demux/demux_mf.c
@@ -289,6 +289,8 @@ static const struct {
{ "jls", "ljpeg" },
{ "thm", "mjpeg" },
{ "db", "mjpeg" },
+ { "pcd", "photocd" },
+ { "pfm", "pfm" },
{ "pcx", "pcx" },
{ "png", "png" },
{ "pns", "png" },
@@ -379,8 +381,12 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
// create a new video stream header
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
- struct mp_codec_params *c = sh->codec;
+ if (mf->nr_of_files == 1) {
+ MP_VERBOSE(demuxer, "Assuming this is an image format.\n");
+ sh->image = true;
+ }
+ struct mp_codec_params *c = sh->codec;
c->codec = codec;
c->disp_w = 0;
c->disp_h = 0;
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index 2e03fac..b0117f0 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -1300,6 +1300,7 @@ static void add_coverart(struct demuxer *demuxer)
sh->attached_picture->pts = 0;
talloc_steal(sh, sh->attached_picture);
sh->attached_picture->keyframe = true;
+ sh->image = true;
}
sh->title = att->name;
demux_add_sh_stream(demuxer, sh);
@@ -1741,7 +1742,11 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
if (!strcmp(codec, "mp2") || !strcmp(codec, "mp3") ||
!strcmp(codec, "truehd") || !strcmp(codec, "eac3"))
{
+ mkv_demuxer_t *mkv_d = demuxer->priv;
+ int64_t segment_timebase = (1e9 / mkv_d->tc_scale);
+
track->parse = true;
+ track->parse_timebase = MPMAX(sh_a->samplerate, segment_timebase);
} else if (!strcmp(codec, "flac")) {
unsigned char *ptr = extradata;
unsigned int size = extradata_len;
diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c
index 417642e..d671bba 100644
--- a/demux/demux_playlist.c
+++ b/demux/demux_playlist.c
@@ -50,7 +50,7 @@ static bool check_mimetype(struct stream *s, const char *const *list)
struct pl_parser {
struct mp_log *log;
struct stream *s;
- char buffer[512 * 1024];
+ char buffer[2 * 1024 * 1024];
int utf16;
struct playlist *pl;
bool error;
diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c
index 9b4a049..5572fb5 100644
--- a/demux/demux_timeline.c
+++ b/demux/demux_timeline.c
@@ -525,6 +525,7 @@ static void apply_meta(struct sh_stream *dst, struct sh_stream *src)
dst->missing_timestamps = src->missing_timestamps;
if (src->attached_picture)
dst->attached_picture = src->attached_picture;
+ dst->image = src->image;
}
// This is mostly for EDL user-defined metadata.
diff --git a/demux/stheader.h b/demux/stheader.h
index 6be3b16..8d2129e 100644
--- a/demux/stheader.h
+++ b/demux/stheader.h
@@ -48,6 +48,7 @@ struct sh_stream {
bool dependent_track; // container dependent track flag
bool visual_impaired_track; // container flag
bool hearing_impaired_track;// container flag
+ bool image; // video stream is an image
bool still_image; // video stream contains still images
int hls_bitrate;
diff --git a/etc/_mpv.zsh b/etc/_mpv.zsh
index a15bb7c..d7803ce 100644
--- a/etc/_mpv.zsh
+++ b/etc/_mpv.zsh
@@ -42,7 +42,7 @@ function _mpv_generate_arguments {
local -a option_aliases=()
local list_options_line
- for list_options_line in "${(@f)$($words[1] --list-options)}"; do
+ for list_options_line in "${(@f)$($~words[1] --list-options)}"; do
[[ $list_options_line =~ $'^[ \t]+--([^ \t]+)[ \t]*(.*)' ]] || continue
@@ -146,7 +146,7 @@ function _mpv_generate_arguments {
function _mpv_generate_protocols {
_mpv_completion_protocols=()
local list_protos_line
- for list_protos_line in "${(@f)$($words[1] --list-protocols)}"; do
+ for list_protos_line in "${(@f)$($~words[1] --list-protocols)}"; do
if [[ $list_protos_line =~ $'^[ \t]+(.*)' ]]; then
_mpv_completion_protocols+="$match[1]"
fi
@@ -158,7 +158,7 @@ function _mpv_generate_if_changed {
# on the first run and re-generates it if the executable being completed for
# is different than the one we used to generate the cached list.
typeset -gA _mpv_completion_binary
- local current_binary=${words[1]:c}
+ local current_binary=${~words[1]:c}
zmodload -F zsh/stat b:zstat
current_binary+=T$(zstat +mtime $current_binary)
if [[ $_mpv_completion_binary[$1] != $current_binary ]]; then
@@ -207,7 +207,7 @@ case $state in
esac
local -a values
local current
- for current in "${(@f)$($words[1] --${option_name}=help)}"; do
+ for current in "${(@f)$($~words[1] --${option_name}=help)}"; do
[[ $current =~ $pattern ]] || continue;
local name=${match[name_group]//:/\\:} desc=${match[desc_group]}
if [[ -n $desc ]]; then
diff --git a/etc/input.conf b/etc/input.conf
index 538443a..76c246a 100644
--- a/etc/input.conf
+++ b/etc/input.conf
@@ -29,87 +29,82 @@
#default-bindings start
#MBTN_LEFT ignore # don't do anything
-#MBTN_LEFT_DBL cycle fullscreen # toggle fullscreen on/off
-#MBTN_RIGHT cycle pause # toggle pause on/off
-#MBTN_BACK playlist-prev
-#MBTN_FORWARD playlist-next
+#MBTN_LEFT_DBL cycle fullscreen # toggle fullscreen
+#MBTN_RIGHT cycle pause # toggle pause/playback mode
+#MBTN_BACK playlist-prev # skip to the previous file
+#MBTN_FORWARD playlist-next # skip to the next file
# Mouse wheels, touchpad or other input devices that have axes
# if the input devices supports precise scrolling it will also scale the
# numeric value accordingly
-#WHEEL_UP seek 10
-#WHEEL_DOWN seek -10
+#WHEEL_UP seek 10 # seek 10 seconds forward
+#WHEEL_DOWN seek -10 # seek 10 seconds backward
#WHEEL_LEFT add volume -2
#WHEEL_RIGHT add volume 2
## Seek units are in seconds, but note that these are limited by keyframes
-#RIGHT seek 5
-#LEFT seek -5
-#UP seek 60
-#DOWN seek -60
+#RIGHT seek 5 # seek 5 seconds forward
+#LEFT seek -5 # seek 5 seconds backward
+#UP seek 60 # seek 1 minute forward
+#DOWN seek -60 # seek 1 minute backward
# Do smaller, always exact (non-keyframe-limited), seeks with shift.
# Don't show them on the OSD (no-osd).
-#Shift+RIGHT no-osd seek 1 exact
-#Shift+LEFT no-osd seek -1 exact
-#Shift+UP no-osd seek 5 exact
-#Shift+DOWN no-osd seek -5 exact
-# Skip to previous/next subtitle (subject to some restrictions; see manpage)
-#Ctrl+LEFT no-osd sub-seek -1
-#Ctrl+RIGHT no-osd sub-seek 1
-# Adjust timing to previous/next subtitle
-#Ctrl+Shift+LEFT sub-step -1
-#Ctrl+Shift+RIGHT sub-step 1
-# Move video rectangle
-#Alt+left add video-pan-x 0.1
-#Alt+right add video-pan-x -0.1
-#Alt+up add video-pan-y 0.1
-#Alt+down add video-pan-y -0.1
-# Zoom/unzoom video
-#Alt++ add video-zoom 0.1
-#Alt+- add video-zoom -0.1
-# Reset video zoom/pan settings
-#Alt+BS set video-zoom 0 ; set video-pan-x 0 ; set video-pan-y 0
-#PGUP add chapter 1 # skip to next chapter
-#PGDWN add chapter -1 # skip to previous chapter
-#Shift+PGUP seek 600
-#Shift+PGDWN seek -600
-#[ multiply speed 1/1.1 # scale playback speed
-#] multiply speed 1.1
-#{ multiply speed 0.5
-#} multiply speed 2.0
-#BS set speed 1.0 # reset speed to normal
-#Shift+BS revert-seek # undo previous (or marked) seek
-#Shift+Ctrl+BS revert-seek mark # mark position for revert-seek
+#Shift+RIGHT no-osd seek 1 exact # seek exactly 1 second forward
+#Shift+LEFT no-osd seek -1 exact # seek exactly 1 second backward
+#Shift+UP no-osd seek 5 exact # seek exactly 5 seconds forward
+#Shift+DOWN no-osd seek -5 exact # seek exactly 5 seconds backward
+#Ctrl+LEFT no-osd sub-seek -1 # seek to the previous subtitle
+#Ctrl+RIGHT no-osd sub-seek 1 # seek to the next subtitle
+#Ctrl+Shift+LEFT sub-step -1 # change subtitle timing such that the previous subtitle is displayed
+#Ctrl+Shift+RIGHT sub-step 1 # change subtitle timing such that the next subtitle is displayed
+#Alt+left add video-pan-x 0.1 # move the video right
+#Alt+right add video-pan-x -0.1 # move the video left
+#Alt+up add video-pan-y 0.1 # move the video down
+#Alt+down add video-pan-y -0.1 # move the video up
+#Alt++ add video-zoom 0.1 # zoom in
+#Alt+- add video-zoom -0.1 # zoom out
+#Alt+BS set video-zoom 0 ; set video-pan-x 0 ; set video-pan-y 0 # reset zoom and pan settings
+#PGUP add chapter 1 # seek to the next chapter
+#PGDWN add chapter -1 # seek to the previous chapter
+#Shift+PGUP seek 600 # seek 10 minutes forward
+#Shift+PGDWN seek -600 # seek 10 minutes backward
+#[ multiply speed 1/1.1 # decrease the playback speed
+#] multiply speed 1.1 # increase the playback speed
+#{ multiply speed 0.5 # halve the playback speed
+#} multiply speed 2.0 # double the playback speed
+#BS set speed 1.0 # reset the speed to normal
+#Shift+BS revert-seek # undo the previous (or marked) seek
+#Shift+Ctrl+BS revert-seek mark # mark the position for revert-seek
#q quit
-#Q quit-watch-later
+#Q quit-watch-later # exit and remember the playback position
#q {encode} quit 4
-#ESC set fullscreen no
+#ESC set fullscreen no # leave fullscreen
#ESC {encode} quit 4
#p cycle pause # toggle pause/playback mode
#. frame-step # advance one frame and pause
#, frame-back-step # go back by one frame and pause
-#SPACE cycle pause
-#> 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 show-progress
-#P show-progress
-#i script-binding stats/display-stats
-#I script-binding stats/display-stats-toggle
-#` script-binding console/enable
-#z add sub-delay -0.1 # subtract 100 ms delay from subs
-#Z add sub-delay +0.1 # add
-#x add sub-delay +0.1 # same as previous binding (discouraged)
-#ctrl++ add audio-delay 0.100 # this changes audio/video sync
-#ctrl+- add audio-delay -0.100
-#Shift+g add sub-scale +0.1 # increase subtitle font size
-#Shift+f add sub-scale -0.1 # decrease subtitle font size
+#SPACE cycle pause # toggle pause/playback mode
+#> playlist-next # skip to the next file
+#ENTER playlist-next # skip to the next file
+#< playlist-prev # skip to the previous file
+#O no-osd cycle-values osd-level 3 1 # toggle displaying the OSD on user interaction or always
+#o show-progress # show playback progress
+#P show-progress # show playback progress
+#i script-binding stats/display-stats # display information and statistics
+#I script-binding stats/display-stats-toggle # toggle displaying information and statistics
+#` script-binding console/enable # open the console
+#z add sub-delay -0.1 # shift subtitles 100 ms earlier
+#Z add sub-delay +0.1 # delay subtitles by 100 ms
+#x add sub-delay +0.1 # delay subtitles by 100 ms
+#ctrl++ add audio-delay 0.100 # change audio/video sync by delaying the audio
+#ctrl+- add audio-delay -0.100 # change audio/video sync by shifting the audio earlier
+#Shift+g add sub-scale +0.1 # increase the subtitle font size
+#Shift+f add sub-scale -0.1 # decrease the subtitle font size
#9 add volume -2
#/ add volume -2
#0 add volume 2
#* add volume 2
-#m cycle mute
+#m cycle mute # toggle mute
#1 add contrast -1
#2 add contrast 1
#3 add brightness -1
@@ -118,73 +113,67 @@
#6 add gamma 1
#7 add saturation -1
#8 add saturation 1
-#Alt+0 set window-scale 0.5
-#Alt+1 set window-scale 1.0
-#Alt+2 set window-scale 2.0
-# toggle deinterlacer (automatically inserts or removes required filter)
-#d cycle deinterlace
+#Alt+0 set current-window-scale 0.5 # halve the window size
+#Alt+1 set current-window-scale 1.0 # reset the window size
+#Alt+2 set current-window-scale 2.0 # double the window size
+#d cycle deinterlace # toggle the deinterlacing filter
#r add sub-pos -1 # move subtitles up
-#R add sub-pos +1 # down
-#t add sub-pos +1 # same as previous binding (discouraged)
-#v cycle sub-visibility
-# stretch SSA/ASS subtitles with anamorphic videos to match historical
-#V cycle sub-ass-vsfilter-aspect-compat
-# switch between applying no style overrides to SSA/ASS subtitles, and
-# overriding them almost completely with the normal subtitle style
-#u cycle-values sub-ass-override "force" "no"
-#j cycle sub # cycle through subtitles
-#J cycle sub down # ...backwards
-#SHARP cycle audio # switch audio streams
-#_ cycle video
-#T cycle ontop # toggle video window ontop of other windows
+#R add sub-pos +1 # move subtitles down
+#t add sub-pos +1 # move subtitles down
+#v cycle sub-visibility # hide or show the subtitles
+#Alt+v cycle secondary-sub-visibility # hide or show the secondary subtitles
+#V cycle sub-ass-vsfilter-aspect-compat # toggle stretching SSA/ASS subtitles with anamorphic videos to match the historical renderer
+#u cycle-values sub-ass-override "force" "no" # toggle overriding SSA/ASS subtitle styles with the normal styles
+#j cycle sub # switch subtitle track
+#J cycle sub down # switch subtitle track backwards
+#SHARP cycle audio # switch audio track
+#_ cycle video # switch video track
+#T cycle ontop # toggle placing the video on top of other windows
#f cycle fullscreen # toggle fullscreen
-#s screenshot # take a screenshot
-#S screenshot video # ...without subtitles
-#Ctrl+s screenshot window # ...with subtitles and OSD, and scaled
-#Alt+s screenshot each-frame # automatically screenshot every frame
-#w add panscan -0.1 # zoom out with -panscan 0 -fs
-#W add panscan +0.1 # in
-#e add panscan +0.1 # same as previous binding (discouraged)
-# cycle video aspect ratios; "-1" is the container aspect
-#A cycle-values video-aspect-override "16:9" "4:3" "2.35:1" "-1"
+#s screenshot # take a screenshot of the video in its original resolution with subtitles
+#S screenshot video # take a screenshot of the video in its original resolution without subtitles
+#Ctrl+s screenshot window # take a screenshot of the window with OSD and subtitles
+#Alt+s screenshot each-frame # automatically screenshot every frame; issue this command again to stop taking screenshots
+#w add panscan -0.1 # decrease panscan
+#W add panscan +0.1 # shrink black bars by cropping the video
+#e add panscan +0.1 # shrink black bars by cropping the video
+#A cycle-values video-aspect-override "16:9" "4:3" "2.35:1" "-1" # cycle the video aspect ratio ("-1" is the container aspect)
#POWER quit
-#PLAY cycle pause
-#PAUSE cycle pause
-#PLAYPAUSE cycle pause
-#PLAYONLY set pause no
-#PAUSEONLY set pause yes
+#PLAY cycle pause # toggle pause/playback mode
+#PAUSE cycle pause # toggle pause/playback mode
+#PLAYPAUSE cycle pause # toggle pause/playback mode
+#PLAYONLY set pause no # unpause
+#PAUSEONLY set pause yes # pause
#STOP quit
-#FORWARD seek 60
-#REWIND seek -60
-#NEXT playlist-next
-#PREV playlist-prev
+#FORWARD seek 60 # seek 1 minute forward
+#REWIND seek -60 # seek 1 minute backward
+#NEXT playlist-next # skip to the next file
+#PREV playlist-prev # skip to the previous file
#VOLUME_UP add volume 2
#VOLUME_DOWN add volume -2
-#MUTE cycle mute
+#MUTE cycle mute # toggle mute
#CLOSE_WIN quit
#CLOSE_WIN {encode} quit 4
#ctrl+w quit
-#E cycle edition # next edition
-#l ab-loop # Set/clear A-B loop points
+#E cycle edition # switch edition
+#l ab-loop # set/clear A-B loop points
#L cycle-values loop-file "inf" "no" # toggle infinite looping
#ctrl+c quit 4
-#DEL script-binding osc/visibility # cycle OSC display
-#ctrl+h cycle-values hwdec "auto" "no" # cycle hardware decoding
-#F8 show_text ${playlist} # show playlist
-#F9 show_text ${track-list} # show list of audio/sub streams
+#DEL script-binding osc/visibility # cycle OSC visibility between never, auto (mouse-move) and always
+#ctrl+h cycle-values hwdec "auto" "no" # toggle hardware decoding
+#F8 show-text ${playlist} # show the playlist
+#F9 show-text ${track-list} # show the list of video, audio and sub tracks
#
# Legacy bindings (may or may not be removed in the future)
#
-#! add chapter -1 # skip to previous chapter
-#@ add chapter 1 # next
+#! add chapter -1 # seek to the previous chapter
+#@ add chapter 1 # seek to the next chapter
#
# Not assigned by default
# (not an exhaustive list of unbound commands)
#
-# ? cycle angle # switch DVD/Bluray angle
# ? cycle sub-forced-only # toggle DVD forced subs
-# ? cycle program # cycle transport stream programs
# ? stop # stop playback (quit or enter idle mode)
diff --git a/etc/mpv.bash-completion b/etc/mpv.bash-completion
index 60c1cab..f33caf6 100644
--- a/etc/mpv.bash-completion
+++ b/etc/mpv.bash-completion
@@ -34,13 +34,12 @@ _mpv_get_args()
declare -a candidates
case $type in
String)
- echo "$doc" | grep -q '\[file\]'
- if [ $? -eq 0 ]; then
+ if echo "$doc" | grep -q '\[file\]' ; then
if [ "$cur" = '=' ]; then
# Without this, _filedir will try and complete files starting with '='
cur=""
fi
- _filedir
+ _filedir 2>/dev/null || COMPREPLY=($(compgen -f))
return 0
else
candidates=($(mpv $1=help | grep -v ':' | awk '{print $1;}'))
@@ -106,7 +105,7 @@ _mpv()
fi
;;
*)
- _filedir
+ _filedir 2>/dev/null || COMPREPLY=($(compgen -f))
;;
esac
fi
diff --git a/etc/mpv.desktop b/etc/mpv.desktop
index 38544bd..05df80d 100644
--- a/etc/mpv.desktop
+++ b/etc/mpv.desktop
@@ -34,3 +34,4 @@ Terminal=false
Categories=AudioVideo;Audio;Video;Player;TV;
MimeType=application/ogg;application/x-ogg;application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/musepack;audio/x-musepack;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;audio/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;application/x-cue;audio/m3u;
X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb,srt
+StartupWMClass=mpv
diff --git a/filters/f_auto_filters.c b/filters/f_auto_filters.c
index a5394dd..5dd3a4a 100644
--- a/filters/f_auto_filters.c
+++ b/filters/f_auto_filters.c
@@ -329,9 +329,9 @@ static void aspeed_process(struct mp_filter *f)
if (req_filter) {
if (req_filter == 1) {
- MP_VERBOSE(f, "adding scaletempo\n");
+ MP_VERBOSE(f, "adding scaletempo2\n");
p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO,
- "scaletempo", NULL);
+ "scaletempo2", NULL);
} else if (req_filter == 2) {
MP_VERBOSE(f, "adding drop\n");
p->sub.filter = mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO,
diff --git a/filters/f_auto_filters.h b/filters/f_auto_filters.h
index 98043c9..f315084 100644
--- a/filters/f_auto_filters.h
+++ b/filters/f_auto_filters.h
@@ -9,5 +9,5 @@ struct mp_filter *mp_deint_create(struct mp_filter *parent);
// Rotate according to mp_image.rotate and VO capabilities.
struct mp_filter *mp_autorotate_create(struct mp_filter *parent);
-// Insert a filter that inserts scaletempo depending on speed settings.
+// Insert a filter that inserts scaletempo2 depending on speed settings.
struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent);
diff --git a/filters/f_decoder_wrapper.c b/filters/f_decoder_wrapper.c
index 397c3a0..9be8743 100644
--- a/filters/f_decoder_wrapper.c
+++ b/filters/f_decoder_wrapper.c
@@ -110,16 +110,19 @@ struct dec_wrapper_opts {
int64_t audio_reverse_size;
};
-static int decoder_list_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param);
+static int decoder_list_help(struct mp_log *log, const m_option_t *opt,
+ struct bstr name);
const struct m_sub_options dec_wrapper_conf = {
.opts = (const struct m_option[]){
{"correct-pts", OPT_FLAG(correct_pts)},
{"fps", OPT_DOUBLE(force_fps), M_RANGE(0, DBL_MAX)},
- {"ad", OPT_STRING_VALIDATE(audio_decoders, decoder_list_opt)},
- {"vd", OPT_STRING_VALIDATE(video_decoders, decoder_list_opt)},
- {"audio-spdif", OPT_STRING_VALIDATE(audio_spdif, decoder_list_opt)},
+ {"ad", OPT_STRING(audio_decoders),
+ .help = decoder_list_help},
+ {"vd", OPT_STRING(video_decoders),
+ .help = decoder_list_help},
+ {"audio-spdif", OPT_STRING(audio_spdif),
+ .help = decoder_list_help},
{"video-rotate", OPT_CHOICE(video_rotate, {"no", -1}),
.flags = UPDATE_IMGPAR, M_RANGE(0, 359)},
{"video-aspect-override", OPT_ASPECT(movie_aspect),
@@ -232,11 +235,9 @@ struct priv {
int dropped_frames; // total frames _probably_ dropped
};
-static int decoder_list_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+static int decoder_list_help(struct mp_log *log, const m_option_t *opt,
+ struct bstr name)
{
- if (!bstr_equals0(param, "help"))
- return 1;
if (strcmp(opt->name, "ad") == 0) {
struct mp_decoder_list *list = audio_decoder_list();
mp_print_decoders(log, MSGL_INFO, "Audio decoders:", list);
diff --git a/input/cmd.c b/input/cmd.c
index 692b9f5..6423214 100644
--- a/input/cmd.c
+++ b/input/cmd.c
@@ -336,11 +336,34 @@ static int pctx_read_token(struct parse_ctx *ctx, bstr *out)
return -1;
}
if (!bstr_eatstart0(&ctx->str, "\"")) {
- MP_ERR(ctx, "Unterminated quotes: ...>%.*s<.\n", BSTR_P(start));
+ MP_ERR(ctx, "Unterminated double quote: ...>%.*s<.\n", BSTR_P(start));
return -1;
}
return 1;
}
+ if (bstr_eatstart0(&ctx->str, "'")) {
+ int next = bstrchr(ctx->str, '\'');
+ if (next < 0) {
+ MP_ERR(ctx, "Unterminated single quote: ...>%.*s<.\n", BSTR_P(start));
+ return -1;
+ }
+ *out = bstr_splice(ctx->str, 0, next);
+ ctx->str = bstr_cut(ctx->str, next+1);
+ return 1;
+ }
+ if (ctx->start.len > 1 && bstr_eatstart0(&ctx->str, "`")) {
+ char endquote[2] = {ctx->str.start[0], '`'};
+ ctx->str = bstr_cut(ctx->str, 1);
+ int next = bstr_find(ctx->str, (bstr){endquote, 2});
+ if (next < 0) {
+ MP_ERR(ctx, "Unterminated custom quote: ...>%.*s<.\n", BSTR_P(start));
+ return -1;
+ }
+ *out = bstr_splice(ctx->str, 0, next);
+ ctx->str = bstr_cut(ctx->str, next+2);
+ return 1;
+ }
+
return read_token(ctx->str, &ctx->str, out) ? 1 : 0;
}
@@ -585,8 +608,10 @@ void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd)
bool mp_input_is_repeatable_cmd(struct mp_cmd *cmd)
{
- return (cmd->def->allow_auto_repeat) || cmd->def == &mp_cmd_list ||
- (cmd->flags & MP_ALLOW_REPEAT);
+ if (cmd->def == &mp_cmd_list && cmd->args[0].v.p)
+ cmd = cmd->args[0].v.p; // list - only 1st cmd is considered
+
+ return (cmd->def->allow_auto_repeat) || (cmd->flags & MP_ALLOW_REPEAT);
}
bool mp_input_is_scalable_cmd(struct mp_cmd *cmd)
diff --git a/input/input.c b/input/input.c
index 40abd7d..f69517f 100644
--- a/input/input.c
+++ b/input/input.c
@@ -176,6 +176,7 @@ struct input_opts {
int use_gamepad;
int use_media_keys;
int default_bindings;
+ int builtin_bindings;
int enable_mouse_movements;
int vo_key_input;
int test;
@@ -190,6 +191,7 @@ const struct m_sub_options input_config = {
{"input-keylist", OPT_PRINT(mp_print_key_list)},
{"input-cmdlist", OPT_PRINT(mp_print_cmd_list)},
{"input-default-bindings", OPT_FLAG(default_bindings)},
+ {"input-builtin-bindings", OPT_FLAG(builtin_bindings)},
{"input-test", OPT_FLAG(test)},
{"input-doubleclick-time", OPT_INT(doubleclick_time),
M_RANGE(0, 1000)},
@@ -218,6 +220,7 @@ const struct m_sub_options input_config = {
.enable_mouse_movements = 1,
.use_media_keys = 1,
.default_bindings = 1,
+ .builtin_bindings = 1,
.vo_key_input = 1,
.allow_win_drag = 1,
},
@@ -1367,7 +1370,7 @@ void mp_input_load_config(struct input_ctx *ictx)
// "Uncomment" the default key bindings in etc/input.conf and add them.
// All lines that do not start with '# ' are parsed.
bstr builtin = bstr0(builtin_input_conf);
- while (builtin.len) {
+ while (ictx->opts->builtin_bindings && builtin.len) {
bstr line = bstr_getline(builtin, &builtin);
bstr_eatstart0(&line, "#");
if (!bstr_startswith0(line, " "))
diff --git a/options/m_config_frontend.c b/options/m_config_frontend.c
index a3356c4..fdb03e0 100644
--- a/options/m_config_frontend.c
+++ b/options/m_config_frontend.c
@@ -244,6 +244,26 @@ void m_config_restore_backups(struct m_config *config)
restore_backups(&config->backup_opts, config);
}
+bool m_config_watch_later_backup_opt_changed(struct m_config *config,
+ char *opt_name)
+{
+ struct m_config_option *co = m_config_get_co(config, bstr0(opt_name));
+ if (!co) {
+ MP_ERR(config, "Option %s not found.\n", opt_name);
+ return false;
+ }
+
+ for (struct m_opt_backup *bc = config->watch_later_backup_opts; bc;
+ bc = bc->next) {
+ if (strcmp(bc->co->name, co->name) == 0) {
+ struct m_config_option *bc_co = (struct m_config_option *)bc->backup;
+ return !m_option_equal(co->opt, co->data, bc_co);
+ }
+ }
+
+ return false;
+}
+
void m_config_backup_opt(struct m_config *config, const char *opt)
{
struct m_config_option *co = m_config_get_co(config, bstr0(opt));
@@ -260,6 +280,11 @@ void m_config_backup_all_opts(struct m_config *config)
ensure_backup(&config->backup_opts, BACKUP_LOCAL, &config->opts[n]);
}
+void m_config_backup_watch_later_opts(struct m_config *config)
+{
+ for (int n = 0; n < config->num_opts; n++)
+ ensure_backup(&config->watch_later_backup_opts, 0, &config->opts[n]);
+}
struct m_config_option *m_config_get_co_raw(const struct m_config *config,
struct bstr name)
@@ -509,6 +534,13 @@ static void config_destroy(void *p)
config->option_change_callback = NULL;
m_config_restore_backups(config);
+ struct m_opt_backup **list = &config->watch_later_backup_opts;
+ while (*list) {
+ struct m_opt_backup *bc = *list;
+ *list = bc->next;
+ talloc_free(bc);
+ }
+
talloc_free(config->cache);
talloc_free(config->shadow);
}
diff --git a/options/m_config_frontend.h b/options/m_config_frontend.h
index ee6b9ae..2812033 100644
--- a/options/m_config_frontend.h
+++ b/options/m_config_frontend.h
@@ -75,6 +75,7 @@ typedef struct m_config {
int profile_backup_flags;
struct m_opt_backup *backup_opts;
+ struct m_opt_backup *watch_later_backup_opts;
bool use_profiles;
bool is_toplevel;
@@ -134,10 +135,18 @@ void m_config_backup_opt(struct m_config *config, const char *opt);
// Call m_config_backup_opt() on all options.
void m_config_backup_all_opts(struct m_config *config);
+// Backup options on startup so that quit-watch-later can compare the current
+// values to their backups, and save them only if they have been changed.
+void m_config_backup_watch_later_opts(struct m_config *config);
+
// Restore all options backed up with m_config_backup_opt(), and delete the
// backups afterwards.
void m_config_restore_backups(struct m_config *config);
+// Whether opt_name is different from its initial value.
+bool m_config_watch_later_backup_opt_changed(struct m_config *config,
+ char *opt_name);
+
enum {
M_SETOPT_PRE_PARSE_ONLY = 1, // Silently ignore non-M_OPT_PRE_PARSE opt.
M_SETOPT_CHECK_ONLY = 2, // Don't set, just check name/value
diff --git a/options/m_option.c b/options/m_option.c
index 4d222df..f5a9ce5 100644
--- a/options/m_option.c
+++ b/options/m_option.c
@@ -61,6 +61,31 @@ const char m_option_path_separator = OPTION_PATH_SEPARATOR;
#define OPT_INT_MAX(opt, T, Tm) ((opt)->min < (opt)->max \
? ((opt)->max >= (double)(Tm) ? (Tm) : (T)((opt)->max)) : (Tm))
+int m_option_parse(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param, void *dst)
+{
+ int r = M_OPT_INVALID;
+ if (bstr_equals0(param, "help") && opt->help) {
+ r = opt->help(log, opt, name);
+ if (r < 0)
+ return r;
+ }
+
+ r = opt->type->parse(log, opt, name, param, dst);
+ if (r < 0)
+ return r;
+
+ if (opt->validate) {
+ r = opt->validate(log, opt, name, dst);
+ if (r < 0) {
+ if (opt->type->free)
+ opt->type->free(dst);
+ return r;
+ }
+ }
+ return 1;
+}
+
char *m_option_strerror(int code)
{
switch (code) {
@@ -1192,13 +1217,6 @@ const m_option_type_t m_option_type_aspect = {
static int parse_str(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
- m_opt_string_validate_fn validate = opt->priv;
- if (validate) {
- int r = validate(log, opt, name, param);
- if (r < 0)
- return r;
- }
-
if (dst) {
talloc_free(VAL(dst));
VAL(dst) = bstrdup0(NULL, param);
@@ -1846,7 +1864,7 @@ static int parse_msglevels(struct mp_log *log, const m_option_t *opt,
struct bstr name, struct bstr param, void *dst)
{
if (bstr_equals0(param, "help")) {
- mp_info(log, "Syntax:\n\n --msglevel=module1=level,module2=level,...\n\n"
+ mp_info(log, "Syntax:\n\n --msg-level=module1=level,module2=level,...\n\n"
"'module' is output prefix as shown with -v, or a prefix\n"
"of it. level is one of:\n\n"
" fatal error warn info status v debug trace\n\n"
@@ -3650,7 +3668,7 @@ static bool obj_settings_list_equal(const m_option_t *opt, void *pa, void *pb)
struct m_obj_settings *b = VAL(pb);
if (a == b || !a || !b)
- return a == b;
+ return a == b || (!a && !b[0].name) || (!b && !a[0].name);
for (int n = 0; a[n].name || b[n].name; n++) {
if (!a[n].name || !b[n].name)
diff --git a/options/m_option.h b/options/m_option.h
index afd86ea..322f120 100644
--- a/options/m_option.h
+++ b/options/m_option.h
@@ -189,9 +189,15 @@ struct m_opt_choice_alternatives {
const char *m_opt_choice_str(const struct m_opt_choice_alternatives *choices,
int value);
-// For OPT_STRING_VALIDATE(). Behaves like m_option_type.parse().
+// Validator function signatures. Required to properly type the param value.
+typedef int (*m_opt_generic_validate_fn)(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, void *value);
+
typedef int (*m_opt_string_validate_fn)(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
+typedef int (*m_opt_int_validate_fn)(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, const int *value);
+
// m_option.priv points to this if OPT_SUBSTRUCT is used
struct m_sub_options {
@@ -270,6 +276,9 @@ struct m_option_type {
// Parse the data from a string.
/** It is the only required function, all others can be NULL.
+ * Generally should not be called directly outside of the options module,
+ * but instead through \ref m_option_parse which calls additional option
+ * specific callbacks during the process.
*
* \param log for outputting parser error or help messages
* \param opt The option that is parsed.
@@ -384,6 +393,12 @@ struct m_option {
// Print a warning when this option is used (for options with no direct
// replacement.)
const char *deprecation_message;
+
+ // Optional function that validates a param value for this option.
+ m_opt_generic_validate_fn validate;
+
+ // Optional function that displays help. Will replace type-specific help.
+ int (*help)(struct mp_log *log, const m_option_t *opt, struct bstr name);
};
char *format_file_size(int64_t size);
@@ -490,12 +505,13 @@ char *format_file_size(int64_t size);
char *m_option_strerror(int code);
-// Helper to parse options, see \ref m_option_type::parse.
-static inline int m_option_parse(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param, void *dst)
-{
- return opt->type->parse(log, opt, name, param, dst);
-}
+// Base function to parse options. Includes calling help and validation
+// callbacks. Only when this functionality is for some reason required to not
+// happen should the parse function pointer be utilized by itself.
+//
+// See \ref m_option_type::parse.
+int m_option_parse(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param, void *dst);
// Helper to print options, see \ref m_option_type::print.
static inline char *m_option_print(const m_option_t *opt, const void *val_ptr)
@@ -661,9 +677,15 @@ extern const char m_option_path_separator;
#define OPT_CHANNELS(field) \
OPT_TYPED_FIELD(m_option_type_channels, struct m_channels, field)
+#define OPT_INT_VALIDATE(field, validate_fn) \
+ OPT_TYPED_FIELD(m_option_type_int, int, field), \
+ .validate = (m_opt_generic_validate_fn) \
+ MP_EXPECT_TYPE(m_opt_int_validate_fn, validate_fn)
+
#define OPT_STRING_VALIDATE(field, validate_fn) \
OPT_TYPED_FIELD(m_option_type_string, char*, field), \
- .priv = MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn)
+ .validate = (m_opt_generic_validate_fn) \
+ MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn)
#define M_CHOICES(...) \
.priv = (void *)&(const struct m_opt_choice_alternatives[]){ __VA_ARGS__, {0}}
diff --git a/options/options.c b/options/options.c
index a11c111..2f8885e 100644
--- a/options/options.c
+++ b/options/options.c
@@ -46,6 +46,7 @@
#include "player/core.h"
#include "player/command.h"
#include "stream/stream.h"
+#include "demux/demux.h"
#if HAVE_DRM
#include "video/out/drm_common.h"
@@ -86,6 +87,7 @@ extern const struct m_sub_options ao_conf;
extern const struct m_sub_options opengl_conf;
extern const struct m_sub_options vulkan_conf;
+extern const struct m_sub_options vulkan_display_conf;
extern const struct m_sub_options spirv_conf;
extern const struct m_sub_options d3d11_conf;
extern const struct m_sub_options d3d11va_conf;
@@ -112,7 +114,8 @@ static const m_option_t mp_vo_opt_list[] = {
{"ontop-level", OPT_CHOICE(ontop_level, {"window", -1}, {"system", -2},
{"desktop", -3}), M_RANGE(0, INT_MAX)},
{"border", OPT_FLAG(border)},
- {"fit-border", OPT_FLAG(fit_border)},
+ {"fit-border", OPT_FLAG(fit_border),
+ .deprecation_message = "the option is ignored and no longer needed"},
{"on-all-workspaces", OPT_FLAG(all_workspaces)},
{"geometry", OPT_GEOMETRY(geometry)},
{"autofit", OPT_SIZE_BOX(autofit)},
@@ -147,8 +150,10 @@ static const m_option_t mp_vo_opt_list[] = {
{"no", 0}, {"yes", 1}, {"downscale-big", 2})},
{"wid", OPT_INT64(WinID)},
{"screen", OPT_CHOICE(screen_id, {"default", -1}), M_RANGE(0, 32)},
+ {"screen-name", OPT_STRING(screen_name)},
{"fs-screen", OPT_CHOICE(fsscreen_id, {"all", -2}, {"current", -1}),
M_RANGE(0, 32)},
+ {"fs-screen-name", OPT_STRING(fsscreen_name)},
{"keepaspect", OPT_FLAG(keepaspect)},
{"keepaspect-window", OPT_FLAG(keepaspect_window)},
{"hidpi-window-scale", OPT_FLAG(hidpi_window_scale)},
@@ -193,6 +198,7 @@ const struct m_sub_options vo_sub_opts = {
.snap_window = 0,
.border = 1,
.fit_border = 1,
+ .appid = "mpv",
.WinID = -1,
.window_scale = 1.0,
.x11_bypass_compositor = 2,
@@ -212,7 +218,9 @@ const struct m_sub_options mp_sub_filter_opts = {
{"sub-filter-sdh", OPT_FLAG(sub_filter_SDH)},
{"sub-filter-sdh-harder", OPT_FLAG(sub_filter_SDH_harder)},
{"sub-filter-regex-enable", OPT_FLAG(rf_enable)},
+ {"sub-filter-regex-plain", OPT_FLAG(rf_plain)},
{"sub-filter-regex", OPT_STRINGLIST(rf_items)},
+ {"sub-filter-jsre", OPT_STRINGLIST(jsre_items)},
{"sub-filter-regex-warn", OPT_FLAG(rf_warn)},
{0}
},
@@ -232,6 +240,7 @@ const struct m_sub_options mp_subtitle_sub_opts = {
{"sub-fps", OPT_FLOAT(sub_fps)},
{"sub-speed", OPT_FLOAT(sub_speed)},
{"sub-visibility", OPT_FLAG(sub_visibility)},
+ {"secondary-sub-visibility", OPT_FLAG(sec_sub_visibility)},
{"sub-forced-only", OPT_CHOICE(forced_subs_only,
{"auto", -1}, {"no", 0}, {"yes", 1})},
{"stretch-dvd-subs", OPT_FLAG(stretch_dvd_subs)},
@@ -269,11 +278,13 @@ const struct m_sub_options mp_subtitle_sub_opts = {
{"sub", OPT_SUBSTRUCT(sub_style, sub_style_conf)},
{"sub-clear-on-seek", OPT_FLAG(sub_clear_on_seek)},
{"teletext-page", OPT_INT(teletext_page), M_RANGE(1, 999)},
+ {"sub-past-video-end", OPT_FLAG(sub_past_video_end)},
{0}
},
.size = sizeof(OPT_BASE_STRUCT),
.defaults = &(OPT_BASE_STRUCT){
.sub_visibility = 1,
+ .sec_sub_visibility = 1,
.forced_subs_only = -1,
.sub_pos = 100,
.sub_speed = 1.0,
@@ -499,7 +510,8 @@ static const m_option_t mp_opts[] = {
{"lavfi-complex", OPT_STRING(lavfi_complex), .flags = UPDATE_LAVFI_COMPLEX},
- {"audio-display", OPT_CHOICE(audio_display, {"no", 0}, {"attachment", 1})},
+ {"audio-display", OPT_CHOICE(audio_display, {"no", 0},
+ {"embedded-first", 1}, {"external-first", 2})},
{"hls-bitrate", OPT_CHOICE(hls_bitrate,
{"no", -1}, {"min", 0}, {"max", INT_MAX}), M_RANGE(0, INT_MAX)},
@@ -512,9 +524,9 @@ static const m_option_t mp_opts[] = {
#endif
// demuxer.c - select audio/sub file/demuxer
- {"demuxer", OPT_STRING(demuxer_name)},
- {"audio-demuxer", OPT_STRING(audio_demuxer_name)},
- {"sub-demuxer", OPT_STRING(sub_demuxer_name)},
+ {"demuxer", OPT_STRING(demuxer_name), .help = demuxer_help},
+ {"audio-demuxer", OPT_STRING(audio_demuxer_name), .help = demuxer_help},
+ {"sub-demuxer", OPT_STRING(sub_demuxer_name), .help = demuxer_help},
{"demuxer-thread", OPT_FLAG(demuxer_thread)},
{"demuxer-termination-timeout", OPT_DOUBLE(demux_termination_timeout)},
{"demuxer-cache-wait", OPT_FLAG(demuxer_cache_wait)},
@@ -588,7 +600,7 @@ static const m_option_t mp_opts[] = {
{"audio-file-auto", OPT_CHOICE(audiofile_auto,
{"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})},
{"cover-art-auto", OPT_CHOICE(coverart_auto,
- {"no", -1}, {"fuzzy", 1})},
+ {"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})},
{"", OPT_SUBSTRUCT(subs_rend, mp_subtitle_sub_opts)},
{"", OPT_SUBSTRUCT(subs_filt, mp_sub_filter_opts)},
@@ -620,7 +632,7 @@ static const m_option_t mp_opts[] = {
{"album", 2}),
.flags = UPDATE_VOL},
{"replaygain-preamp", OPT_FLOAT(rgain_preamp), .flags = UPDATE_VOL,
- M_RANGE(-15, 15)},
+ M_RANGE(-150, 150)},
{"replaygain-clip", OPT_FLAG(rgain_clip), .flags = UPDATE_VOL},
{"replaygain-fallback", OPT_FLOAT(rgain_fallback), .flags = UPDATE_VOL,
M_RANGE(-200, 60)},
@@ -686,6 +698,7 @@ static const m_option_t mp_opts[] = {
OPT_FLAG(ignore_path_in_watch_later_config)},
{"watch-later-directory", OPT_STRING(watch_later_directory),
.flags = M_OPT_FILE},
+ {"watch-later-options", OPT_STRINGLIST(watch_later_options)},
{"ordered-chapters", OPT_FLAG(ordered_chapters)},
{"ordered-chapters-files", OPT_STRING(ordered_chapters_files),
@@ -775,6 +788,7 @@ static const m_option_t mp_opts[] = {
#if HAVE_VULKAN
{"", OPT_SUBSTRUCT(vulkan_opts, vulkan_conf)},
+ {"", OPT_SUBSTRUCT(vulkan_display_opts, vulkan_display_conf)},
#endif
#if HAVE_D3D11
@@ -1028,10 +1042,49 @@ static const struct MPOpts mp_default_opts = {
"Artist", "Album", "Album_Artist", "Comment", "Composer",
"Date", "Description", "Genre", "Performer", "Rating",
"Series", "Title", "Track", "icy-title", "service_name",
+ "Uploader", "Channel_URL",
NULL
},
.cuda_device = -1,
+
+ .watch_later_options = (char **)(const char*[]){
+ "osd-level",
+ "speed",
+ "edition",
+ "pause",
+ "volume",
+ "mute",
+ "audio-delay",
+ "fullscreen",
+ "ontop",
+ "border",
+ "gamma",
+ "brightness",
+ "contrast",
+ "saturation",
+ "hue",
+ "deinterlace",
+ "vf",
+ "af",
+ "panscan",
+ "aid",
+ "vid",
+ "sid",
+ "sub-delay",
+ "sub-speed",
+ "sub-pos",
+ "sub-visibility",
+ "sub-scale",
+ "sub-use-margins",
+ "sub-ass-force-margins",
+ "sub-ass-vsfilter-aspect-compat",
+ "sub-ass-override",
+ "ab-loop-a",
+ "ab-loop-b",
+ "video-aspect-override",
+ NULL
+ },
};
const struct m_sub_options mp_opt_root = {
diff --git a/options/options.h b/options/options.h
index b865ff8..f3c8e31 100644
--- a/options/options.h
+++ b/options/options.h
@@ -22,7 +22,9 @@ typedef struct mp_vo_opts {
bool focus_on_open;
int screen_id;
+ char *screen_name;
int fsscreen_id;
+ char *fsscreen_name;
char *winname;
char *appid;
int x11_netwm;
@@ -71,6 +73,7 @@ typedef struct mp_vo_opts {
// Subtitle options needed by the subtitle decoders/renderers.
struct mp_subtitle_opts {
int sub_visibility;
+ int sec_sub_visibility;
int sub_pos;
float sub_delay;
float sub_fps;
@@ -104,13 +107,16 @@ struct mp_subtitle_opts {
int ass_justify;
int sub_clear_on_seek;
int teletext_page;
+ int sub_past_video_end;
};
struct mp_sub_filter_opts {
int sub_filter_SDH;
int sub_filter_SDH_harder;
int rf_enable;
+ int rf_plain;
char **rf_items;
+ char **jsre_items;
int rf_warn;
};
@@ -248,6 +254,7 @@ typedef struct MPOpts {
int write_filename_in_watch_later_config;
int ignore_path_in_watch_later_config;
char *watch_later_directory;
+ char **watch_later_options;
int pause;
int keep_open;
int keep_open_pause;
@@ -341,6 +348,7 @@ typedef struct MPOpts {
struct angle_opts *angle_opts;
struct opengl_opts *opengl_opts;
struct vulkan_opts *vulkan_opts;
+ struct vulkan_display_opts *vulkan_display_opts;
struct spirv_opts *spirv_opts;
struct d3d11_opts *d3d11_opts;
struct d3d11va_opts *d3d11va_opts;
diff --git a/osdep/macos/libmpv_helper.swift b/osdep/macos/libmpv_helper.swift
index d1b00cf..b140f99 100644
--- a/osdep/macos/libmpv_helper.swift
+++ b/osdep/macos/libmpv_helper.swift
@@ -47,23 +47,26 @@ class LibmpvHelper {
}
func initRender() {
- var advanced: CInt = 1
+ let advanced: CInt = 1
let api = UnsafeMutableRawPointer(mutating: (MPV_RENDER_API_TYPE_OPENGL as NSString).utf8String)
- var pAddress = mpv_opengl_init_params(get_proc_address: getProcAddress,
+ let pAddress = mpv_opengl_init_params(get_proc_address: getProcAddress,
get_proc_address_ctx: nil,
extra_exts: nil)
- var params: [mpv_render_param] = [
- mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api),
- mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: &pAddress),
- mpv_render_param(type: MPV_RENDER_PARAM_ADVANCED_CONTROL, data: &advanced),
- mpv_render_param()
- ]
-
- if (mpv_render_context_create(&mpvRenderContext, mpvHandle, &params) < 0)
- {
- log.sendError("Render context init has failed.")
- exit(1)
+
+ MPVHelper.withUnsafeMutableRawPointers([pAddress, advanced]) { (pointers: [UnsafeMutableRawPointer?]) in
+ var params: [mpv_render_param] = [
+ mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: api),
+ mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, data: pointers[0]),
+ mpv_render_param(type: MPV_RENDER_PARAM_ADVANCED_CONTROL, data: pointers[1]),
+ mpv_render_param()
+ ]
+
+ if (mpv_render_context_create(&mpvRenderContext, mpvHandle, &params) < 0) {
+ log.sendError("Render context init has failed.")
+ exit(1)
+ }
}
+
}
let getProcAddress: (@convention(c) (UnsafeMutableRawPointer?, UnsafePointer<Int8>?)
@@ -119,26 +122,29 @@ class LibmpvHelper {
deinitLock.lock()
if mpvRenderContext != nil {
var i: GLint = 0
- var flip: CInt = 1
- var skip: CInt = skip ? 1 : 0
- var ditherDepth = depth
+ let flip: CInt = 1
+ let skip: CInt = skip ? 1 : 0
+ let ditherDepth = depth
glGetIntegerv(GLenum(GL_DRAW_FRAMEBUFFER_BINDING), &i)
// CAOpenGLLayer has ownership of FBO zero yet can return it to us,
// so only utilize a newly received FBO ID if it is nonzero.
fbo = i != 0 ? i : fbo
- var data = mpv_opengl_fbo(fbo: Int32(fbo),
+ let data = mpv_opengl_fbo(fbo: Int32(fbo),
w: Int32(surface.width),
h: Int32(surface.height),
internal_format: 0)
- var params: [mpv_render_param] = [
- mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: &data),
- mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: &flip),
- mpv_render_param(type: MPV_RENDER_PARAM_DEPTH, data: &ditherDepth),
- mpv_render_param(type: MPV_RENDER_PARAM_SKIP_RENDERING, data: &skip),
- mpv_render_param()
- ]
- mpv_render_context_render(mpvRenderContext, &params);
+
+ MPVHelper.withUnsafeMutableRawPointers([data, flip, ditherDepth, skip]) { (pointers: [UnsafeMutableRawPointer?]) in
+ var params: [mpv_render_param] = [
+ mpv_render_param(type: MPV_RENDER_PARAM_OPENGL_FBO, data: pointers[0]),
+ mpv_render_param(type: MPV_RENDER_PARAM_FLIP_Y, data: pointers[1]),
+ mpv_render_param(type: MPV_RENDER_PARAM_DEPTH, data: pointers[2]),
+ mpv_render_param(type: MPV_RENDER_PARAM_SKIP_RENDERING, data: pointers[3]),
+ mpv_render_param()
+ ]
+ mpv_render_context_render(mpvRenderContext, &params);
+ }
} else {
glClearColor(0, 0, 0, 1)
glClear(GLbitfield(GL_COLOR_BUFFER_BIT))
@@ -161,16 +167,20 @@ class LibmpvHelper {
let u8Ptr = baseAddress.assumingMemoryBound(to: UInt8.self)
let iccBstr = bstrdup(nil, bstr(start: u8Ptr, len: ptr.count))
var icc = mpv_byte_array(data: iccBstr.start, size: iccBstr.len)
- let params = mpv_render_param(type: MPV_RENDER_PARAM_ICC_PROFILE, data: &icc)
- mpv_render_context_set_parameter(mpvRenderContext, params)
+ withUnsafeMutableBytes(of: &icc) { (ptr: UnsafeMutableRawBufferPointer) in
+ let params = mpv_render_param(type: MPV_RENDER_PARAM_ICC_PROFILE, data: ptr.baseAddress)
+ mpv_render_context_set_parameter(mpvRenderContext, params)
+ }
}
}
func setRenderLux(_ lux: Int) {
if mpvRenderContext == nil { return }
var light = lux
- let params = mpv_render_param(type: MPV_RENDER_PARAM_AMBIENT_LIGHT, data: &light)
- mpv_render_context_set_parameter(mpvRenderContext, params)
+ withUnsafeMutableBytes(of: &light) { (ptr: UnsafeMutableRawBufferPointer) in
+ let params = mpv_render_param(type: MPV_RENDER_PARAM_AMBIENT_LIGHT, data: ptr.baseAddress)
+ mpv_render_context_set_parameter(mpvRenderContext, params)
+ }
}
func commandAsync(_ cmd: [String?], id: UInt64 = 1) {
diff --git a/osdep/macos/mpv_helper.swift b/osdep/macos/mpv_helper.swift
index 0e207b9..6fde975 100644
--- a/osdep/macos/mpv_helper.swift
+++ b/osdep/macos/mpv_helper.swift
@@ -86,17 +86,23 @@ class MPVHelper {
func setOption(fullscreen: Bool) {
optsPtr.pointee.fullscreen = fullscreen
- m_config_cache_write_opt(optsCachePtr, UnsafeMutableRawPointer(&optsPtr.pointee.fullscreen))
+ _ = withUnsafeMutableBytes(of: &optsPtr.pointee.fullscreen) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
+ }
}
func setOption(minimized: Bool) {
optsPtr.pointee.window_minimized = Int32(minimized)
- m_config_cache_write_opt(optsCachePtr, UnsafeMutableRawPointer(&optsPtr.pointee.window_minimized))
+ _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_minimized) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
+ }
}
func setOption(maximized: Bool) {
optsPtr.pointee.window_maximized = Int32(maximized)
- m_config_cache_write_opt(optsCachePtr, UnsafeMutableRawPointer(&optsPtr.pointee.window_maximized))
+ _ = withUnsafeMutableBytes(of: &optsPtr.pointee.window_maximized) { (ptr: UnsafeMutableRawBufferPointer) in
+ m_config_cache_write_opt(optsCachePtr, ptr.baseAddress)
+ }
}
func setMacOptionCallback(_ callback: swift_wakeup_cb_fn, context object: AnyObject) {
@@ -123,4 +129,28 @@ class MPVHelper {
class func bridge<T: AnyObject>(ptr: UnsafeRawPointer) -> T {
return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue()
}
+
+ class func withUnsafeMutableRawPointers(_ arguments: [Any],
+ pointers: [UnsafeMutableRawPointer?] = [],
+ closure: (_ pointers: [UnsafeMutableRawPointer?]) -> Void) {
+ if arguments.count > 0 {
+ let args = Array(arguments.dropFirst(1))
+ var newPtrs = pointers
+ var firstArg = arguments.first
+ withUnsafeMutableBytes(of: &firstArg) { (ptr: UnsafeMutableRawBufferPointer) in
+ newPtrs.append(ptr.baseAddress)
+ withUnsafeMutableRawPointers(args, pointers: newPtrs, closure: closure)
+ }
+
+ return
+ }
+
+ closure(pointers)
+ }
+
+ class func getPointer<T>(_ value: inout T) -> UnsafeMutableRawPointer? {
+ return withUnsafeMutableBytes(of: &value) { (ptr: UnsafeMutableRawBufferPointer) in
+ ptr.baseAddress
+ }
+ }
}
diff --git a/osdep/macos/swift_compat.swift b/osdep/macos/swift_compat.swift
index c14aa08..24c00b0 100644
--- a/osdep/macos/swift_compat.swift
+++ b/osdep/macos/swift_compat.swift
@@ -33,7 +33,6 @@ extension String {
#endif
extension NSPasteboard.PasteboardType {
-
static let fileURLCompat: NSPasteboard.PasteboardType = {
if #available(OSX 10.13, *) {
return .fileURL
@@ -53,7 +52,6 @@ extension NSPasteboard.PasteboardType {
#if !swift(>=5.0)
extension Data {
-
mutating func withUnsafeMutableBytes<Type>(_ body: (UnsafeMutableRawBufferPointer) throws -> Type) rethrows -> Type {
let dataCount = count
return try withUnsafeMutableBytes { (ptr: UnsafeMutablePointer<UInt8>) throws -> Type in
@@ -65,33 +63,8 @@ extension Data {
#if !swift(>=4.2)
extension NSDraggingInfo {
-
var draggingPasteboard: NSPasteboard {
get { return draggingPasteboard() }
}
}
#endif
-
-#if !swift(>=4.1)
-extension Array {
-
- func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
- return try self.flatMap(transform)
- }
-}
-
-extension Array where Element == [CGLPixelFormatAttribute] {
-
- func contains(_ obj: [CGLPixelFormatAttribute]) -> Bool {
- return self.contains(where:{ $0 == obj })
- }
-}
-
-extension NSWindow.Level {
-
- static func +(left: NSWindow.Level, right: Int) -> NSWindow.Level {
- return NSWindow.Level(left.rawValue + right)
- }
-}
-#endif
-
diff --git a/osdep/macosx_application.h b/osdep/macosx_application.h
index 9a366d8..05d0b07 100644
--- a/osdep/macosx_application.h
+++ b/osdep/macosx_application.h
@@ -21,6 +21,11 @@
#include "osdep/macosx_menubar.h"
#include "options/m_option.h"
+enum {
+ FRAME_VISIBLE = 0,
+ FRAME_WHOLE,
+};
+
struct macos_opts {
int macos_title_bar_style;
int macos_title_bar_appearance;
@@ -29,6 +34,7 @@ struct macos_opts {
int macos_fs_animation_duration;
int macos_force_dedicated_gpu;
int macos_app_activation_policy;
+ int macos_geometry_calculation;
int cocoa_cb_sw_renderer;
int cocoa_cb_10bit_context;
};
diff --git a/osdep/macosx_application.m b/osdep/macosx_application.m
index 95c6a3f..bb8b67b 100644
--- a/osdep/macosx_application.m
+++ b/osdep/macosx_application.m
@@ -65,6 +65,8 @@ const struct m_sub_options macos_conf = {
{"macos-force-dedicated-gpu", OPT_FLAG(macos_force_dedicated_gpu)},
{"macos-app-activation-policy", OPT_CHOICE(macos_app_activation_policy,
{"regular", 0}, {"accessory", 1}, {"prohibited", 2})},
+ {"macos-geometry-calculation", OPT_CHOICE(macos_geometry_calculation,
+ {"visible", FRAME_VISIBLE}, {"whole", FRAME_WHOLE})},
{"cocoa-cb-sw-renderer", OPT_CHOICE(cocoa_cb_sw_renderer,
{"auto", -1}, {"no", 0}, {"yes", 1})},
{"cocoa-cb-10bit-context", OPT_FLAG(cocoa_cb_10bit_context)},
diff --git a/osdep/macosx_menubar.m b/osdep/macosx_menubar.m
index 11fdda5..6fefcb9 100644
--- a/osdep/macosx_menubar.m
+++ b/osdep/macosx_menubar.m
@@ -218,6 +218,13 @@
@"target" : self,
@"cmd" : @"cycle ontop"
}],
+ [NSMutableDictionary dictionaryWithDictionary:@{
+ @"name" : @"Toggle Visibility on All Workspaces",
+ @"action" : @"cmd:",
+ @"key" : @"",
+ @"target" : self,
+ @"cmd" : @"cycle on-all-workspaces"
+ }],
#if HAVE_MACOS_TOUCHBAR
@{ @"name": @"separator" },
[NSMutableDictionary dictionaryWithDictionary:@{
diff --git a/osdep/macosx_touchbar.h b/osdep/macosx_touchbar.h
index 0aa5a05..a03b68c 100644
--- a/osdep/macosx_touchbar.h
+++ b/osdep/macosx_touchbar.h
@@ -41,5 +41,6 @@ struct mpv_event;
@property(nonatomic, retain) NSDictionary *touchbarItems;
@property(nonatomic, assign) double duration;
@property(nonatomic, assign) double position;
+@property(nonatomic, assign) int pause;
@end
diff --git a/osdep/macosx_touchbar.m b/osdep/macosx_touchbar.m
index c189e32..ccce8f7 100644
--- a/osdep/macosx_touchbar.m
+++ b/osdep/macosx_touchbar.m
@@ -24,6 +24,7 @@
@synthesize touchbarItems = _touchbar_items;
@synthesize duration = _duration;
@synthesize position = _position;
+@synthesize pause = _pause;
- (id)init
{
@@ -86,58 +87,25 @@
@"name": @"Time Left"
}]
};
- }
- return self;
-}
-
--(void)processEvent:(struct mpv_event *)event
-{
- switch (event->event_id) {
- case MPV_EVENT_END_FILE: {
- self.position = 0;
- self.duration = 0;
- break;
- }
- case MPV_EVENT_PROPERTY_CHANGE: {
- [self handlePropertyChange:(mpv_event_property *)event->data];
- break;
- }
- }
-}
-
--(void)handlePropertyChange:(struct mpv_event_property *)property
-{
- NSString *name = [NSString stringWithUTF8String:property->name];
- mpv_format format = property->format;
- if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) {
- self.position = *(double *)property->data;
- self.position = self.position < 0 ? 0 : self.position;
- [self updateTouchBarTimeItems];
- } else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) {
- self.duration = *(double *)property->data;
- [self updateTouchBarTimeItems];
- } else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) {
- NSButton *playButton = self.touchbarItems[play][@"view"];
- if (*(int *)property->data) {
- playButton.image = self.touchbarItems[play][@"imageAlt"];
- } else {
- playButton.image = self.touchbarItems[play][@"image"];
- }
+ [self addObserver:self forKeyPath:@"visible" options:0 context:nil];
}
+ return self;
}
- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar
makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
{
if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"slider"]) {
- NSSliderTouchBarItem *tbItem = [[NSSliderTouchBarItem alloc] initWithIdentifier:identifier];
- tbItem.slider.minValue = 0.0f;
- tbItem.slider.maxValue = 100.0f;
- tbItem.target = self;
- tbItem.action = @selector(seekbarChanged:);
+ NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ NSSlider *slider = [NSSlider sliderWithTarget:self action:@selector(seekbarChanged:)];
+ slider.minValue = 0.0f;
+ slider.maxValue = 100.0f;
+ tbItem.view = slider;
tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
- [self.touchbarItems[identifier] setObject:tbItem.slider forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:slider forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
return tbItem;
} else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"button"]) {
NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
@@ -146,6 +114,8 @@
tbItem.view = tbButton;
tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
[self.touchbarItems[identifier] setObject:tbButton forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
return tbItem;
} else if ([self.touchbarItems[identifier][@"type"] isEqualToString:@"text"]) {
NSCustomTouchBarItem *tbItem = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
@@ -154,12 +124,124 @@
tbItem.view = tbText;
tbItem.customizationLabel = self.touchbarItems[identifier][@"name"];
[self.touchbarItems[identifier] setObject:tbText forKey:@"view"];
+ [self.touchbarItems[identifier] setObject:tbItem forKey:@"tbItem"];
+ [tbItem addObserver:self forKeyPath:@"visible" options:0 context:nil];
return tbItem;
}
return nil;
}
+- (void)observeValueForKeyPath:(NSString *)keyPath
+ ofObject:(id)object
+ change:(NSDictionary<NSKeyValueChangeKey,id> *)change
+ context:(void *)context {
+ if ([keyPath isEqualToString:@"visible"]) {
+ NSNumber *visible = [object valueForKey:@"visible"];
+ if (visible.boolValue) {
+ [self updateTouchBarTimeItems];
+ [self updatePlayButton];
+ }
+ }
+}
+
+- (void)updateTouchBarTimeItems
+{
+ if (!self.isVisible)
+ return;
+
+ [self updateSlider];
+ [self updateTimeLeft];
+ [self updateCurrentPosition];
+}
+
+- (void)updateSlider
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[seekBar][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"];
+
+ if (self.duration <= 0) {
+ seekSlider.enabled = NO;
+ seekSlider.doubleValue = 0;
+ } else {
+ seekSlider.enabled = YES;
+ if (!seekSlider.highlighted)
+ seekSlider.doubleValue = (self.position/self.duration)*100;
+ }
+}
+
+- (void)updateTimeLeft
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[timeLeft][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"];
+
+ [self removeConstraintForIdentifier:timeLeft];
+ if (self.duration <= 0) {
+ timeLeftItem.stringValue = @"";
+ } else {
+ int left = (int)(floor(self.duration)-floor(self.position));
+ NSString *leftFormat = [self formatTime:left];
+ NSString *durFormat = [self formatTime:self.duration];
+ timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat];
+ [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat]
+ forIdentifier:timeLeft];
+ }
+}
+
+- (void)updateCurrentPosition
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[currentPosition][@"tbItem"];
+ if (!tbItem.visible)
+ return;
+
+ NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"];
+ NSString *posFormat = [self formatTime:(int)floor(self.position)];
+ curPosItem.stringValue = posFormat;
+
+ [self removeConstraintForIdentifier:currentPosition];
+ if (self.duration <= 0) {
+ [self applyConstraintFromString:[self formatTime:self.position]
+ forIdentifier:currentPosition];
+ } else {
+ NSString *durFormat = [self formatTime:self.duration];
+ [self applyConstraintFromString:durFormat forIdentifier:currentPosition];
+ }
+}
+
+- (void)updatePlayButton
+{
+ NSCustomTouchBarItem *tbItem = self.touchbarItems[play][@"tbItem"];
+ if (!self.isVisible || !tbItem.visible)
+ return;
+
+ NSButton *playButton = self.touchbarItems[play][@"view"];
+ if (self.pause) {
+ playButton.image = self.touchbarItems[play][@"imageAlt"];
+ } else {
+ playButton.image = self.touchbarItems[play][@"image"];
+ }
+}
+
+- (void)buttonAction:(NSButton *)sender
+{
+ NSString *identifier = [self getIdentifierFromView:sender];
+ [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]];
+}
+
+- (void)seekbarChanged:(NSSlider *)slider
+{
+ NSString *identifier = [self getIdentifierFromView:slider];
+ NSString *seek = [NSString stringWithFormat:
+ self.touchbarItems[identifier][@"cmd"], slider.doubleValue];
+ [self.app queueCommand:(char *)[seek UTF8String]];
+}
+
- (NSString *)formatTime:(int)time
{
int seconds = time % 60;
@@ -204,48 +286,6 @@
}
}
-- (void)updateTouchBarTimeItemConstrains
-{
- [self removeConstraintForIdentifier:currentPosition];
- [self removeConstraintForIdentifier:timeLeft];
-
- if (self.duration <= 0) {
- [self applyConstraintFromString:[self formatTime:self.position]
- forIdentifier:currentPosition];
- } else {
- NSString *durFormat = [self formatTime:self.duration];
-
- [self applyConstraintFromString:durFormat forIdentifier:currentPosition];
- [self applyConstraintFromString:[NSString stringWithFormat:@"-%@", durFormat]
- forIdentifier:timeLeft];
- }
-}
-
-- (void)updateTouchBarTimeItems
-{
- NSSlider *seekSlider = self.touchbarItems[seekBar][@"view"];
- NSTextField *curPosItem = self.touchbarItems[currentPosition][@"view"];
- NSTextField *timeLeftItem = self.touchbarItems[timeLeft][@"view"];
-
- if (self.duration <= 0) {
- seekSlider.enabled = NO;
- seekSlider.doubleValue = 0;
- timeLeftItem.stringValue = @"";
- }
- else {
- seekSlider.enabled = YES;
- if (!seekSlider.highlighted)
- seekSlider.doubleValue = (self.position/self.duration)*100;
- int left = (int)(floor(self.duration)-floor(self.position));
- NSString *leftFormat = [self formatTime:left];
- timeLeftItem.stringValue = [NSString stringWithFormat:@"-%@", leftFormat];
- }
- NSString *posFormat = [self formatTime:(int)floor(self.position)];
- curPosItem.stringValue = posFormat;
-
- [self updateTouchBarTimeItemConstrains];
-}
-
- (NSString *)getIdentifierFromView:(id)view
{
NSString *identifier;
@@ -255,18 +295,40 @@
return identifier;
}
-- (void)buttonAction:(NSButton *)sender
+- (void)processEvent:(struct mpv_event *)event
{
- NSString *identifier = [self getIdentifierFromView:sender];
- [self.app queueCommand:(char *)[self.touchbarItems[identifier][@"cmd"] UTF8String]];
+ switch (event->event_id) {
+ case MPV_EVENT_END_FILE: {
+ self.position = 0;
+ self.duration = 0;
+ break;
+ }
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ [self handlePropertyChange:(mpv_event_property *)event->data];
+ break;
+ }
+ }
}
-- (void)seekbarChanged:(NSSliderTouchBarItem *)sender
+- (void)handlePropertyChange:(struct mpv_event_property *)property
{
- NSString *identifier = [self getIdentifierFromView:sender.slider];
- NSString *seek = [NSString stringWithFormat:
- self.touchbarItems[identifier][@"cmd"], sender.slider.doubleValue];
- [self.app queueCommand:(char *)[seek UTF8String]];
+ NSString *name = [NSString stringWithUTF8String:property->name];
+ mpv_format format = property->format;
+
+ if ([name isEqualToString:@"time-pos"] && format == MPV_FORMAT_DOUBLE) {
+ double newPosition = *(double *)property->data;
+ newPosition = newPosition < 0 ? 0 : newPosition;
+ if ((int)(floor(newPosition) - floor(self.position)) != 0) {
+ self.position = newPosition;
+ [self updateTouchBarTimeItems];
+ }
+ } else if ([name isEqualToString:@"duration"] && format == MPV_FORMAT_DOUBLE) {
+ self.duration = *(double *)property->data;
+ [self updateTouchBarTimeItems];
+ } else if ([name isEqualToString:@"pause"] && format == MPV_FORMAT_FLAG) {
+ self.pause = *(int *)property->data;
+ [self updatePlayButton];
+ }
}
@end
diff --git a/osdep/terminal-unix.c b/osdep/terminal-unix.c
index 78eb4c4..adcf3a3 100644
--- a/osdep/terminal-unix.c
+++ b/osdep/terminal-unix.c
@@ -45,6 +45,14 @@
// Timeout in ms after which the (normally ambiguous) ESC key is detected.
#define ESC_TIMEOUT 100
+// Timeout in ms after which the poll for input is aborted. The FG/BG state is
+// tested before every wait, and a positive value allows reactivating input
+// processing when mpv is brought to the foreground while it was running in the
+// background. In such a situation, an infinite timeout (-1) will keep mpv
+// waiting for input without realizing the terminal state changed - and thus
+// buffer all keypresses until ENTER is pressed.
+#define INPUT_TIMEOUT 1000
+
static volatile struct termios tio_orig;
static volatile int tio_orig_set;
@@ -213,6 +221,17 @@ static void process_input(struct input_ctx *input_ctx, bool timeout)
if (!match) { // normal or unknown key
int mods = 0;
if (buf.b[0] == '\033') {
+ if (buf.len > 1 && buf.b[1] == '[') {
+ // unknown CSI sequence. wait till it completes
+ for (int i = 2; i < buf.len; i++) {
+ if (buf.b[i] >= 0x40 && buf.b[i] <= 0x7E) {
+ skip_buf(&buf, i+1);
+ continue; // complete - throw it away
+ }
+ }
+ goto read_more; // not yet complete
+ }
+ // non-CSI esc sequence
skip_buf(&buf, 1);
if (buf.len > 0 && buf.b[0] > 0 && buf.b[0] < 127) {
// meta+normal key
@@ -227,7 +246,8 @@ static void process_input(struct input_ctx *input_ctx, bool timeout)
unsigned char c = buf.b[0];
skip_buf(&buf, 1);
if (c < 32) {
- c = c <= 25 ? (c + 'a' - 1) : (c - 25 + '2' - 1);
+ // 1..26 is ^A..^Z, and 27..31 is ^3..^7
+ c = c <= 26 ? (c + 'a' - 1) : (c + '3' - 27);
mods |= MP_KEY_MODIFIER_CTRL;
}
mp_input_put_key(input_ctx, c | mods);
@@ -397,7 +417,7 @@ static void *terminal_thread(void *ptr)
{ .events = POLLIN, .fd = death_pipe[0] },
{ .events = POLLIN, .fd = tty_in }
};
- int r = polldev(fds, stdin_ok ? 2 : 1, buf.len ? ESC_TIMEOUT : -1);
+ int r = polldev(fds, stdin_ok ? 2 : 1, buf.len ? ESC_TIMEOUT : INPUT_TIMEOUT);
if (fds[0].revents)
break;
if (fds[1].revents) {
diff --git a/osdep/timer-win2.c b/osdep/timer-win2.c
index 63c4235..72bcca5 100644
--- a/osdep/timer-win2.c
+++ b/osdep/timer-win2.c
@@ -19,12 +19,39 @@
#include <sys/time.h>
#include <mmsystem.h>
#include <stdlib.h>
+#include <versionhelpers.h>
+
#include "timer.h"
#include "config.h"
static LARGE_INTEGER perf_freq;
+// ms values
+static int hires_max = 50;
+static int hires_res = 1;
+
+int mp_start_hires_timers(int wait_ms)
+{
+#if !HAVE_UWP
+ // policy: request hires_res ms resolution if wait < hires_max ms
+ if (wait_ms > 0 && wait_ms <= hires_max &&
+ timeBeginPeriod(hires_res) == TIMERR_NOERROR)
+ {
+ return hires_res;
+ }
+#endif
+ return 0;
+}
+
+void mp_end_hires_timers(int res_ms)
+{
+#if !HAVE_UWP
+ if (res_ms > 0)
+ timeEndPeriod(res_ms);
+#endif
+}
+
void mp_sleep_us(int64_t us)
{
if (us < 0)
@@ -34,7 +61,9 @@ void mp_sleep_us(int64_t us)
// it may take some time until it actually starts to run again
if (us < 1000)
us = 1000;
+ int hrt = mp_start_hires_timers(us / 1000);
Sleep(us / 1000);
+ mp_end_hires_timers(hrt);
}
uint64_t mp_raw_time_us(void)
@@ -52,7 +81,37 @@ uint64_t mp_raw_time_us(void)
void mp_raw_time_init(void)
{
QueryPerformanceFrequency(&perf_freq);
+
#if !HAVE_UWP
- timeBeginPeriod(1); // request 1ms timer resolution
+ // allow (undocumented) control of all the High Res Timers parameters,
+ // for easier experimentation and diagnostic of bug reports.
+ const char *v;
+
+ // 1..1000 ms max timetout for hires (used in "perwait" mode)
+ if ((v = getenv("MPV_HRT_MAX"))) {
+ int hmax = atoi(v);
+ if (hmax >= 1 && hmax <= 1000)
+ hires_max = hmax;
+ }
+
+ // 1..15 ms hires resolution (not used in "never" mode)
+ if ((v = getenv("MPV_HRT_RES"))) {
+ int res = atoi(v);
+ if (res >= 1 && res <= 15)
+ hires_res = res;
+ }
+
+ // "always"/"never"/"perwait" (or "auto" - same as unset)
+ if (!(v = getenv("MPV_HRT")) || !strcmp(v, "auto"))
+ v = IsWindows10OrGreater() ? "perwait" : "always";
+
+ if (!strcmp(v, "perwait")) {
+ // no-op, already per-wait
+ } else if (!strcmp(v, "never")) {
+ hires_max = 0;
+ } else { // "always" or unknown value
+ hires_max = 0;
+ timeBeginPeriod(hires_res);
+ }
#endif
}
diff --git a/osdep/timer.h b/osdep/timer.h
index be5e9c0..8cc1d9d 100644
--- a/osdep/timer.h
+++ b/osdep/timer.h
@@ -37,6 +37,14 @@ uint64_t mp_raw_time_us(void);
// Sleep in microseconds.
void mp_sleep_us(int64_t us);
+#ifdef _WIN32
+// returns: timer resolution in ms if needed and started successfully, else 0
+int mp_start_hires_timers(int wait_ms);
+
+// call unconditionally with the return value of mp_start_hires_timers
+void mp_end_hires_timers(int resolution_ms);
+#endif /* _WIN32 */
+
#define MP_START_TIME 10000000
// Duration of a second in mpv time.
diff --git a/osdep/win32/pthread.c b/osdep/win32/pthread.c
index 141ecfc..e892d44 100644
--- a/osdep/win32/pthread.c
+++ b/osdep/win32/pthread.c
@@ -22,6 +22,8 @@
#include <sys/time.h>
#include <assert.h>
+#include "osdep/timer.h" // mp_{start,end}_hires_timers
+
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
{
BOOL pending;
@@ -78,11 +80,13 @@ static int cond_wait(pthread_cond_t *restrict cond,
DWORD ms)
{
BOOL res;
+ int hrt = mp_start_hires_timers(ms);
if (mutex->use_cs) {
res = SleepConditionVariableCS(cond, &mutex->lock.cs, ms);
} else {
res = SleepConditionVariableSRW(cond, &mutex->lock.srw, ms, 0);
}
+ mp_end_hires_timers(hrt);
return res ? 0 : ETIMEDOUT;
}
diff --git a/player/audio.c b/player/audio.c
index 50c0faa..8f1abbc 100644
--- a/player/audio.c
+++ b/player/audio.c
@@ -314,6 +314,7 @@ static void ao_chain_set_ao(struct ao_chain *ao_c, struct ao *ao)
mp_async_queue_set_notifier(ao_c->queue_filter, ao_c->ao_filter);
// Make sure filtering never stops with frames stuck in access filter.
mp_filter_set_high_priority(ao_c->queue_filter, true);
+ audio_update_volume(ao_c->mpctx);
}
if (ao_c->filter->ao_needs_update)
@@ -322,7 +323,7 @@ static void ao_chain_set_ao(struct ao_chain *ao_c, struct ao *ao)
mp_filter_wakeup(ao_c->ao_filter);
}
-static void reinit_audio_filters_and_output(struct MPContext *mpctx)
+static int reinit_audio_filters_and_output(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct ao_chain *ao_c = mpctx->ao_chain;
@@ -359,13 +360,13 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
{
ao_chain_set_ao(ao_c, mpctx->ao);
talloc_free(out_fmt);
- return;
+ return 0;
}
// Wait until all played.
if (mpctx->ao && ao_is_playing(mpctx->ao)) {
talloc_free(out_fmt);
- return;
+ return 0;
}
// Format change during syncing. Force playback start early, then wait.
if (ao_c->ao_queue && mp_async_queue_get_frames(ao_c->ao_queue) &&
@@ -374,11 +375,11 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
mpctx->audio_status = STATUS_READY;
mp_wakeup_core(mpctx);
talloc_free(out_fmt);
- return;
+ return 0;
}
if (mpctx->audio_status == STATUS_READY) {
talloc_free(out_fmt);
- return;
+ return 0;
}
uninit_audio_out(mpctx);
@@ -446,7 +447,7 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
reset_audio_state(mpctx);
mp_output_chain_reset_harder(ao_c->filter);
mp_wakeup_core(mpctx); // reinit with new format next time
- return;
+ return 0;
}
MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
@@ -477,12 +478,13 @@ static void reinit_audio_filters_and_output(struct MPContext *mpctx)
mp_wakeup_core(mpctx);
mp_notify(mpctx, MPV_EVENT_AUDIO_RECONFIG, NULL);
- return;
+ return 0;
init_error:
uninit_audio_chain(mpctx);
uninit_audio_out(mpctx);
error_on_track(mpctx, track);
+ return -1;
}
int init_audio_decoder(struct MPContext *mpctx, struct track *track)
@@ -516,7 +518,8 @@ void reinit_audio_chain(struct MPContext *mpctx)
struct track *track = NULL;
track = mpctx->current_track[0][STREAM_AUDIO];
if (!track || !track->stream) {
- uninit_audio_out(mpctx);
+ if (!mpctx->encode_lavc_ctx)
+ uninit_audio_out(mpctx);
error_on_track(mpctx, track);
return;
}
@@ -820,8 +823,10 @@ void audio_start_ao(struct MPContext *mpctx)
MP_VERBOSE(mpctx, "starting audio playback\n");
ao_start(ao_c->ao);
mpctx->audio_status = STATUS_PLAYING;
- if (ao_c->out_eof)
+ if (ao_c->out_eof) {
mpctx->audio_status = STATUS_DRAINING;
+ MP_VERBOSE(mpctx, "audio draining\n");
+ }
ao_c->underrun = false;
mpctx->logged_async_diff = -1;
mp_wakeup_core(mpctx);
@@ -849,8 +854,10 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
return;
}
- if (ao_c->filter->ao_needs_update)
- reinit_audio_filters_and_output(mpctx);
+ if (ao_c->filter->ao_needs_update) {
+ if (reinit_audio_filters_and_output(mpctx) < 0)
+ return;
+ }
if (mpctx->vo_chain && ao_c->track && ao_c->track->dec &&
mp_decoder_wrapper_get_pts_reset(ao_c->track->dec))
@@ -870,8 +877,10 @@ void fill_audio_out_buffers(struct MPContext *mpctx)
// until the old audio is fully played.
// (Buggy if AO underruns.)
if (mpctx->ao && ao_is_playing(mpctx->ao) &&
- mpctx->video_status != STATUS_EOF)
+ mpctx->video_status != STATUS_EOF) {
+ MP_VERBOSE(mpctx, "blocked, waiting for old audio to play\n");
ok = false;
+ }
if (ao_c->start_pts_known != ok || ao_c->start_pts != pts) {
ao_c->start_pts_known = ok;
diff --git a/player/command.c b/player/command.c
index 066e8be..4f00555 100644
--- a/player/command.c
+++ b/player/command.c
@@ -54,6 +54,7 @@
#include "options/m_option.h"
#include "options/m_property.h"
#include "options/m_config_frontend.h"
+#include "osdep/getpid.h"
#include "video/out/vo.h"
#include "video/csputils.h"
#include "video/hwdec.h"
@@ -426,6 +427,13 @@ static int mp_property_display_sync_active(void *ctx, struct m_property *prop,
return m_property_flag_ro(action, arg, mpctx->display_sync_active);
}
+static int mp_property_pid(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ // 32 bit on linux/windows - which C99 `int' is not guaranteed to hold
+ return m_property_int64_ro(action, arg, mp_getpid());
+}
+
/// filename with path (RO)
static int mp_property_path(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -1941,6 +1949,7 @@ static int get_track_entry(int item, int action, void *arg, void *ctx)
.unavailable = !track->lang},
{"audio-channels", SUB_PROP_INT(track_channels(track)),
.unavailable = track_channels(track) <= 0},
+ {"image", SUB_PROP_FLAG(track->image)},
{"albumart", SUB_PROP_FLAG(track->attached_picture)},
{"default", SUB_PROP_FLAG(track->default_track)},
{"forced", SUB_PROP_FLAG(track->forced_track)},
@@ -2067,6 +2076,16 @@ static int property_current_tracks(void *ctx, struct m_property *prop,
return M_PROPERTY_UNKNOWN;
struct track *t = mpctx->current_track[order][type];
+
+ if (!t && mpctx->lavfi) {
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ if (mpctx->tracks[n]->type == type && mpctx->tracks[n]->selected) {
+ t = mpctx->tracks[n];
+ break;
+ }
+ }
+ }
+
if (!t)
return M_PROPERTY_UNAVAILABLE;
@@ -2322,6 +2341,25 @@ static int mp_property_current_window_scale(void *ctx, struct m_property *prop,
if (vid_w < 1 || vid_h < 1)
return M_PROPERTY_UNAVAILABLE;
+ if (params.rotate % 180 == 90 && (vo->driver->caps & VO_CAP_ROTATE90))
+ MPSWAP(int, vid_w, vid_h);
+
+ if (vo->monitor_par < 1) {
+ vid_h = MPCLAMP(vid_h / vo->monitor_par, 1, 16000);
+ } else {
+ vid_w = MPCLAMP(vid_w * vo->monitor_par, 1, 16000);
+ }
+
+ if (action == M_PROPERTY_SET) {
+ // Also called by update_window_scale as a NULL property.
+ double scale = *(double *)arg;
+ int s[2] = {vid_w * scale, vid_h * scale};
+ if (s[0] <= 0 || s[1] <= 0)
+ return M_PROPERTY_INVALID_FORMAT;
+ vo_control(vo, VOCTRL_SET_UNFS_WINDOW_SIZE, s);
+ return M_PROPERTY_OK;
+ }
+
int s[2];
if (vo_control(vo, VOCTRL_GET_UNFS_WINDOW_SIZE, s) <= 0 ||
s[0] < 1 || s[1] < 1)
@@ -2334,20 +2372,9 @@ static int mp_property_current_window_scale(void *ctx, struct m_property *prop,
static void update_window_scale(struct MPContext *mpctx)
{
- struct vo *vo = mpctx->video_out;
- if (!vo)
- return;
-
- struct mp_image_params params = get_video_out_params(mpctx);
- int vid_w, vid_h;
- mp_image_params_get_dsize(&params, &vid_w, &vid_h);
- if (vid_w < 1 || vid_h < 1)
- return;
-
double scale = mpctx->opts->vo->window_scale;
- int s[2] = {vid_w * scale, vid_h * scale};
- if (s[0] > 0 && s[1] > 0)
- vo_control(vo, VOCTRL_SET_UNFS_WINDOW_SIZE, s);
+ mp_property_current_window_scale(mpctx, (struct m_property *)NULL,
+ M_PROPERTY_SET, (void*)&scale);
}
static int mp_property_display_fps(void *ctx, struct m_property *prop,
@@ -2404,6 +2431,23 @@ static int mp_property_vsync_jitter(void *ctx, struct m_property *prop,
return m_property_double_ro(action, arg, stddev);
}
+static int mp_property_display_resolution(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct vo *vo = mpctx->video_out;
+ if (!vo)
+ return M_PROPERTY_UNAVAILABLE;
+ int res[2];
+ if (vo_control(vo, VOCTRL_GET_DISPLAY_RES, &res) <= 0)
+ return M_PROPERTY_UNAVAILABLE;
+ if (strcmp(prop->name, "display-width") == 0) {
+ return m_property_int_ro(action, arg, res[0]);
+ } else {
+ return m_property_int_ro(action, arg, res[1]);
+ }
+}
+
static int mp_property_hidpi_scale(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -2587,14 +2631,14 @@ static int mp_property_osd_dim(void *ctx, struct m_property *prop,
(vo_res.display_par ? vo_res.display_par : 1);
struct m_sub_property props[] = {
- {"w", SUB_PROP_DOUBLE(vo_res.w)},
- {"h", SUB_PROP_DOUBLE(vo_res.h)},
+ {"w", SUB_PROP_INT(vo_res.w)},
+ {"h", SUB_PROP_INT(vo_res.h)},
{"par", SUB_PROP_DOUBLE(vo_res.display_par)},
{"aspect", SUB_PROP_DOUBLE(aspect)},
- {"mt", SUB_PROP_DOUBLE(vo_res.mt)},
- {"mb", SUB_PROP_DOUBLE(vo_res.mb)},
- {"ml", SUB_PROP_DOUBLE(vo_res.ml)},
- {"mr", SUB_PROP_DOUBLE(vo_res.mr)},
+ {"mt", SUB_PROP_INT(vo_res.mt)},
+ {"mb", SUB_PROP_INT(vo_res.mb)},
+ {"ml", SUB_PROP_INT(vo_res.ml)},
+ {"mr", SUB_PROP_INT(vo_res.mr)},
{0}
};
@@ -2805,12 +2849,12 @@ static int mp_property_sub_pos(void *ctx, struct m_property *prop,
return mp_property_generic_option(mpctx, prop, action, arg);
}
-static int mp_property_sub_text(void *ctx, struct m_property *prop,
- int action, void *arg)
+static int get_sub_text(void *ctx, struct m_property *prop,
+ int action, void *arg, int sub_index)
{
int type = *(int *)prop->priv;
MPContext *mpctx = ctx;
- struct track *track = mpctx->current_track[0][STREAM_SUB];
+ struct track *track = mpctx->current_track[sub_index][STREAM_SUB];
struct dec_sub *sub = track ? track->d_sub : NULL;
double pts = mpctx->playback_pts;
if (!sub || pts == MP_NOPTS_VALUE)
@@ -2831,12 +2875,25 @@ static int mp_property_sub_text(void *ctx, struct m_property *prop,
return M_PROPERTY_NOT_IMPLEMENTED;
}
+static int mp_property_sub_text(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ return get_sub_text(ctx, prop, action, arg, 0);
+}
+
+static int mp_property_secondary_sub_text(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ return get_sub_text(ctx, prop, action, arg, 1);
+}
+
static struct sd_times get_times(void *ctx, struct m_property *prop,
int action, void *arg)
{
struct sd_times res = { .start = MP_NOPTS_VALUE, .end = MP_NOPTS_VALUE };
MPContext *mpctx = ctx;
- struct track *track = mpctx->current_track[0][STREAM_SUB];
+ int track_ind = *(int *)prop->priv;
+ struct track *track = mpctx->current_track[track_ind][STREAM_SUB];
struct dec_sub *sub = track ? track->d_sub : NULL;
double pts = mpctx->playback_pts;
if (!sub || pts == MP_NOPTS_VALUE)
@@ -3500,6 +3557,7 @@ static int mp_property_script_props(void *ctx, struct m_property *prop,
// Base list of properties. This does not include option-mapped properties.
static const struct m_property mp_properties_base[] = {
// General
+ {"pid", mp_property_pid},
{"speed", mp_property_playback_speed},
{"audio-speed-correction", mp_property_av_speed_correction, .priv = "a"},
{"video-speed-correction", mp_property_av_speed_correction, .priv = "v"},
@@ -3519,6 +3577,8 @@ static const struct m_property mp_properties_base[] = {
{"total-avsync-change", mp_property_total_avsync_change},
{"mistimed-frame-count", mp_property_mistimed_frame_count},
{"vsync-ratio", mp_property_vsync_ratio},
+ {"display-width", mp_property_display_resolution},
+ {"display-height", mp_property_display_resolution},
{"decoder-frame-drop-count", mp_property_frame_drop_dec},
{"frame-drop-count", mp_property_frame_drop_vo},
{"vo-delayed-frame-count", mp_property_vo_delayed_frame_count},
@@ -3630,10 +3690,18 @@ static const struct m_property mp_properties_base[] = {
{"sub-pos", mp_property_sub_pos},
{"sub-text", mp_property_sub_text,
.priv = (void *)&(const int){SD_TEXT_TYPE_PLAIN}},
+ {"secondary-sub-text", mp_property_secondary_sub_text,
+ .priv = (void *)&(const int){SD_TEXT_TYPE_PLAIN}},
{"sub-text-ass", mp_property_sub_text,
.priv = (void *)&(const int){SD_TEXT_TYPE_ASS}},
- {"sub-start", mp_property_sub_start},
- {"sub-end", mp_property_sub_end},
+ {"sub-start", mp_property_sub_start,
+ .priv = (void *)&(const int){0}},
+ {"secondary-sub-start", mp_property_sub_start,
+ .priv = (void *)&(const int){1}},
+ {"sub-end", mp_property_sub_end,
+ .priv = (void *)&(const int){0}},
+ {"secondary-sub-end", mp_property_sub_end,
+ .priv = (void *)&(const int){1}},
{"vf", mp_property_vf},
{"af", mp_property_af},
@@ -3710,10 +3778,10 @@ static const char *const *const mp_event_property_change[] = {
"estimated-vf-fps", "drop-frame-count", "vo-drop-frame-count",
"total-avsync-change", "audio-speed-correction", "video-speed-correction",
"vo-delayed-frame-count", "mistimed-frame-count", "vsync-ratio",
- "estimated-display-fps", "vsync-jitter", "sub-text", "audio-bitrate",
- "video-bitrate", "sub-bitrate", "decoder-frame-drop-count",
+ "estimated-display-fps", "vsync-jitter", "sub-text", "secondary-sub-text",
+ "audio-bitrate", "video-bitrate", "sub-bitrate", "decoder-frame-drop-count",
"frame-drop-count", "video-frame-info", "vf-metadata", "af-metadata",
- "sub-start", "sub-end"),
+ "sub-start", "sub-end", "secondary-sub-start", "secondary-sub-end"),
E(MP_EVENT_DURATION_UPDATE, "duration"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
@@ -3734,7 +3802,8 @@ static const char *const *const mp_event_property_change[] = {
"demuxer-cache-state"),
E(MP_EVENT_WIN_RESIZE, "current-window-scale", "osd-width", "osd-height",
"osd-par", "osd-dimensions"),
- E(MP_EVENT_WIN_STATE, "display-names", "display-fps"),
+ E(MP_EVENT_WIN_STATE, "display-names", "display-fps", "display-width",
+ "display-height"),
E(MP_EVENT_WIN_STATE2, "display-hidpi-scale"),
E(MP_EVENT_FOCUS, "focused"),
E(MP_EVENT_CHANGE_PLAYLIST, "playlist", "playlist-pos", "playlist-pos-1",
@@ -3917,6 +3986,7 @@ static const struct property_osd_display {
{"taskbar-progress", "Progress in taskbar"},
{"snap-window", "Snap to screen edges"},
{"ontop", "Stay on top"},
+ {"on-all-workspaces", "Visibility on all workspaces"},
{"border", "Border"},
{"framedrop", "Framedrop"},
{"deinterlace", "Deinterlace"},
@@ -3935,6 +4005,9 @@ static const struct property_osd_display {
{"sub-visibility",
.msg = "Subtitles ${!sub-visibility==yes:hidden}"
"${?sub-visibility==yes:visible${?sub==no: (but no subtitles selected)}}"},
+ {"secondary-sub-visibility",
+ .msg = "Secondary Subtitles ${!secondary-sub-visibility==yes:hidden}"
+ "${?secondary-sub-visibility==yes:visible${?secondary-sid==no: (but no secondary subtitles selected)}}"},
{"sub-forced-only", "Forced sub only"},
{"sub-scale", "Sub Scale"},
{"sub-ass-vsfilter-aspect-compat", "Subtitle VSFilter aspect compat"},
@@ -4859,7 +4932,9 @@ static void cmd_add_cycle(void *p)
bool is_cycle = !!cmd->priv;
char *property = cmd->args[0].v.s;
- if (cmd->cmd->repeated && !check_property_autorepeat(property, mpctx)) {
+ if (cmd->cmd->repeated && !check_property_autorepeat(property, mpctx) &&
+ !(cmd->cmd->flags & MP_ALLOW_REPEAT) /* "repeatable" prefix */ )
+ {
MP_VERBOSE(mpctx, "Dropping command '%s' from auto-repeated key.\n",
cmd->cmd->original);
return;
@@ -4981,13 +5056,14 @@ static void cmd_sub_step_seek(void *p)
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
bool step = *(bool *)cmd->priv;
+ int track_ind = cmd->args[1].v.i;
if (!mpctx->playback_initialized) {
cmd->success = false;
return;
}
- struct track *track = mpctx->current_track[0][STREAM_SUB];
+ struct track *track = mpctx->current_track[track_ind][STREAM_SUB];
struct dec_sub *sub = track ? track->d_sub : NULL;
double refpts = get_current_time(mpctx);
if (sub && refpts != MP_NOPTS_VALUE) {
@@ -5097,7 +5173,7 @@ static void cmd_loadlist(void *p)
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
char *filename = cmd->args[0].v.s;
- bool append = cmd->args[1].v.i;
+ int append = cmd->args[1].v.i;
struct playlist *pl = playlist_parse_file(filename, cmd->abort->cancel,
mpctx->global);
@@ -5114,7 +5190,7 @@ static void cmd_loadlist(void *p)
if (!new)
new = playlist_get_first(mpctx->playlist);
- if (!append && new)
+ if ((!append || (append == 2 && !mpctx->playlist->current)) && new)
mp_set_playlist_entry(mpctx, new);
struct mpv_node *res = &cmd->result;
@@ -5240,6 +5316,8 @@ static void cmd_track_add(void *p)
struct mp_cmd_ctx *cmd = p;
struct MPContext *mpctx = cmd->mpctx;
int type = *(int *)cmd->priv;
+ bool is_albumart = type == STREAM_VIDEO &&
+ cmd->args[4].v.i;
if (mpctx->stop_play) {
cmd->success = false;
@@ -5259,7 +5337,7 @@ static void cmd_track_add(void *p)
}
}
int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type,
- cmd->abort->cancel);
+ cmd->abort->cancel, is_albumart);
if (first < 0) {
cmd->success = false;
return;
@@ -5322,8 +5400,10 @@ static void cmd_track_reload(void *p)
if (t && t->is_external && t->external_filename) {
char *filename = talloc_strdup(NULL, t->external_filename);
+ bool is_albumart = t->attached_picture;
mp_remove_track(mpctx, t);
- nt_num = mp_add_external_file(mpctx, filename, type, cmd->abort->cancel);
+ nt_num = mp_add_external_file(mpctx, filename, type, cmd->abort->cancel,
+ is_albumart);
talloc_free(filename);
}
@@ -6015,10 +6095,28 @@ const struct mp_cmd_def mp_cmds[] = {
},
{ "playlist-shuffle", cmd_playlist_shuffle, },
{ "playlist-unshuffle", cmd_playlist_unshuffle, },
- { "sub-step", cmd_sub_step_seek, { {"skip", OPT_INT(v.i)} },
- .allow_auto_repeat = true, .priv = &(const bool){true} },
- { "sub-seek", cmd_sub_step_seek, { {"skip", OPT_INT(v.i)} },
- .allow_auto_repeat = true, .priv = &(const bool){false} },
+ { "sub-step", cmd_sub_step_seek,
+ {
+ {"skip", OPT_INT(v.i)},
+ {"flags", OPT_CHOICE(v.i,
+ {"primary", 0},
+ {"secondary", 1}),
+ OPTDEF_INT(0)},
+ },
+ .allow_auto_repeat = true,
+ .priv = &(const bool){true}
+ },
+ { "sub-seek", cmd_sub_step_seek,
+ {
+ {"skip", OPT_INT(v.i)},
+ {"flags", OPT_CHOICE(v.i,
+ {"primary", 0},
+ {"secondary", 1}),
+ OPTDEF_INT(0)},
+ },
+ .allow_auto_repeat = true,
+ .priv = &(const bool){false}
+ },
{ "print-text", cmd_print_text, { {"text", OPT_STRING(v.s)} },
.is_noisy = true, .allow_auto_repeat = true },
{ "show-text", cmd_show_text,
@@ -6070,6 +6168,7 @@ const struct mp_cmd_def mp_cmds[] = {
.flags = MP_CMD_OPT_ARG},
{"title", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
{"lang", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG},
+ {"albumart", OPT_FLAG(v.i), .flags = MP_CMD_OPT_ARG},
},
.priv = &(const int){STREAM_VIDEO},
.spawn_thread = true,
@@ -6165,7 +6264,10 @@ const struct mp_cmd_def mp_cmds[] = {
{ "loadlist", cmd_loadlist,
{
{"url", OPT_STRING(v.s)},
- {"flags", OPT_CHOICE(v.i, {"replace", 0}, {"append", 1}),
+ {"flags", OPT_CHOICE(v.i,
+ {"replace", 0},
+ {"append", 1},
+ {"append-play", 2}),
.flags = MP_CMD_OPT_ARG},
},
.spawn_thread = true,
@@ -6471,6 +6573,25 @@ static void update_priority(struct MPContext *mpctx)
#endif
}
+static void update_track_switch(struct MPContext *mpctx, int order, int type)
+{
+ if (!mpctx->playback_initialized)
+ return;
+
+ int tid = mpctx->opts->stream_id[order][type];
+ struct track *track;
+ if (tid == -1) {
+ // If "auto" reset to default track selection
+ track = select_default_track(mpctx, order, type);
+ mark_track_selection(mpctx, order, type, -1);
+ } else {
+ track = mp_track_by_tid(mpctx, type, tid);
+ }
+ mp_switch_track_n(mpctx, order, type, track, (tid == -1) ? 0 : FLAG_MARK_SELECTION);
+ print_track_list(mpctx, "Track switched:");
+ mp_wakeup_core(mpctx);
+}
+
void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
bool self_update)
{
@@ -6525,6 +6646,16 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global);
}
+ if (opt_ptr == &opts->vo->video_driver_list) {
+ struct track *track = mpctx->current_track[0][STREAM_VIDEO];
+ uninit_video_out(mpctx);
+ reinit_video_chain(mpctx);
+ if (track)
+ reselect_demux_stream(mpctx, track, true);
+
+ mp_wakeup_core(mpctx);
+ }
+
if (flags & UPDATE_AUDIO)
reload_audio_output(mpctx);
@@ -6619,15 +6750,8 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
for (int order = 0; order < num_ptracks[type]; order++) {
- if (opt_ptr == &opts->stream_id[order][type] &&
- mpctx->playback_initialized)
- {
- struct track *track =
- mp_track_by_tid(mpctx, type, opts->stream_id[order][type]);
- mp_switch_track_n(mpctx, order, type, track, FLAG_MARK_SELECTION);
- print_track_list(mpctx, "Track switched:");
- mp_wakeup_core(mpctx);
- }
+ if (opt_ptr == &opts->stream_id[order][type])
+ update_track_switch(mpctx, order, type);
}
}
@@ -6636,6 +6760,11 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,
if (opt_ptr == &opts->vo->taskbar_progress)
update_vo_playback_state(mpctx);
+
+ if (opt_ptr == &opts->image_display_duration && mpctx->vo_chain
+ && mpctx->vo_chain->is_sparse && !mpctx->ao_chain
+ && mpctx->video_status == STATUS_DRAINING)
+ mpctx->time_frame = opts->image_display_duration;
}
void mp_notify_property(struct MPContext *mpctx, const char *property)
diff --git a/player/configfiles.c b/player/configfiles.c
index 3930cf2..65dd9df 100644
--- a/player/configfiles.c
+++ b/player/configfiles.c
@@ -37,6 +37,7 @@
#include "misc/ctype.h"
#include "options/path.h"
#include "options/m_config.h"
+#include "options/m_config_frontend.h"
#include "options/parse_configfile.h"
#include "common/playlist.h"
#include "options/options.h"
@@ -237,63 +238,6 @@ exit:
return res;
}
-static const char *const backup_properties[] = {
- "osd-level",
- //"loop",
- "speed",
- "options/edition",
- "pause",
- "volume",
- "mute",
- "audio-delay",
- //"balance",
- "fullscreen",
- "ontop",
- "border",
- "gamma",
- "brightness",
- "contrast",
- "saturation",
- "hue",
- "options/deinterlace",
- "vf",
- "af",
- "panscan",
- "options/aid",
- "options/vid",
- "options/sid",
- "sub-delay",
- "sub-speed",
- "sub-pos",
- "sub-visibility",
- "sub-scale",
- "sub-use-margins",
- "sub-ass-force-margins",
- "sub-ass-vsfilter-aspect-compat",
- "sub-style-override",
- "ab-loop-a",
- "ab-loop-b",
- "options/video-aspect-override",
- 0
-};
-
-// Used to retrieve default settings, which should not be stored in the
-// resume config. Uses backup_properties[] meaning/order of values.
-// This explicitly includes values set by config files and command line.
-void mp_get_resume_defaults(struct MPContext *mpctx)
-{
- char **list =
- talloc_zero_array(mpctx, char*, MP_ARRAY_SIZE(backup_properties));
- for (int i = 0; backup_properties[i]; i++) {
- const char *pname = backup_properties[i];
- char *val = NULL;
- int r = mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
- if (r == M_PROPERTY_OK)
- list[i] = talloc_steal(list, val);
- }
- mpctx->resume_defaults = list;
-}
-
// Should follow what parser-cfg.c does/needs
static bool needs_config_quoting(const char *s)
{
@@ -368,25 +312,21 @@ void mp_write_watch_later_conf(struct MPContext *mpctx)
} else {
fprintf(file, "start=%f\n", pos);
}
- for (int i = 0; backup_properties[i]; i++) {
- const char *pname = backup_properties[i];
- char *val = NULL;
- int r = mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
- if (r == M_PROPERTY_OK) {
- if (strncmp(pname, "options/", 8) == 0)
- pname += 8;
- // Only store it if it's different from the initial value.
- char *prev = mpctx->resume_defaults[i];
- if (!prev || strcmp(prev, val) != 0) {
- if (needs_config_quoting(val)) {
- // e.g. '%6%STRING'
- fprintf(file, "%s=%%%d%%%s\n", pname, (int)strlen(val), val);
- } else {
- fprintf(file, "%s=%s\n", pname, val);
- }
+ char **watch_later_options = mpctx->opts->watch_later_options;
+ for (int i = 0; watch_later_options && watch_later_options[i]; i++) {
+ char *pname = watch_later_options[i];
+ // Only store it if it's different from the initial value.
+ if (m_config_watch_later_backup_opt_changed(mpctx->mconfig, pname)) {
+ char *val = NULL;
+ mp_property_do(pname, M_PROPERTY_GET_STRING, &val, mpctx);
+ if (needs_config_quoting(val)) {
+ // e.g. '%6%STRING'
+ fprintf(file, "%s=%%%d%%%s\n", pname, (int)strlen(val), val);
+ } else {
+ fprintf(file, "%s=%s\n", pname, val);
}
+ talloc_free(val);
}
- talloc_free(val);
}
fclose(file);
diff --git a/player/core.h b/player/core.h
index ec154de..b597137 100644
--- a/player/core.h
+++ b/player/core.h
@@ -136,6 +136,7 @@ struct track {
char *title;
bool default_track, forced_track, dependent_track;
bool visual_impaired_track, hearing_impaired_track;
+ bool image;
bool attached_picture;
char *lang;
@@ -295,7 +296,6 @@ typedef struct MPContext {
// Return code to use with PT_QUIT
int quit_custom_rc;
bool has_quit_custom_rc;
- char **resume_defaults;
// Global file statistics
int files_played; // played without issues (even if stopped by user)
@@ -511,7 +511,6 @@ void audio_start_ao(struct MPContext *mpctx);
// configfiles.c
void mp_parse_cfgfiles(struct MPContext *mpctx);
void mp_load_auto_profiles(struct MPContext *mpctx);
-void mp_get_resume_defaults(struct MPContext *mpctx);
void mp_load_playback_resume(struct MPContext *mpctx, const char *file);
void mp_write_watch_later_conf(struct MPContext *mpctx);
void mp_delete_watch_later_conf(struct MPContext *mpctx, const char *file);
@@ -528,7 +527,8 @@ void mp_abort_trigger_locked(struct MPContext *mpctx,
struct mp_abort_entry *abort);
void uninit_player(struct MPContext *mpctx, unsigned int mask);
int mp_add_external_file(struct MPContext *mpctx, char *filename,
- enum stream_type filter, struct mp_cancel *cancel);
+ enum stream_type filter, struct mp_cancel *cancel,
+ bool cover_art);
void mark_track_selection(struct MPContext *mpctx, int order,
enum stream_type type, int value);
#define FLAG_MARK_SELECTION 1
@@ -547,7 +547,8 @@ void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e);
void mp_play_files(struct MPContext *mpctx);
void update_demuxer_properties(struct MPContext *mpctx);
void print_track_list(struct MPContext *mpctx, const char *msg);
-void reselect_demux_stream(struct MPContext *mpctx, struct track *track);
+void reselect_demux_stream(struct MPContext *mpctx, struct track *track,
+ bool refresh_only);
void prepare_playlist(struct MPContext *mpctx, struct playlist *pl);
void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel);
struct track *select_default_track(struct MPContext *mpctx, int order,
diff --git a/player/external_files.c b/player/external_files.c
index 06585bf..442314d 100644
--- a/player/external_files.c
+++ b/player/external_files.c
@@ -39,7 +39,11 @@ static const char *const sub_exts[] = {"utf", "utf8", "utf-8", "idx", "sub",
static const char *const audio_exts[] = {"mp3", "aac", "mka", "dts", "flac",
"ogg", "m4a", "ac3", "opus", "wav",
- "wv",
+ "wv", "eac3",
+ NULL};
+
+static const char *const image_exts[] = {"jpg", "jpeg", "png", "gif", "bmp",
+ "webp",
NULL};
// Stolen from: vlc/-/blob/master/modules/meta_engine/folder.c#L40
@@ -79,18 +83,19 @@ static int test_ext(bstr ext)
return STREAM_SUB;
if (test_ext_list(ext, audio_exts))
return STREAM_AUDIO;
+ if (test_ext_list(ext, image_exts))
+ return STREAM_VIDEO;
return -1;
}
-static int test_cover_filename(bstr fname, int *priority)
+static int test_cover_filename(bstr fname)
{
for (int n = 0; cover_files[n]; n++) {
if (bstrcasecmp(bstr0(cover_files[n]), fname) == 0) {
- *priority = MP_ARRAY_SIZE(cover_files) - n;
- return STREAM_VIDEO;
+ return MP_ARRAY_SIZE(cover_files) - n;
}
}
- return -1;
+ return 0;
}
bool mp_might_be_subtitle_file(const char *filename)
@@ -191,10 +196,7 @@ static void append_dir_subtitles(struct mpv_global *global, struct MPOpts *opts,
talloc_steal(tmpmem2, dename.start);
// check what it is (most likely)
- int cover_prio = 0;
int type = test_ext(tmp_fname_ext);
- if (type < 0)
- type = test_cover_filename(dename, &cover_prio);
char **langs = NULL;
int fuzz = -1;
switch (type) {
@@ -244,15 +246,14 @@ static void append_dir_subtitles(struct mpv_global *global, struct MPOpts *opts,
if (bstr_find(tmp_fname_trim, f_fname_trim) >= 0 && fuzz >= 1)
prio |= 2; // contains the movie name
+ if (type == STREAM_VIDEO && fuzz >= 1 && prio == 0)
+ prio = test_cover_filename(dename);
+
// doesn't contain the movie name
// don't try in the mplayer subtitle directory
if (!limit_fuzziness && fuzz >= 2)
prio |= 1;
- // cover art: just accept it
- if (type == STREAM_VIDEO && fuzz >= 1)
- prio = cover_prio;
-
mp_dbg(log, "Potential external file: \"%s\" Priority: %d\n",
de->d_name, prio);
diff --git a/player/javascript.c b/player/javascript.c
index 8cb263e..50a1550 100644
--- a/player/javascript.c
+++ b/player/javascript.c
@@ -40,7 +40,6 @@
#include "misc/bstr.h"
#include "osdep/timer.h"
#include "osdep/threads.h"
-#include "osdep/getpid.h"
#include "stream/stream.h"
#include "sub/osd.h"
#include "core.h"
@@ -102,16 +101,25 @@ static uint64_t jsL_checkuint64(js_State *J, int idx);
// padded with undefined if called with less, or bigger if called with more.
//
// - Almost all vm APIs (js_*) may throw an error - a longjmp to the last
-// recovery/catch point, which could skip releasing resources. Use protected
-// code (e.g. js_pcall) between aquisition and release. Alternatively, use
-// the autofree mechanism to manage it more easily. See more details below.
+// recovery/catch point, which could skip releasing resources. This includes
+// js_try itself(!), except at the outer-most [1] js_try which is always
+// entering the try part (and the catch part if the try part throws).
+// The assumption should be that anything can throw and needs careful setup.
+// One such automated setup is the autofree mechanism. Details later.
//
// - Unless named s_foo, all the functions at this file (inc. init) which
// touch the vm may throw, but either cleanup resources regardless (mostly
// autofree) or leave allocated resources on caller-provided talloc context
// which the caller should release, typically with autofree (e.g. makenode).
//
-// - Functions named s_foo (safe foo) never throw, return 0 on success, else 1.
+// - Functions named s_foo (safe foo) never throw if called at the outer-most
+// try-levels, or, inside JS C functions - never throw after allocating.
+// If they didn't throw then they return 0 on success, 1 on js-errors.
+//
+// [1] In practice the N outer-most (nested) tries are guaranteed to enter the
+// try/carch code, where N is the mujs try-stack size (64 with mujs 1.1.3).
+// But because we can't track try-level at (called-back) JS C functions,
+// it's only guaranteed when we know we're near the outer-most try level.
/**********************************************************************
* mpv scripting API error handling
@@ -202,13 +210,27 @@ static bool pushed_error(js_State *J, int err, int def)
// inserted into the vm using af_newcfunction, but otherwise used normally.
//
// To wrap an autofree function af_TARGET in C:
-// 1. Create a wrapper s_TARGET which runs af_TARGET safely inside js_try.
-// 2. Use s_TARGET like so (always autofree, and throws if af_TARGET threw):
-// void *af = talloc_new(NULL);
-// int r = s_TARGET(J, ..., af); // use J, af where the callee expects.
+// 1. Create a wrapper s_TARGET which does this:
+// if (js_try(J))
+// return 1;
+// *af = talloc_new(NULL);
+// af_TARGET(J, ..., *af);
+// js_endtry(J);
+// return 0;
+// 2. Use s_TARGET like so (frees if allocated, throws if {s,af}_TARGET threw):
+// void *af = NULL;
+// int r = s_TARGET(J, ..., &af); // use J, af where the callee expects.
// talloc_free(af);
// if (r)
// js_throw(J);
+//
+// The reason that the allocation happens inside try/catch is that js_try
+// itself can throw (if it runs out of try-stack) and therefore the code
+// inside the try part is not reached - but neither is the catch part(!),
+// and instead it throws to the next outer catch - but before we've allocated
+// anything, hence no leaks on such case. If js_try did get entered, then the
+// allocation happened, and then if af_TARGET threw then s_TARGET will catch
+// it (and return 1) and we'll free if afterwards.
// add_af_file, add_af_dir, add_af_mpv_alloc take a valid FILE*/DIR*/char* value
// respectively, and fclose/closedir/mpv_free it when the parent is freed.
@@ -267,11 +289,12 @@ static mpv_node *new_af_mpv_node(void *parent)
typedef void (*af_CFunction)(js_State*, void*);
// safely run autofree js c function directly
-static int s_run_af_jsc(js_State *J, af_CFunction fn, void *af)
+static int s_run_af_jsc(js_State *J, af_CFunction fn, void **af)
{
if (js_try(J))
return 1;
- fn(J, af);
+ *af = talloc_new(NULL);
+ fn(J, *af);
js_endtry(J);
return 0;
}
@@ -286,8 +309,8 @@ static void script__autofree(js_State *J)
af_CFunction fn = (af_CFunction)js_touserdata(J, -1, "af_fn");
js_pop(J, 2);
- void *af = talloc_new(NULL);
- int r = s_run_af_jsc(J, fn, af);
+ void *af = NULL;
+ int r = s_run_af_jsc(J, fn, &af);
talloc_free(af);
if (r)
js_throw(J);
@@ -358,11 +381,12 @@ static void af_push_file(js_State *J, const char *fname, int limit, void *af)
}
// Safely run af_push_file.
-static int s_push_file(js_State *J, const char *fname, int limit, void *af)
+static int s_push_file(js_State *J, const char *fname, int limit, void **af)
{
if (js_try(J))
return 1;
- af_push_file(J, fname, limit, af);
+ *af = talloc_new(NULL);
+ af_push_file(J, fname, limit, *af);
js_endtry(J);
return 0;
}
@@ -370,8 +394,8 @@ static int s_push_file(js_State *J, const char *fname, int limit, void *af)
// Called directly, push up to limit bytes of file fname (from builtin/os).
static void push_file_content(js_State *J, const char *fname, int limit)
{
- void *af = talloc_new(NULL);
- int r = s_push_file(J, fname, limit, af);
+ void *af = NULL;
+ int r = s_push_file(J, fname, limit, &af);
talloc_free(af);
if (r)
js_throw(J);
@@ -925,33 +949,31 @@ static void script_get_user_path(js_State *J, void *af)
js_pushstring(J, mp_get_user_path(af, jctx(J)->mpctx->global, path));
}
-// args: none
-static void script_getpid(js_State *J)
-{
- js_pushnumber(J, mp_getpid());
-}
-
-// args: prefixed file name, data (c-str)
-static void script_write_file(js_State *J, void *af)
+// args: is_append, prefixed file name, data (c-str)
+static void script__write_file(js_State *J, void *af)
{
static const char *prefix = "file://";
- const char *fname = js_tostring(J, 1);
- const char *data = js_tostring(J, 2);
+ bool append = js_toboolean(J, 1);
+ const char *fname = js_tostring(J, 2);
+ const char *data = js_tostring(J, 3);
+ const char *opstr = append ? "append" : "write";
+
if (strstr(fname, prefix) != fname) // simple protection for incorrect use
js_error(J, "File name must be prefixed with '%s'", prefix);
fname += strlen(prefix);
fname = mp_get_user_path(af, jctx(J)->mpctx->global, fname);
- MP_VERBOSE(jctx(J), "Writing file '%s'\n", fname);
+ MP_VERBOSE(jctx(J), "%s file '%s'\n", opstr, fname);
- FILE *f = fopen(fname, "wb");
+ FILE *f = fopen(fname, append ? "ab" : "wb");
if (!f)
- js_error(J, "Cannot open file for writing: '%s'", fname);
+ js_error(J, "Cannot open (%s) file: '%s'", opstr, fname);
add_af_file(af, f);
int len = strlen(data); // limited by terminating null
int wrote = fwrite(data, 1, len, f);
if (len != wrote)
- js_error(J, "Cannot write to file: '%s'", fname);
+ js_error(J, "Cannot %s to file: '%s'", opstr, fname);
+ js_pushboolean(J, 1); // success. doesn't touch last_error
}
// args: env var name
@@ -1183,11 +1205,10 @@ static const struct fn_entry utils_fns[] = {
FN_ENTRY(split_path, 1),
AF_ENTRY(join_path, 2),
AF_ENTRY(get_user_path, 1),
- FN_ENTRY(getpid, 0),
FN_ENTRY(get_env_list, 0),
FN_ENTRY(read_file, 2),
- AF_ENTRY(write_file, 2),
+ AF_ENTRY(_write_file, 3),
FN_ENTRY(getenv, 1),
FN_ENTRY(compile_js, 2),
FN_ENTRY(_gc, 1),
diff --git a/player/javascript/defaults.js b/player/javascript/defaults.js
index 0904524..bf83f42 100644
--- a/player/javascript/defaults.js
+++ b/player/javascript/defaults.js
@@ -276,9 +276,18 @@ function dispatch_key_binding(name, state, key_name) {
var binds_tid = 0; // flush timer id. actual id's are always true-thy
mp.flush_key_bindings = function flush_key_bindings() {
+ function prioritized_inputs(arr) {
+ return arr.sort(function(a, b) { return a.id > b.id })
+ .map(function(bind) { return bind.input });
+ }
+
var def = [], forced = [];
- for (var n in binds) // Array.join() will later skip undefined .input
- (binds[n].forced ? forced : def).push(binds[n].input);
+ for (var n in binds)
+ if (binds[n].input)
+ (binds[n].forced ? forced : def).push(binds[n]);
+ // newer bindings for the same key override/hide older ones
+ def = prioritized_inputs(def);
+ forced = prioritized_inputs(forced);
var sect = "input_" + mp.script_name;
mp.commandv("define-section", sect, def.join("\n"), "default");
@@ -306,13 +315,14 @@ function add_binding(forced, key, name, fn, opts) {
fn = name;
name = false;
}
- if (!name)
- name = "__keybinding" + next_bid++; // new unique binding name
var key_data = {forced: forced};
switch (typeof opts) { // merge opts into key_data
case "string": key_data[opts] = true; break;
case "object": for (var o in opts) key_data[o] = opts[o];
}
+ key_data.id = next_bid++;
+ if (!name)
+ name = "__keybinding" + key_data.id; // new unique binding name
if (key_data.complex) {
mp.register_script_message(name, function msg_cb() {
@@ -663,7 +673,10 @@ mp.get_script_file = function() { return mp.script_file };
mp.get_script_directory = function() { return mp.script_path };
mp.get_time = function() { return mp.get_time_ms() / 1000 };
mp.utils.getcwd = function() { return mp.get_property("working-directory") };
+mp.utils.getpid = function() { return mp.get_property_number("pid") }
mp.get_mouse_pos = function() { return mp.get_property_native("mouse-pos") };
+mp.utils.write_file = mp.utils._write_file.bind(null, false);
+mp.utils.append_file = mp.utils._write_file.bind(null, true);
mp.dispatch_event = dispatch_event;
mp.process_timers = process_timers;
mp.notify_idle_observers = notify_idle_observers;
@@ -748,7 +761,7 @@ g.mp_event_loop = function mp_event_loop() {
wait = 0; // poll the next one
} else {
wait = process_timers() / 1000;
- if (wait != 0) {
+ if (wait != 0 && iobservers.length) {
notify_idle_observers(); // can add timers -> recalculate wait
wait = peek_timers_wait() / 1000;
}
@@ -756,9 +769,12 @@ g.mp_event_loop = function mp_event_loop() {
} while (mp.keep_running);
};
-})(this)
-try {
- // let the user extend us, e.g. for updating mp.module_paths
- require("~~/.init");
-} catch(e) {}
+// let the user extend us, e.g. by adding items to mp.module_paths
+var initjs = mp.find_config_file("init.js"); // ~~/init.js
+if (initjs)
+ require(initjs.slice(0, -3)); // remove ".js"
+else if ((initjs = mp.find_config_file(".init.js")))
+ mp.msg.warn("Use init.js instead of .init.js (ignoring " + initjs + ")");
+
+})(this)
diff --git a/player/loadfile.c b/player/loadfile.c
index 0adc8e3..51865cf 100644
--- a/player/loadfile.c
+++ b/player/loadfile.c
@@ -362,7 +362,9 @@ void update_demuxer_properties(struct MPContext *mpctx)
// Enables or disables the stream for the given track, according to
// track->selected.
-void reselect_demux_stream(struct MPContext *mpctx, struct track *track)
+// With refresh_only=true, refreshes the stream if it's enabled.
+void reselect_demux_stream(struct MPContext *mpctx, struct track *track,
+ bool refresh_only)
{
if (!track->stream)
return;
@@ -372,7 +374,10 @@ void reselect_demux_stream(struct MPContext *mpctx, struct track *track)
if (track->type == STREAM_SUB)
pts -= 10.0;
}
- demuxer_select_track(track->demuxer, track->stream, pts, track->selected);
+ if (refresh_only)
+ demuxer_refresh_track(track->demuxer, track->stream, pts);
+ else
+ demuxer_select_track(track->demuxer, track->stream, pts, track->selected);
}
static void enable_demux_thread(struct MPContext *mpctx, struct demuxer *demux)
@@ -416,6 +421,7 @@ static struct track *add_stream_track(struct MPContext *mpctx,
.dependent_track = stream->dependent_track,
.visual_impaired_track = stream->visual_impaired_track,
.hearing_impaired_track = stream->hearing_impaired_track,
+ .image = stream->image,
.attached_picture = stream->attached_picture != NULL,
.lang = stream->lang,
.demuxer = demuxer,
@@ -471,8 +477,12 @@ static bool compare_track(struct track *t1, struct track *t2, char **langs,
return !t1->is_external;
bool ext1 = t1->is_external && !t1->no_default;
bool ext2 = t2->is_external && !t2->no_default;
- if (ext1 != ext2)
+ if (ext1 != ext2) {
+ if (t1->attached_picture && t2->attached_picture
+ && opts->audio_display == 1)
+ return !ext1;
return ext1;
+ }
if (t1->auto_loaded != t2->auto_loaded)
return !t1->auto_loaded;
int l1 = match_lang(langs, t1->lang), l2 = match_lang(langs, t2->lang);
@@ -658,14 +668,14 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type
if (current->remux_sink)
close_recorder_and_error(mpctx);
current->selected = false;
- reselect_demux_stream(mpctx, current);
+ reselect_demux_stream(mpctx, current, false);
}
mpctx->current_track[order][type] = track;
if (track) {
track->selected = true;
- reselect_demux_stream(mpctx, track);
+ reselect_demux_stream(mpctx, track, false);
}
if (type == STREAM_VIDEO && order == 0) {
@@ -753,7 +763,8 @@ bool mp_remove_track(struct MPContext *mpctx, struct track *track)
// cancel will generally be used to abort the loading process, but on success
// the demuxer is changed to be slaved to mpctx->playback_abort instead.
int mp_add_external_file(struct MPContext *mpctx, char *filename,
- enum stream_type filter, struct mp_cancel *cancel)
+ enum stream_type filter, struct mp_cancel *cancel,
+ bool cover_art)
{
struct MPOpts *opts = mpctx->opts;
if (!filename || mp_cancel_test(cancel))
@@ -827,8 +838,8 @@ int mp_add_external_file(struct MPContext *mpctx, char *filename,
t->external_filename = talloc_strdup(t, filename);
t->no_default = sh->type != filter;
t->no_auto_select = t->no_default;
- // filter==STREAM_VIDEO always means cover art.
- t->attached_picture = t->type == STREAM_VIDEO && filter == STREAM_VIDEO;
+ // if we found video, and we are loading cover art, flag as such.
+ t->attached_picture = t->type == STREAM_VIDEO && cover_art;
if (first_num < 0 && (filter == STREAM_TYPE_COUNT || sh->type == filter))
first_num = mpctx->num_tracks - 1;
}
@@ -853,7 +864,9 @@ static void open_external_files(struct MPContext *mpctx, char **files,
files = mp_dup_str_array(tmp, files);
for (int n = 0; files && files[n]; n++)
- mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort);
+ // when given filter is set to video, we are loading up cover art
+ mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort,
+ filter == STREAM_VIDEO);
talloc_free(tmp);
}
@@ -892,15 +905,16 @@ void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel)
goto skip;
if (e->type == STREAM_VIDEO && (sc[STREAM_VIDEO] || !sc[STREAM_AUDIO]))
goto skip;
- int first = mp_add_external_file(mpctx, e->fname, e->type, cancel);
+
+ // when given filter is set to video, we are loading up cover art
+ int first = mp_add_external_file(mpctx, e->fname, e->type, cancel,
+ e->type == STREAM_VIDEO);
if (first < 0)
goto skip;
for (int n = first; n < mpctx->num_tracks; n++) {
struct track *t = mpctx->tracks[n];
t->auto_loaded = true;
- t->attached_picture =
- t->type == STREAM_VIDEO && e->type == STREAM_VIDEO;
if (!t->lang)
t->lang = talloc_strdup(t, e->lang);
}
@@ -1341,7 +1355,7 @@ done:
if (mpctx->playback_initialized) {
for (int n = 0; n < mpctx->num_tracks; n++)
- reselect_demux_stream(mpctx, mpctx->tracks[n]);
+ reselect_demux_stream(mpctx, mpctx->tracks[n], false);
}
mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL);
@@ -1583,7 +1597,7 @@ static void play_current_file(struct MPContext *mpctx)
}
for (int n = 0; n < mpctx->num_tracks; n++)
- reselect_demux_stream(mpctx, mpctx->tracks[n]);
+ reselect_demux_stream(mpctx, mpctx->tracks[n], false);
update_demuxer_properties(mpctx);
@@ -1610,7 +1624,7 @@ static void play_current_file(struct MPContext *mpctx)
if (mpctx->vo_chain && mpctx->vo_chain->is_coverart) {
MP_INFO(mpctx,
- "Displaying attached picture. Use --no-audio-display to prevent this.\n");
+ "Displaying cover art. Use --no-audio-display to prevent this.\n");
}
if (!mpctx->vo_chain)
@@ -1950,11 +1964,18 @@ void open_recorder(struct MPContext *mpctx, bool on_init)
MP_TARRAY_APPEND(NULL, streams, num_streams, track->stream);
}
+ struct demux_attachment **attachments = talloc_array(NULL, struct demux_attachment*, mpctx->demuxer->num_attachments);
+ for (int n = 0; n < mpctx->demuxer->num_attachments; n++) {
+ attachments[n] = &mpctx->demuxer->attachments[n];
+ }
+
mpctx->recorder = mp_recorder_create(mpctx->global, mpctx->opts->record_file,
- streams, num_streams);
+ streams, num_streams,
+ attachments, mpctx->demuxer->num_attachments);
if (!mpctx->recorder) {
talloc_free(streams);
+ talloc_free(attachments);
close_recorder_and_error(mpctx);
return;
}
@@ -1978,5 +1999,6 @@ void open_recorder(struct MPContext *mpctx, bool on_init)
}
talloc_free(streams);
+ talloc_free(attachments);
}
diff --git a/player/lua.c b/player/lua.c
index 2eb163e..bdb08e3 100644
--- a/player/lua.c
+++ b/player/lua.c
@@ -45,7 +45,6 @@
#include "osdep/subprocess.h"
#include "osdep/timer.h"
#include "osdep/threads.h"
-#include "osdep/getpid.h"
#include "stream/stream.h"
#include "sub/osd.h"
#include "core.h"
@@ -122,14 +121,30 @@ static void mp_lua_optarg(lua_State *L, int arg)
lua_pushnil(L);
}
+// autofree: avoid leaks if a lua-error occurs between talloc new/free.
+// If a lua c-function does a new allocation (not tied to an existing context),
+// and an uncaught lua-error occures before "free" - the allocation is leaked.
+
// autofree lua C function: same as lua_CFunction but with these differences:
-// - It accepts an additional void* argument which is a pre-initialized talloc
-// context which it can use, and which is freed with its children once the
-// function completes - regardless if a lua error occured or not. If a lua
-// error did occur then it's re-thrown after the ctx is freed.
-// - At struct fn_entry it's declared with AF_ENTRY instead of FN_ENTRY.
+// - It accepts an additional void* argument - a pre-initialized talloc context
+// which it can use, and which is freed with its children once the function
+// completes - regardless if a lua error occured or not. If a lua error did
+// occur then it's re-thrown after the ctx is freed.
+// The stack/arguments/upvalues/return are the same as with lua_CFunction.
+// - It's inserted into the lua VM using af_pushc{function,closure} instead of
+// lua_pushc{function,closure}, which takes care of wrapping it with the
+// automatic talloc alocation + lua-error-handling + talloc release.
+// This requires using AF_ENTRY instead of FN_ENTRY at struct fn_entry.
+// - The autofree overhead per call is roughly two additional plain lua calls.
+// Typically that's up to 20% slower than plain new+free without "auto",
+// and at most about twice slower - compared to bare new+free lua_CFunction.
+// - The overhead of af_push* is one aditional lua-c-closure with two upvalues.
typedef int (*af_CFunction)(lua_State *L, void *ctx);
+static void af_pushcclosure(lua_State *L, af_CFunction fn, int n);
+#define af_pushcfunction(L, fn) af_pushcclosure((L), (fn), 0)
+
+
// add_af_dir, add_af_mpv_alloc take a valid DIR*/char* value respectively,
// and closedir/mpv_free it when the parent is freed.
@@ -655,6 +670,8 @@ static int script_set_property_number(lua_State *L)
static void makenode(void *tmp, mpv_node *dst, lua_State *L, int t)
{
+ luaL_checkstack(L, 6, "makenode");
+
if (t < 0)
t = lua_gettop(L) + (t + 1);
switch (lua_type(L, t)) {
@@ -854,7 +871,7 @@ static int script_get_property_number(lua_State *L)
static void pushnode(lua_State *L, mpv_node *node)
{
- luaL_checkstack(L, 6, "stack overflow");
+ luaL_checkstack(L, 6, "pushnode");
switch (node->format) {
case MPV_FORMAT_STRING:
@@ -1146,12 +1163,6 @@ static int script_join_path(lua_State *L)
return 1;
}
-static int script_getpid(lua_State *L)
-{
- lua_pushnumber(L, mp_getpid());
- return 1;
-}
-
static int script_parse_json(lua_State *L, void *tmp)
{
mp_lua_optarg(L, 2);
@@ -1247,7 +1258,6 @@ static const struct fn_entry utils_fns[] = {
FN_ENTRY(file_info),
FN_ENTRY(split_path),
FN_ENTRY(join_path),
- FN_ENTRY(getpid),
AF_ENTRY(parse_json),
AF_ENTRY(format_json),
FN_ENTRY(get_env_list),
@@ -1262,39 +1272,53 @@ typedef struct autofree_data {
/* runs the target autofree script_* function with the ctx argument */
static int script_autofree_call(lua_State *L)
{
- autofree_data *data = lua_touserdata(L, lua_upvalueindex(1));
+ // n*args &data
+ autofree_data *data = lua_touserdata(L, -1);
+ lua_pop(L, 1); // n*args
assert(data && data->target && data->ctx);
return data->target(L, data->ctx);
}
static int script_autofree_trampoline(lua_State *L)
{
+ // n*args
autofree_data data = {
- .target = lua_touserdata(L, lua_upvalueindex(1)),
+ .target = lua_touserdata(L, lua_upvalueindex(2)), // fn
.ctx = NULL,
};
assert(data.target);
- int nargs = lua_gettop(L);
-
- lua_pushlightuserdata(L, &data);
- lua_pushcclosure(L, script_autofree_call, 1);
- lua_insert(L, 1);
+ lua_pushvalue(L, lua_upvalueindex(1)); // n*args autofree_call (closure)
+ lua_insert(L, 1); // autofree_call n*args
+ lua_pushlightuserdata(L, &data); // autofree_call n*args &data
data.ctx = talloc_new(NULL);
- int r = lua_pcall(L, nargs, LUA_MULTRET, 0);
+ int r = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0); // m*retvals
talloc_free(data.ctx);
if (r)
lua_error(L);
- return lua_gettop(L);
+ return lua_gettop(L); // m (retvals)
}
-static void mp_push_autofree_fn(lua_State *L, af_CFunction fn)
+static void af_pushcclosure(lua_State *L, af_CFunction fn, int n)
{
+ // Instead of pushing a direct closure of fn with n upvalues, we push an
+ // autofree_trampoline closure with two upvalues:
+ // 1: autofree_call closure with the n upvalues given here.
+ // 2: fn
+ //
+ // when called the autofree_trampoline closure will pcall the autofree_call
+ // closure with the current lua call arguments and an additional argument
+ // which holds ctx and fn. the autofree_call closure (with the n upvalues
+ // given here) calls fn directly and provides it with the ctx C argument,
+ // so that fn sees the exact n upvalues and lua call arguments as intended,
+ // wrapped with ctx init/cleanup.
+
+ lua_pushcclosure(L, script_autofree_call, n);
lua_pushlightuserdata(L, fn);
- lua_pushcclosure(L, script_autofree_trampoline, 1);
+ lua_pushcclosure(L, script_autofree_trampoline, 2);
}
static void register_package_fns(lua_State *L, char *module,
@@ -1303,7 +1327,7 @@ static void register_package_fns(lua_State *L, char *module,
push_module_table(L, module); // modtable
for (int n = 0; e[n].name; n++) {
if (e[n].af) {
- mp_push_autofree_fn(L, e[n].af); // modtable fn
+ af_pushcclosure(L, e[n].af, 0); // modtable fn
} else {
lua_pushcclosure(L, e[n].fn, 0); // modtable fn
}
diff --git a/player/lua/auto_profiles.lua b/player/lua/auto_profiles.lua
index 6856138..fba57cc 100644
--- a/player/lua/auto_profiles.lua
+++ b/player/lua/auto_profiles.lua
@@ -136,16 +136,20 @@ setmetatable(p, {
})
local function compile_cond(name, s)
- -- (pre 5.2 ignores the extra arguments)
- local chunk, err = load("return " .. s, "profile " .. name .. " condition",
- "t", evil_magic)
+ local code, chunkname = "return " .. s, "profile " .. name .. " condition"
+ local chunk, err
+ if setfenv then -- lua 5.1
+ chunk, err = loadstring(code, chunkname)
+ if chunk then
+ setfenv(chunk, evil_magic)
+ end
+ else -- lua 5.2
+ chunk, err = load(code, chunkname, "t", evil_magic)
+ end
if not chunk then
msg.error("Profile '" .. name .. "' condition: " .. err)
chunk = function() return false end
end
- if setfenv then
- setfenv(chunk, evil_magic)
- end
return chunk
end
diff --git a/player/lua/console.lua b/player/lua/console.lua
index a483bbe..36c0c95 100644
--- a/player/lua/console.lua
+++ b/player/lua/console.lua
@@ -36,6 +36,8 @@ function detect_platform()
return 'windows'
elseif mp.get_property_native('options/macos-force-dedicated-gpu', o) ~= o then
return 'macos'
+ elseif os.getenv('WAYLAND_DISPLAY') then
+ return 'wayland'
end
return 'x11'
end
@@ -258,7 +260,7 @@ function prev_utf8(str, pos)
return pos
end
--- Insert a character at the current cursor position (any_unicode, Shift+Enter)
+-- Insert a character at the current cursor position (any_unicode)
function handle_char_input(c)
if insert_mode then
line = line:sub(1, cursor - 1) .. c .. line:sub(next_utf8(line, cursor))
@@ -311,10 +313,13 @@ function clear()
update()
end
--- Close the REPL if the current line is empty, otherwise do nothing (Ctrl+D)
+-- Close the REPL if the current line is empty, otherwise delete the next
+-- character (Ctrl+D)
function maybe_exit()
if line == '' then
set_active(false)
+ else
+ handle_del()
end
end
@@ -569,7 +574,7 @@ function go_end()
update()
end
--- Delete from the cursor to the end of the word (Ctrl+W)
+-- Delete from the cursor to the beginning of the word (Ctrl+Backspace)
function del_word()
local before_cur = line:sub(1, cursor - 1)
local after_cur = line:sub(cursor)
@@ -580,6 +585,18 @@ function del_word()
update()
end
+-- Delete from the cursor to the end of the word (Ctrl+Del)
+function del_next_word()
+ if cursor > line:len() then return end
+
+ local before_cur = line:sub(1, cursor - 1)
+ local after_cur = line:sub(cursor)
+
+ after_cur = after_cur:gsub('^%s*[^%s]+', '', 1)
+ line = before_cur .. after_cur
+ update()
+end
+
-- Delete from the cursor to the end of the line (Ctrl+K)
function del_to_eol()
line = line:sub(1, cursor - 1)
@@ -609,6 +626,14 @@ function get_clipboard(clip)
if not res.error then
return res.stdout
end
+ elseif platform == 'wayland' then
+ local res = utils.subprocess({
+ args = { 'wl-paste', clip and '-n' or '-np' },
+ playback_only = false,
+ })
+ if not res.error then
+ return res.stdout
+ end
elseif platform == 'windows' then
local res = utils.subprocess({
args = { 'powershell', '-NoProfile', '-Command', [[& {
@@ -647,7 +672,7 @@ function get_clipboard(clip)
end
-- Paste text from the window-system's clipboard. 'clip' determines whether the
--- clipboard or the primary selection buffer is used (on X11 only.)
+-- clipboard or the primary selection buffer is used (on X11 and Wayland only.)
function paste(clip)
local text = get_clipboard(clip)
local before_cur = line:sub(1, cursor - 1)
@@ -665,25 +690,37 @@ function get_bindings()
{ 'enter', handle_enter },
{ 'kp_enter', handle_enter },
{ 'shift+enter', function() handle_char_input('\n') end },
+ { 'ctrl+j', handle_enter },
+ { 'ctrl+m', handle_enter },
{ 'bs', handle_backspace },
{ 'shift+bs', handle_backspace },
+ { 'ctrl+h', handle_backspace },
{ 'del', handle_del },
{ 'shift+del', handle_del },
{ 'ins', handle_ins },
{ 'shift+ins', function() paste(false) end },
{ 'mbtn_mid', function() paste(false) end },
{ 'left', function() prev_char() end },
+ { 'ctrl+b', function() prev_char() end },
{ 'right', function() next_char() end },
+ { 'ctrl+f', function() next_char() end },
{ 'up', function() move_history(-1) end },
+ { 'ctrl+p', function() move_history(-1) end },
{ 'wheel_up', function() move_history(-1) end },
{ 'down', function() move_history(1) end },
+ { 'ctrl+n', function() move_history(1) end },
{ 'wheel_down', function() move_history(1) end },
{ 'wheel_left', function() end },
{ 'wheel_right', function() end },
{ 'ctrl+left', prev_word },
+ { 'alt+b', prev_word },
{ 'ctrl+right', next_word },
+ { 'alt+f', next_word },
{ 'tab', complete },
+ { 'ctrl+i', complete },
+ { 'ctrl+a', go_home },
{ 'home', go_home },
+ { 'ctrl+e', go_end },
{ 'end', go_end },
{ 'pgup', handle_pgup },
{ 'pgdwn', handle_pgdown },
@@ -694,7 +731,10 @@ function get_bindings()
{ 'ctrl+u', del_to_start },
{ 'ctrl+v', function() paste(true) end },
{ 'meta+v', function() paste(true) end },
+ { 'ctrl+bs', del_word },
{ 'ctrl+w', del_word },
+ { 'ctrl+del', del_next_word },
+ { 'alt+d', del_next_word },
{ 'kp_dec', function() handle_char_input('.') end },
}
diff --git a/player/lua/defaults.lua b/player/lua/defaults.lua
index 920ee42..de28409 100644
--- a/player/lua/defaults.lua
+++ b/player/lua/defaults.lua
@@ -330,9 +330,11 @@ function mp.get_next_timeout()
return timer.next_deadline - now
end
--- Run timers that have met their deadline.
--- Return: next absolute time a timer expires as number, or nil if no timers
+-- Run timers that have met their deadline at the time of invocation.
+-- Return: time>0 in seconds till the next due timer, 0 if there are due timers
+-- (aborted to avoid infinite loop), or nil if no timers
local function process_timers()
+ local t0 = nil
while true do
local timer = get_next_timer()
if not timer then
@@ -343,6 +345,14 @@ local function process_timers()
if wait > 0 then
return wait
else
+ if not t0 then
+ t0 = now -- first due callback: always executes, remember t0
+ elseif timer.next_deadline > t0 then
+ -- don't block forever with slow callbacks and endless timers.
+ -- we'll continue right after checking mpv events.
+ return 0
+ end
+
if timer.oneshot then
timer:kill()
else
@@ -518,8 +528,19 @@ function mp.dispatch_events(allow_wait)
local wait = 0
if not more_events then
wait = process_timers() or 1e20 -- infinity for all practical purposes
- for _, handler in ipairs(idle_handlers) do
- handler()
+ if wait ~= 0 then
+ local idle_called = nil
+ for _, handler in ipairs(idle_handlers) do
+ idle_called = true
+ handler()
+ end
+ if idle_called then
+ -- handlers don't complete in 0 time, and may modify timers
+ wait = mp.get_next_timeout() or 1e20
+ if wait < 0 then
+ wait = 0
+ end
+ end
end
-- Resume playloop - important especially if an error happened while
-- suspended, and the error was handled, but no resume was done.
@@ -747,6 +768,10 @@ function mp_utils.getcwd()
return mp.get_property("working-directory")
end
+function mp_utils.getpid()
+ return mp.get_property_number("pid")
+end
+
function mp_utils.format_bytes_humanized(b)
local d = {"Bytes", "KiB", "MiB", "GiB", "TiB", "PiB"}
local i = 1
diff --git a/player/lua/options.lua b/player/lua/options.lua
index 1ea0d72..4517df2 100644
--- a/player/lua/options.lua
+++ b/player/lua/options.lua
@@ -15,14 +15,14 @@ local function typeconv(desttypeval, val)
elseif val == "no" then
val = false
else
- msg.error("Error: Can't convert " .. val .. " to boolean!")
+ msg.error("Error: Can't convert '" .. val .. "' to boolean!")
val = nil
end
elseif type(desttypeval) == "number" then
if not (tonumber(val) == nil) then
val = tonumber(val)
else
- msg.error("Error: Can't convert " .. val .. " to number!")
+ msg.error("Error: Can't convert '" .. val .. "' to number!")
val = nil
end
end
@@ -92,7 +92,7 @@ local function read_options(options, identifier, on_update)
-- match found values with defaults
if option_types[key] == nil then
msg.warn(conffilename..":"..linecounter..
- " unknown key " .. key .. ", ignoring")
+ " unknown key '" .. key .. "', ignoring")
else
local convval = typeconv(option_types[key], val)
if convval == nil then
diff --git a/player/lua/osc.lua b/player/lua/osc.lua
index af59470..19f4e9b 100644
--- a/player/lua/osc.lua
+++ b/player/lua/osc.lua
@@ -48,6 +48,7 @@ local user_opts = {
windowcontrols = "auto", -- whether to show window controls
windowcontrols_alignment = "right", -- which side to show window controls on
greenandgrumpy = false, -- disable santa hat
+ livemarkers = true, -- update seekbar chapter markers on duration change
}
-- read options from config and command-line
@@ -101,6 +102,7 @@ local state = {
tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds
mp_screen_sizeX, mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs
initREQ = false, -- is a re-init request pending?
+ marginsREQ = false, -- is a margins update pending?
last_mouseX, last_mouseY, -- last mouse position, to detect significant mouse movement
mouse_in_window = false,
message_text,
@@ -130,6 +132,12 @@ local is_december = os.date("*t").month == 12
-- Helperfunctions
--
+function kill_animation()
+ state.anistart = nil
+ state.animation = nil
+ state.anitype = nil
+end
+
function set_osd(res_x, res_y, text)
if state.osd.res_x == res_x and
state.osd.res_y == res_y and
@@ -591,8 +599,38 @@ end
-- Element Rendering
--
+-- returns nil or a chapter element from the native property chapter-list
+function get_chapter(possec)
+ local cl = mp.get_property_native("chapter-list", {})
+ local ch = nil
+
+ -- chapters might not be sorted by time. find nearest-before/at possec
+ for n=1, #cl do
+ if possec >= cl[n].time and (not ch or cl[n].time > ch.time) then
+ ch = cl[n]
+ end
+ end
+ return ch
+end
+
function render_elements(master_ass)
+ -- when the slider is dragged or hovered and we have a target chapter name
+ -- then we use it instead of the normal title. we calculate it before the
+ -- render iterations because the title may be rendered before the slider.
+ state.forced_title = nil
+ local se, ae = state.slider_element, elements[state.active_element]
+ if se and (ae == se or (not ae and mouse_hit(se))) then
+ local dur = mp.get_property_number("duration", 0)
+ if dur > 0 then
+ local possec = get_slider_value(se) * dur / 100 -- of mouse pos
+ local ch = get_chapter(possec)
+ if ch and ch.title and ch.title ~= "" then
+ state.forced_title = "Chapter: " .. ch.title
+ end
+ end
+ end
+
for n=1, #elements do
local element = elements[n]
@@ -1687,6 +1725,7 @@ function update_options(list)
validate_user_opts()
request_tick()
visibility_mode(user_opts.visibility, true)
+ update_duration_watch()
request_init()
end
@@ -1738,7 +1777,8 @@ function osc_init()
ne = new_element("title", "button")
ne.content = function ()
- local title = mp.command_native({"expand-text", user_opts.title})
+ local title = state.forced_title or
+ mp.command_native({"expand-text", user_opts.title})
-- escape ASS, and strip newlines and trailing slashes
title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{")
return not (title == "") and title or "mpv"
@@ -1915,6 +1955,7 @@ function osc_init()
ne = new_element("seekbar", "slider")
ne.enabled = not (mp.get_property("percent-pos") == nil)
+ state.slider_element = ne.enabled and ne or nil -- used for forced_title
ne.slider.markerF = function ()
local duration = mp.get_property_number("duration", nil)
if not (duration == nil) then
@@ -2099,7 +2140,10 @@ function update_margins()
local margins = osc_param.video_margins
-- Don't use margins if it's visible only temporarily.
- if (not state.osc_visible) or (get_hidetimeout() >= 0) then
+ if (not state.osc_visible) or (get_hidetimeout() >= 0) or
+ (state.fullscreen and not user_opts.showfullscreen) or
+ (not state.fullscreen and not user_opts.showwindowed)
+ then
margins = {l = 0, r = 0, t = 0, b = 0}
end
@@ -2236,6 +2280,7 @@ end
function render_wipe()
msg.trace("render_wipe()")
+ state.osd.data = "" -- allows set_osd to immediately update on enable
state.osd:remove()
end
@@ -2256,7 +2301,14 @@ function render()
end
-- init management
- if state.initREQ then
+ if state.active_element then
+ -- mouse is held down on some element - keep ticking and igore initReq
+ -- till it's released, or else the mouse-up (click) will misbehave or
+ -- get ignored. that's because osc_init() recreates the osc elements,
+ -- but mouse handling depends on the elements staying unmodified
+ -- between mouse-down and mouse-up (using the index active_element).
+ request_tick()
+ elseif state.initREQ then
osc_init()
state.initREQ = false
@@ -2293,14 +2345,10 @@ function render()
if (state.anitype == "out") then
osc_visible(false)
end
- state.anistart = nil
- state.animation = nil
- state.anitype = nil
+ kill_animation()
end
else
- state.anistart = nil
- state.animation = nil
- state.anitype = nil
+ kill_animation()
end
--mouse show/hide area
@@ -2472,8 +2520,10 @@ function process_event(source, what)
if element_has_action(elements[n], action) then
elements[n].eventresponder[action](elements[n])
end
- request_tick()
end
+
+ -- ensure rendering after any (mouse) event - icons could change etc
+ request_tick()
end
@@ -2504,6 +2554,11 @@ local santa_hat_lines = {
-- called by mpv on every frame
function tick()
+ if state.marginsREQ == true then
+ update_margins()
+ state.marginsREQ = false
+ end
+
if (not state.enabled) then return end
if (state.idle) then
@@ -2548,13 +2603,23 @@ function tick()
render()
else
-- Flush OSD
- set_osd(osc_param.playresy, osc_param.playresy, "")
+ render_wipe()
end
state.tick_last_time = mp.get_time()
if state.anitype ~= nil then
- request_tick()
+ -- state.anistart can be nil - animation should now start, or it can
+ -- be a timestamp when it started. state.idle has no animation.
+ if not state.idle and
+ (not state.anistart or
+ mp.get_time() < 1 + state.anistart + user_opts.fadeduration/1000)
+ then
+ -- animating or starting, or still within 1s past the deadline
+ request_tick()
+ else
+ kill_animation()
+ end
end
end
@@ -2582,12 +2647,39 @@ function enable_osc(enable)
end
end
+-- duration is observed for the sole purpose of updating chapter markers
+-- positions. live streams with chapters are very rare, and the update is also
+-- expensive (with request_init), so it's only observed when we have chapters
+-- and the user didn't disable the livemarkers option (update_duration_watch).
+function on_duration() request_init() end
+
+local duration_watched = false
+function update_duration_watch()
+ local want_watch = user_opts.livemarkers and
+ (mp.get_property_number("chapters", 0) or 0) > 0 and
+ true or false -- ensure it's a boolean
+
+ if (want_watch ~= duration_watched) then
+ if want_watch then
+ mp.observe_property("duration", nil, on_duration)
+ else
+ mp.unobserve_property(on_duration)
+ end
+ duration_watched = want_watch
+ end
+end
+
validate_user_opts()
+update_duration_watch()
mp.register_event("shutdown", shutdown)
mp.register_event("start-file", request_init)
mp.observe_property("track-list", nil, request_init)
mp.observe_property("playlist", nil, request_init)
+mp.observe_property("chapter-list", nil, function()
+ update_duration_watch()
+ request_init()
+end)
mp.register_script_message("osc-message", show_message)
mp.register_script_message("osc-chapterlist", function(dur)
@@ -2607,6 +2699,7 @@ end)
mp.observe_property("fullscreen", "bool",
function(name, val)
state.fullscreen = val
+ state.marginsREQ = true
request_init_resize()
end
)
@@ -2722,6 +2815,7 @@ function visibility_mode(mode, no_osd)
end
user_opts.visibility = mode
+ utils.shared_script_property_set("osc-visibility", mode)
if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then
mp.osd_message("OSC visibility: " .. mode)
diff --git a/player/lua/stats.lua b/player/lua/stats.lua
index 6054480..8e42351 100644
--- a/player/lua/stats.lua
+++ b/player/lua/stats.lua
@@ -13,12 +13,11 @@ local utils = require 'mp.utils'
-- Options
local o = {
-- Default key bindings
- key_oneshot = "i",
- key_toggle = "I",
key_page_1 = "1",
key_page_2 = "2",
key_page_3 = "3",
key_page_4 = "4",
+ key_page_0 = "0",
-- For pages which support scrolling
key_scroll_up = "UP",
key_scroll_down = "DOWN",
@@ -77,6 +76,8 @@ local o = {
no_ass_b0 = "\027[0m",
no_ass_it1 = "\027[3m",
no_ass_it0 = "\027[0m",
+
+ bindlist = "no", -- print page 4 to the terminal on startup and quit mpv
}
options.read_options(o)
@@ -131,17 +132,27 @@ local function compat(p)
return p
end
-
-local function set_ASS(b)
- if not o.use_ass or o.persistent_overlay then
- return ""
- end
- return b and ass_start or ass_stop
-end
-
+-- "\\<U+2060>" in UTF-8 (U+2060 is WORD-JOINER)
+local ESC_BACKSLASH = "\\" .. string.char(0xE2, 0x81, 0xA0)
local function no_ASS(t)
- return set_ASS(false) .. t .. set_ASS(true)
+ if not o.use_ass then
+ return t
+ elseif not o.persistent_overlay then
+ -- mp.osd_message supports ass-escape using osd-ass-cc/{0|1}
+ return ass_stop .. t .. ass_start
+ else
+ -- mp.set_osd_ass doesn't support ass-escape. roll our own.
+ -- similar to mpv's sub/osd_libass.c:mangle_ass(...), excluding
+ -- space after newlines because no_ASS is not used with multi-line.
+ -- space at the begining is replaced with "\\h" because it matters
+ -- at the begining of a line, and we can't know where our output
+ -- ends up. no issue if it ends up at the middle of a line.
+ return tostring(t)
+ :gsub("\\", ESC_BACKSLASH)
+ :gsub("{", "\\{")
+ :gsub("^ ", "\\h")
+ end
end
@@ -160,11 +171,11 @@ local function text_style()
return ""
end
if o.custom_header and o.custom_header ~= "" then
- return set_ASS(true) .. o.custom_header
+ return o.custom_header
else
- return format("%s{\\r}{\\an7}{\\fs%d}{\\fn%s}{\\bord%f}{\\3c&H%s&}" ..
+ return format("{\\r}{\\an7}{\\fs%d}{\\fn%s}{\\bord%f}{\\3c&H%s&}" ..
"{\\1c&H%s&}{\\alpha&H%s&}{\\xshad%f}{\\yshad%f}{\\4c&H%s&}",
- set_ASS(true), o.font_size, o.font, o.border_size,
+ o.font_size, o.font, o.border_size,
o.border_color, o.font_color, o.alpha, o.shadow_x_offset,
o.shadow_y_offset, o.shadow_color)
end
@@ -351,6 +362,146 @@ local function append_perfdata(s, dedicated_page)
end
end
+local function ellipsis(s, maxlen)
+ if not maxlen or s:len() <= maxlen then return s end
+ return s:sub(1, maxlen - 3) .. "..."
+end
+
+-- command prefix tokens to strip - includes generic property commands
+local cmd_prefixes = {
+ osd_auto=1, no_osd=1, osd_bar=1, osd_msg=1, osd_msg_bar=1, raw=1, sync=1,
+ async=1, expand_properties=1, repeatable=1, set=1, add=1, multiply=1,
+ toggle=1, cycle=1, cycle_values=1, ["!reverse"]=1, change_list=1,
+}
+-- commands/writable-properties prefix sub-words (followed by -) to strip
+local name_prefixes = {
+ define=1, delete=1, enable=1, disable=1, dump=1, write=1, drop=1, revert=1,
+ ab=1, hr=1, secondary=1, current=1,
+}
+-- extract a command "subject" from a command string, by removing all
+-- generic prefix tokens and then returning the first interesting sub-word
+-- of the next token. For target-script name we also check another token.
+-- The tokenizer works fine for things we care about - valid mpv commands,
+-- properties and script names, possibly quoted, white-space[s]-separated.
+-- It's decent in practice, and worst case is "incorrect" subject.
+local function cmd_subject(cmd)
+ cmd = cmd:gsub(";.*", ""):gsub("%-", "_") -- only first cmd, s/-/_/
+ local TOKEN = '^%s*["\']?([%w_!]*)' -- captures+ends before (maybe) final "
+ local tok, sname, subw
+
+ repeat tok, cmd = cmd:match(TOKEN .. '["\']?(.*)')
+ until not cmd_prefixes[tok]
+ -- tok is the 1st non-generic command/property name token, cmd is the rest
+
+ sname = tok == "script_message_to" and cmd:match(TOKEN)
+ or tok == "script_binding" and cmd:match(TOKEN .. "/")
+ if sname and sname ~= "" then
+ return "script: " .. sname
+ end
+
+ -- return the first sub-word of tok which is not a useless prefix
+ repeat subw, tok = tok:match("([^_]*)_?(.*)")
+ until tok == "" or not name_prefixes[subw]
+ return subw:len() > 1 and subw or "[unknown]"
+end
+
+-- key names are valid UTF-8, ascii7 except maybe the last/only codepoint.
+-- we count codepoints and ignore wcwidth. no need for grapheme clusters.
+-- our error for alignment is at most one cell (if last CP is double-width).
+-- (if k was valid but arbitrary: we'd count all bytes <0x80 or >=0xc0)
+local function keyname_cells(k)
+ local klen = k:len()
+ if klen > 1 and k:byte(klen) >= 0x80 then -- last/only CP is not ascii7
+ repeat klen = klen-1
+ until klen == 1 or k:byte(klen) >= 0xc0 -- last CP begins at klen
+ end
+ return klen
+end
+
+local function get_kbinfo_lines(width)
+ -- active keys: only highest priotity of each key, and not our (stats) keys
+ local bindings = mp.get_property_native("input-bindings", {})
+ local active = {} -- map: key-name -> bind-info
+ for _, bind in pairs(bindings) do
+ if bind.priority >= 0 and (
+ not active[bind.key] or
+ (active[bind.key].is_weak and not bind.is_weak) or
+ (bind.is_weak == active[bind.key].is_weak and
+ bind.priority > active[bind.key].priority)
+ ) and not bind.cmd:find("script-binding stats/__forced_", 1, true)
+ then
+ active[bind.key] = bind
+ end
+ end
+
+ -- make an array, find max key len, add sort keys (.subject/.mods[_count])
+ local ordered = {}
+ local kspaces = "" -- as many spaces as the longest key name
+ for _, bind in pairs(active) do
+ bind.subject = cmd_subject(bind.cmd)
+ if bind.subject ~= "ignore" then
+ ordered[#ordered+1] = bind
+ _,_, bind.mods = bind.key:find("(.*)%+.")
+ _, bind.mods_count = bind.key:gsub("%+.", "")
+ if bind.key:len() > kspaces:len() then
+ kspaces = string.rep(" ", bind.key:len())
+ end
+ end
+ end
+
+ local function align_right(key)
+ return kspaces:sub(keyname_cells(key)) .. key
+ end
+
+ -- sort by: subject, mod(ifier)s count, mods, key-len, lowercase-key, key
+ table.sort(ordered, function(a, b)
+ if a.subject ~= b.subject then
+ return a.subject < b.subject
+ elseif a.mods_count ~= b.mods_count then
+ return a.mods_count < b.mods_count
+ elseif a.mods ~= b.mods then
+ return a.mods < b.mods
+ elseif a.key:len() ~= b.key:len() then
+ return a.key:len() < b.key:len()
+ elseif a.key:lower() ~= b.key:lower() then
+ return a.key:lower() < b.key:lower()
+ else
+ return a.key > b.key -- only case differs, lowercase first
+ end
+ end)
+
+ -- key/subject pre/post formatting for terminal/ass.
+ -- key/subject alignment uses spaces (with mono font if ass)
+ -- word-wrapping is disabled for ass, or cut at 79 for the terminal
+ local LTR = string.char(0xE2, 0x80, 0x8E) -- U+200E Left To Right mark
+ local term = not o.use_ass
+ local kpre = term and "" or format("{\\q2\\fn%s}%s", o.font_mono, LTR)
+ local kpost = term and " " or format(" {\\fn%s}", o.font)
+ local spre = term and kspaces .. " "
+ or format("{\\q2\\fn%s}%s {\\fn%s}{\\fs%d\\u1}",
+ o.font_mono, kspaces, o.font, 1.3*o.font_size)
+ local spost = term and "" or format("{\\u0\\fs%d}", o.font_size)
+ local _, itabs = o.indent:gsub("\t", "")
+ local cutoff = term and (width or 79) - o.indent:len() - itabs * 7 - spre:len()
+
+ -- create the display lines
+ local info_lines = {}
+ local subject = nil
+ for _, bind in ipairs(ordered) do
+ if bind.subject ~= subject then -- new subject (title)
+ subject = bind.subject
+ append(info_lines, "", {})
+ append(info_lines, "", { prefix = spre .. subject .. spost })
+ end
+ if bind.comment then
+ bind.cmd = bind.cmd .. " # " .. bind.comment
+ end
+ append(info_lines, ellipsis(bind.cmd, cutoff),
+ { prefix = kpre .. no_ASS(align_right(bind.key)) .. kpost })
+ end
+ return info_lines
+end
+
local function append_general_perfdata(s, offset)
local perf_info = mp.get_property_native("perf-info") or {}
local count = 0
@@ -426,6 +577,10 @@ local function append_filters(s, prop, prefix)
n = n .. " (disabled)"
end
+ if f.label ~= nil then
+ n = "@" .. f.label .. ": " .. n
+ end
+
local p = {}
for key,value in pairs(f.params) do
p[#p+1] = key .. "=" .. value
@@ -505,7 +660,7 @@ end
local function add_video(s)
local r = mp.get_property_native("video-params")
- -- in case of e.g. lavi-complex there can be no input video, only output
+ -- in case of e.g. lavfi-complex there can be no input video, only output
if not r then
r = mp.get_property_native("video-out-params")
end
@@ -513,6 +668,10 @@ local function add_video(s)
return
end
+ local osd_dims = mp.get_property_native("osd-dimensions")
+ local scaled_width = osd_dims["w"] - osd_dims["ml"] - osd_dims["mr"]
+ local scaled_height = osd_dims["h"] - osd_dims["mt"] - osd_dims["mb"]
+
append(s, "", {prefix=o.nl .. o.nl .. "Video:", nl="", indent=""})
if append_property(s, "video-codec", {prefix_sep="", nl="", indent=""}) then
append_property(s, "hwdec-current", {prefix="(hwdec:", nl="", indent=" ",
@@ -544,6 +703,9 @@ local function add_video(s)
if append(s, r["w"], {prefix="Native Resolution:"}) then
append(s, r["h"], {prefix="x", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true})
end
+ if append(s, scaled_width, {prefix="Scaled Resolution:"}) then
+ append(s, scaled_height, {prefix="x", nl="", indent=" ", prefix_sep=" ", no_prefix_markup=true})
+ end
append_property(s, "current-window-scale", {prefix="Window Scale:"})
if r["aspect"] ~= nil then
append(s, format("%.2f", r["aspect"]), {prefix="Aspect Ratio:"})
@@ -574,7 +736,7 @@ end
local function add_audio(s)
local r = mp.get_property_native("audio-params")
- -- in case of e.g. lavi-complex there can be no input audio, only output
+ -- in case of e.g. lavfi-complex there can be no input audio, only output
if not r then
r = mp.get_property_native("audio-out-params")
end
@@ -654,11 +816,34 @@ local function vo_stats()
return table.concat(stats)
end
+local kbinfo_lines = nil
+local function keybinding_info(after_scroll)
+ local header = {}
+ local page = pages[o.key_page_4]
+ eval_ass_formatting()
+ add_header(header)
+ append(header, "", {prefix=o.nl .. page.desc .. ":", nl="", indent=""})
+
+ if not kbinfo_lines or not after_scroll then
+ kbinfo_lines = get_kbinfo_lines()
+ end
+ -- up to 20 lines for the terminal - so that mpv can also print
+ -- the status line without scrolling, and up to 40 lines for libass
+ -- because it can put a big performance toll on libass to process
+ -- many lines which end up outside (below) the screen.
+ local term = not o.use_ass
+ local nlines = #kbinfo_lines
+ page.offset = max(1, min((page.offset or 1), term and nlines - 20 or nlines))
+ local maxline = min(nlines, page.offset + (term and 20 or 40))
+ return table.concat(header) ..
+ table.concat(kbinfo_lines, "", page.offset, maxline)
+end
+
local function perf_stats()
local stats = {}
eval_ass_formatting()
add_header(stats)
- local page = pages[o.key_page_4]
+ local page = pages[o.key_page_0]
append(stats, "", {prefix=o.nl .. o.nl .. page.desc .. ":", nl="", indent=""})
page.offset = append_general_perfdata(stats, page.offset)
return table.concat(stats)
@@ -789,7 +974,8 @@ pages = {
[o.key_page_1] = { f = default_stats, desc = "Default" },
[o.key_page_2] = { f = vo_stats, desc = "Extended Frame Timings", scroll = true },
[o.key_page_3] = { f = cache_stats, desc = "Cache Statistics" },
- [o.key_page_4] = { f = perf_stats, desc = "Internal performance info", scroll = true },
+ [o.key_page_4] = { f = keybinding_info, desc = "Active key bindings", scroll = true },
+ [o.key_page_0] = { f = perf_stats, desc = "Internal performance info", scroll = true },
}
@@ -827,11 +1013,15 @@ local function record_data(skip)
end
-- Call the function for `page` and print it to OSD
-local function print_page(page)
+local function print_page(page, after_scroll)
+ -- the page functions assume we start in ass-enabled mode.
+ -- that's true for mp.set_osd_ass, but not for mp.osd_message.
+ local ass_content = pages[page].f(after_scroll)
if o.persistent_overlay then
- mp.set_osd_ass(0, 0, pages[page].f())
+ mp.set_osd_ass(0, 0, ass_content)
else
- mp.osd_message(pages[page].f(), display_timer.oneshot and o.duration or o.redraw_delay + 1)
+ mp.osd_message((o.use_ass and ass_start or "") .. ass_content,
+ display_timer.oneshot and o.duration or o.redraw_delay + 1)
end
end
@@ -843,7 +1033,7 @@ end
local function scroll_delta(d)
if display_timer.oneshot then display_timer:kill() ; display_timer:resume() end
pages[curr_page].offset = (pages[curr_page].offset or 1) + d
- print_page(curr_page)
+ print_page(curr_page, true)
end
local function scroll_up() scroll_delta(-o.scroll_lines) end
local function scroll_down() scroll_delta(o.scroll_lines) end
@@ -855,15 +1045,15 @@ local function reset_scroll_offsets()
end
local function bind_scroll()
if not scroll_bound then
- mp.add_forced_key_binding(o.key_scroll_up, o.key_scroll_up, scroll_up, {repeatable=true})
- mp.add_forced_key_binding(o.key_scroll_down, o.key_scroll_down, scroll_down, {repeatable=true})
+ mp.add_forced_key_binding(o.key_scroll_up, "__forced_"..o.key_scroll_up, scroll_up, {repeatable=true})
+ mp.add_forced_key_binding(o.key_scroll_down, "__forced_"..o.key_scroll_down, scroll_down, {repeatable=true})
scroll_bound = true
end
end
local function unbind_scroll()
if scroll_bound then
- mp.remove_key_binding(o.key_scroll_up)
- mp.remove_key_binding(o.key_scroll_down)
+ mp.remove_key_binding("__forced_"..o.key_scroll_up)
+ mp.remove_key_binding("__forced_"..o.key_scroll_down)
scroll_bound = false
end
end
@@ -887,7 +1077,7 @@ local function add_page_bindings()
end
end
for k, _ in pairs(pages) do
- mp.add_forced_key_binding(k, k, a(k), {repeatable=true})
+ mp.add_forced_key_binding(k, "__forced_"..k, a(k), {repeatable=true})
end
update_scroll_bindings(curr_page)
end
@@ -896,7 +1086,7 @@ end
-- Remove keybindings for every page
local function remove_page_bindings()
for k, _ in pairs(pages) do
- mp.remove_key_binding(k)
+ mp.remove_key_binding("__forced_"..k)
end
unbind_scroll()
end
@@ -960,11 +1150,11 @@ display_timer = mp.add_periodic_timer(o.duration,
display_timer:kill()
-- Single invocation key binding
-mp.add_key_binding(o.key_oneshot, "display-stats", function() process_key_binding(true) end,
+mp.add_key_binding(nil, "display-stats", function() process_key_binding(true) end,
{repeatable=true})
-- Toggling key binding
-mp.add_key_binding(o.key_toggle, "display-stats-toggle", function() process_key_binding(false) end,
+mp.add_key_binding(nil, "display-stats-toggle", function() process_key_binding(false) end,
{repeatable=false})
-- Single invocation bindings without key, can be used in input.conf to create
@@ -984,3 +1174,22 @@ mp.register_event("video-reconfig",
print_page(curr_page)
end
end)
+
+-- --script-opts=stats-bindlist=[-]{yes|<TERM-WIDTH>}
+if o.bindlist ~= "no" then
+ mp.command("no-osd set really-quiet yes")
+ if o.bindlist:sub(1, 1) == "-" then
+ o.bindlist = o.bindlist:sub(2)
+ o.no_ass_b0 = ""
+ o.no_ass_b1 = ""
+ end
+ local width = max(40, math.floor(tonumber(o.bindlist) or 79))
+ mp.add_timeout(0, function() -- wait for all other scripts to finish init
+ o.ass_formatting = false
+ o.no_ass_indent = " "
+ eval_ass_formatting()
+ io.write(pages[o.key_page_4].desc .. ":" ..
+ table.concat(get_kbinfo_lines(width)) .. "\n")
+ mp.command("quit")
+ end)
+end
diff --git a/player/lua/ytdl_hook.lua b/player/lua/ytdl_hook.lua
index e9a7b3a..27d39af 100644
--- a/player/lua/ytdl_hook.lua
+++ b/player/lua/ytdl_hook.lua
@@ -8,11 +8,12 @@ local o = {
use_manifests = false,
all_formats = false,
force_all_formats = true,
- ytdl_path = "youtube-dl",
+ ytdl_path = "",
}
local ytdl = {
- path = nil,
+ path = "",
+ paths_to_search = {"yt-dlp", "yt-dlp_x86", "youtube-dl"},
searched = false,
blacklisted = {}
}
@@ -38,6 +39,16 @@ function iif(cond, if_true, if_false)
return if_false
end
+-- youtube-dl JSON name to mpv tag name
+local tag_list = {
+ ["uploader"] = "uploader",
+ ["channel_url"] = "channel_url",
+ -- these titles tend to be a bit too long, so hide them on the terminal
+ -- (default --display-tags does not include this name)
+ ["description"] = "ytdl_description",
+ -- "title" is handled by force-media-title
+}
+
local safe_protos = Set {
"http", "https", "ftp", "ftps",
"rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte",
@@ -78,7 +89,13 @@ local function map_codec_to_mpv(codec)
return nil
end
+local function platform_is_windows()
+ return package.config:sub(1,1) == "\\"
+end
+
local function exec(args)
+ msg.debug("Running: " .. table.concat(args, " "))
+
local ret = mp.command_native({name = "subprocess",
args = args,
capture_stdout = true,
@@ -341,6 +358,20 @@ local function as_integer(v, def)
return def
end
+local function tags_to_edl(json)
+ local tags = {}
+ for json_name, mp_name in pairs(tag_list) do
+ local v = json[json_name]
+ if v then
+ tags[#tags + 1] = mp_name .. "=" .. edl_escape(tostring(v))
+ end
+ end
+ if #tags == 0 then
+ return nil
+ end
+ return "!global_tags," .. table.concat(tags, ",")
+end
+
-- Convert a format list from youtube-dl to an EDL URL, or plain URL.
-- json: full json blob by youtube-dl
-- formats: format list by youtube-dl
@@ -475,6 +506,11 @@ local function formats_to_edl(json, formats, use_all_formats)
if #streams == 1 and single_url then
res.url = single_url
elseif #streams > 0 then
+ local tags = tags_to_edl(json)
+ if tags then
+ -- not a stream; just for the sake of concatenating the EDL string
+ streams[#streams + 1] = tags
+ end
res.url = "edl://" .. table.concat(streams, ";")
else
return nil
@@ -507,7 +543,7 @@ local function add_single_video(json)
if requested_formats then
for _, track in pairs(requested_formats) do
- max_bitrate = track.tbr > max_bitrate and
+ max_bitrate = (track.tbr and track.tbr > max_bitrate) and
track.tbr or max_bitrate
end
elseif json.tbr then
@@ -689,20 +725,6 @@ end
function run_ytdl_hook(url)
local start_time = os.clock()
- -- check for youtube-dl in mpv's config dir
- if not (ytdl.searched) then
- local exesuf = (package.config:sub(1,1) == '\\') and '.exe' or ''
- local ytdl_mcd = mp.find_config_file(o.ytdl_path .. exesuf)
- if ytdl_mcd == nil then
- msg.verbose("No youtube-dl found with path "..o.ytdl_path..exesuf.." in config directories")
- ytdl.path = o.ytdl_path
- else
- msg.verbose("found youtube-dl at: " .. ytdl_mcd)
- ytdl.path = ytdl_mcd
- end
- ytdl.searched = true
- end
-
-- strip ytdl://
if (url:find("ytdl://") == 1) then
url = url:sub(8)
@@ -757,40 +779,79 @@ function run_ytdl_hook(url)
end
table.insert(command, "--")
table.insert(command, url)
- msg.debug("Running: " .. table.concat(command,' '))
- local es, json, result, aborted = exec(command)
+
+ local es, json, result, aborted
+ if ytdl.searched then
+ es, json, result, aborted = exec(command)
+ else
+ local separator = platform_is_windows() and ";" or ":"
+ if o.ytdl_path:match("[^" .. separator .. "]") then
+ ytdl.paths_to_search = {}
+ for path in o.ytdl_path:gmatch("[^" .. separator .. "]+") do
+ table.insert(ytdl.paths_to_search, path)
+ end
+ end
+
+ for _, path in pairs(ytdl.paths_to_search) do
+ -- search for youtube-dl in mpv's config dir
+ local exesuf = platform_is_windows() and ".exe" or ""
+ local ytdl_cmd = mp.find_config_file(path .. exesuf)
+ if ytdl_cmd then
+ msg.verbose("Found youtube-dl at: " .. ytdl_cmd)
+ ytdl.path = ytdl_cmd
+ command[1] = ytdl.path
+ es, json, result, aborted = exec(command)
+ break
+ else
+ msg.verbose("No youtube-dl found with path " .. path .. exesuf .. " in config directories")
+ command[1] = path
+ es, json, result, aborted = exec(command)
+ if result.error_string == "init" then
+ msg.verbose("youtube-dl with path " .. path .. exesuf .. " not found in PATH or not enough permissions")
+ else
+ msg.verbose("Found youtube-dl with path " .. path .. exesuf .. " in PATH")
+ ytdl.path = path
+ break
+ end
+ end
+ end
+
+ ytdl.searched = true
+ end
if aborted then
return
end
- if (es < 0) or (json == nil) or (json == "") then
+ local parse_err = nil
+
+ if (es < 0) or (json == "") then
+ json = nil
+ elseif json then
+ json, parse_err = utils.parse_json(json)
+ end
+
+ if (json == nil) then
-- trim our stderr to avoid spurious newlines
ytdl_err = result.stderr:gsub("^%s*(.-)%s*$", "%1")
msg.error(ytdl_err)
local err = "youtube-dl failed: "
if result.error_string and result.error_string == "init" then
err = err .. "not found or not enough permissions"
+ elseif parse_err then
+ err = err .. "failed to parse JSON data: " .. parse_err
elseif not result.killed_by_us then
err = err .. "unexpected error occurred"
else
err = string.format("%s returned '%d'", err, es)
end
msg.error(err)
- if string.find(ytdl_err, "yt%-dl%.org/bug") then
+ if parse_err or string.find(ytdl_err, "yt%-dl%.org/bug") then
check_version(ytdl.path)
end
return
end
- local json, err = utils.parse_json(json)
-
- if (json == nil) then
- msg.error("failed to parse JSON data: " .. err)
- check_version(ytdl.path)
- return
- end
-
msg.verbose("youtube-dl succeeded!")
msg.debug('ytdl parsing took '..os.clock()-start_time..' seconds')
diff --git a/player/main.c b/player/main.c
index 71f5d14..e039f61 100644
--- a/player/main.c
+++ b/player/main.c
@@ -54,7 +54,6 @@
#include "input/input.h"
#include "audio/out/ao.h"
-#include "demux/demux.h"
#include "misc/thread_tools.h"
#include "sub/osd.h"
#include "test/tests.h"
@@ -202,13 +201,6 @@ static bool handle_help_options(struct MPContext *mpctx)
{
struct MPOpts *opts = mpctx->opts;
struct mp_log *log = mpctx->log;
- if ((opts->demuxer_name && strcmp(opts->demuxer_name, "help") == 0) ||
- (opts->audio_demuxer_name && strcmp(opts->audio_demuxer_name, "help") == 0) ||
- (opts->sub_demuxer_name && strcmp(opts->sub_demuxer_name, "help") == 0)) {
- demuxer_help(log);
- MP_INFO(mpctx, "\n");
- return true;
- }
if (opts->ao_opts->audio_device &&
strcmp(opts->ao_opts->audio_device, "help") == 0)
{
@@ -361,7 +353,10 @@ int mp_initialize(struct MPContext *mpctx, char **options)
m_config_set_profile(mpctx->mconfig, "pseudo-gui", 0);
}
- mp_get_resume_defaults(mpctx);
+ // Backup the default settings, which should not be stored in the resume
+ // config files. This explicitly includes values set by config files and
+ // the command line.
+ m_config_backup_watch_later_opts(mpctx->mconfig);
mp_input_load_config(mpctx->input);
diff --git a/player/playloop.c b/player/playloop.c
index 87c8b63..c0658ac 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -239,6 +239,10 @@ void reset_playback_state(struct MPContext *mpctx)
sub_set_play_dir(t->d_sub, mpctx->play_dir);
}
+ // May need unpause first
+ if (mpctx->paused_for_cache)
+ update_internal_pause_state(mpctx);
+
mpctx->hrseek_active = false;
mpctx->hrseek_lastframe = false;
mpctx->hrseek_backstep = false;
diff --git a/player/scripting.c b/player/scripting.c
index 24e2931..3db8797 100644
--- a/player/scripting.c
+++ b/player/scripting.c
@@ -174,9 +174,10 @@ static int64_t mp_load_script(struct MPContext *mpctx, const char *fname)
};
talloc_free(tmp);
+ fname = NULL; // might have been freed so don't touch anymore
if (!arg->client) {
- MP_ERR(mpctx, "Failed to create client for script: %s\n", fname);
+ MP_ERR(mpctx, "Failed to create client for script: %s\n", arg->filename);
talloc_free(arg);
return -1;
}
@@ -185,7 +186,7 @@ static int64_t mp_load_script(struct MPContext *mpctx, const char *fname)
arg->log = mp_client_get_log(arg->client);
int64_t id = mpv_client_id(arg->client);
- MP_DBG(arg, "Loading %s %s...\n", backend->name, fname);
+ MP_DBG(arg, "Loading %s %s...\n", backend->name, arg->filename);
if (backend->no_thread) {
run_script(arg);
diff --git a/player/sub.c b/player/sub.c
index ae2a85a..50f3fbe 100644
--- a/player/sub.c
+++ b/player/sub.c
@@ -114,7 +114,10 @@ static bool update_subtitle(struct MPContext *mpctx, double video_pts,
// Handle displaying subtitles on VO with no video being played. This is
// quite different, because normally subtitles are redrawn on new video
// frames, using the video frames' timestamps.
- if (mpctx->video_out && mpctx->video_status == STATUS_EOF) {
+ if (mpctx->video_out && mpctx->video_status == STATUS_EOF &&
+ (mpctx->opts->subs_rend->sub_past_video_end ||
+ !mpctx->current_track[0][STREAM_VIDEO] ||
+ mpctx->current_track[0][STREAM_VIDEO]->attached_picture)) {
if (osd_get_force_video_pts(mpctx->osd) != video_pts) {
osd_set_force_video_pts(mpctx->osd, video_pts);
osd_query_and_reset_want_redraw(mpctx->osd);
@@ -168,7 +171,8 @@ static bool init_subdec(struct MPContext *mpctx, struct track *track)
return false;
track->d_sub = sub_create(mpctx->global, track->stream,
- get_all_attachments(mpctx));
+ get_all_attachments(mpctx),
+ get_order(mpctx, track));
if (!track->d_sub)
return false;
@@ -196,7 +200,7 @@ void reinit_sub(struct MPContext *mpctx, struct track *track)
sub_select(track->d_sub, true);
int order = get_order(mpctx, track);
osd_set_sub(mpctx->osd, order, track->d_sub);
- sub_control(track->d_sub, SD_CTRL_SET_TOP, &(bool){!!order});
+ sub_control(track->d_sub, SD_CTRL_SET_TOP, &order);
if (mpctx->playback_initialized)
update_subtitles(mpctx, mpctx->playback_pts);
diff --git a/player/video.c b/player/video.c
index c13bdb4..734835d 100644
--- a/player/video.c
+++ b/player/video.c
@@ -288,7 +288,7 @@ void reinit_video_chain_src(struct MPContext *mpctx, struct track *track)
}
reset_video_state(mpctx);
- reset_subtitle_state(mpctx);
+ term_osd_set_subs(mpctx, NULL);
return;
diff --git a/stream/dvbin.h b/stream/dvbin.h
index aca2cab..1c314bd 100644
--- a/stream/dvbin.h
+++ b/stream/dvbin.h
@@ -22,8 +22,6 @@
#include <inttypes.h>
#include <linux/dvb/dmx.h>
#include <linux/dvb/frontend.h>
-#include <linux/dvb/video.h>
-#include <linux/dvb/audio.h>
#include <linux/dvb/version.h>
#define MAX_ADAPTERS 16
diff --git a/stream/stream.h b/stream/stream.h
index 2116fdd..423ba12 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -109,7 +109,7 @@ typedef struct stream_info_st {
// opts is set from ->opts
int (*open)(struct stream *st);
// Alternative to open(). Only either open() or open2() can be set.
- int (*open2)(struct stream *st, struct stream_open_args *args);
+ int (*open2)(struct stream *st, const struct stream_open_args *args);
const char *const *protocols;
bool can_write; // correctly checks for READ/WRITE modes
bool local_fs; // supports STREAM_LOCAL_FS_ONLY
diff --git a/stream/stream_concat.c b/stream/stream_concat.c
index 21f04f9..d06bd4a 100644
--- a/stream/stream_concat.c
+++ b/stream/stream_concat.c
@@ -99,7 +99,7 @@ static int combine_origin(int cur, int new)
return new; // including cur==0
}
-static int open2(struct stream *stream, struct stream_open_args *args)
+static int open2(struct stream *stream, const struct stream_open_args *args)
{
struct priv *p = talloc_zero(stream, struct priv);
stream->priv = p;
diff --git a/stream/stream_file.c b/stream/stream_file.c
index d649ff4..4895a83 100644
--- a/stream/stream_file.c
+++ b/stream/stream_file.c
@@ -176,7 +176,7 @@ static bool check_stream_network(int fd)
{
struct statfs fs;
const char *stypes[] = { "afpfs", "nfs", "smbfs", "webdav", "osxfusefs",
- "fuse", "fusefs.sshfs", NULL };
+ "fuse", "fusefs.sshfs", "macfuse", NULL };
if (fstatfs(fd, &fs) == 0)
for (int i=0; stypes[i]; i++)
if (strcmp(stypes[i], fs.f_fstypename) == 0)
@@ -246,7 +246,7 @@ static bool check_stream_network(int fd)
}
#endif
-static int open_f(stream_t *stream, struct stream_open_args *args)
+static int open_f(stream_t *stream, const struct stream_open_args *args)
{
struct priv *p = talloc_ptrtype(stream, p);
*p = (struct priv) {
@@ -340,8 +340,15 @@ static int open_f(stream_t *stream, struct stream_open_args *args)
stream->get_size = get_size;
stream->close = s_close;
- if (check_stream_network(p->fd))
+ if (check_stream_network(p->fd)) {
stream->streaming = true;
+#if HAVE_COCOA
+ if (fcntl(p->fd, F_RDAHEAD, 0) < 0) {
+ MP_VERBOSE(stream, "Cannot disable read ahead on file '%s': %s\n",
+ filename, mp_strerror(errno));
+ }
+#endif
+ }
p->orig_size = get_size(stream);
diff --git a/stream/stream_lavf.c b/stream/stream_lavf.c
index e95dbc0..2269bfb 100644
--- a/stream/stream_lavf.c
+++ b/stream/stream_lavf.c
@@ -73,7 +73,8 @@ const struct m_sub_options stream_lavf_conf = {
},
};
-static const char *const http_like[];
+static const char *const http_like[] =
+ {"http", "https", "mmsh", "mmshttp", "httproxy", NULL};
static int open_f(stream_t *stream);
static struct mp_tags *read_icy(stream_t *stream);
@@ -270,7 +271,7 @@ static int open_f(stream_t *stream)
for (int i = 0; i < sizeof(prefix) / sizeof(prefix[0]); i++)
if (!strncmp(filename, prefix[i], strlen(prefix[i])))
filename += strlen(prefix[i]);
- if (!strncmp(filename, "rtsp:", 5)) {
+ if (!strncmp(filename, "rtsp:", 5) || !strncmp(filename, "rtsps:", 6)) {
/* This is handled as a special demuxer, without a separate
* stream layer. demux_lavf will do all the real work. Note
* that libavformat doesn't even provide a protocol entry for
@@ -404,16 +405,13 @@ done:
return res;
}
-static const char *const http_like[] =
- {"http", "https", "mmsh", "mmshttp", "httproxy", NULL};
-
const stream_info_t stream_info_ffmpeg = {
.name = "ffmpeg",
.open = open_f,
.protocols = (const char *const[]){
- "rtmp", "rtsp", "http", "https", "mms", "mmst", "mmsh", "mmshttp", "rtp",
- "httpproxy", "rtmpe", "rtmps", "rtmpt", "rtmpte", "rtmpts", "srt", "srtp",
- "gopher", "data",
+ "rtmp", "rtsp", "rtsps", "http", "https", "mms", "mmst", "mmsh", "mmshttp",
+ "rtp", "httpproxy", "rtmpe", "rtmps", "rtmpt", "rtmpte", "rtmpts", "srt",
+ "srtp", "gopher", "gophers", "data",
NULL },
.can_write = true,
.stream_origin = STREAM_ORIGIN_NET,
diff --git a/stream/stream_memory.c b/stream/stream_memory.c
index 7383ff4..e4696a7 100644
--- a/stream/stream_memory.c
+++ b/stream/stream_memory.c
@@ -44,7 +44,7 @@ static int64_t get_size(stream_t *s)
return p->data.len;
}
-static int open2(stream_t *stream, struct stream_open_args *args)
+static int open2(stream_t *stream, const struct stream_open_args *args)
{
stream->fill_buffer = fill_buffer;
stream->seek = seek;
diff --git a/stream/stream_mf.c b/stream/stream_mf.c
index 69a6dce..4c871c4 100644
--- a/stream/stream_mf.c
+++ b/stream/stream_mf.c
@@ -39,4 +39,5 @@ const stream_info_t stream_info_mf = {
.name = "mf",
.open = mf_stream_open,
.protocols = (const char*const[]){ "mf", NULL },
+ .stream_origin = STREAM_ORIGIN_FS,
};
diff --git a/stream/stream_slice.c b/stream/stream_slice.c
index d7d0a6b..c0dbeeb 100644
--- a/stream/stream_slice.c
+++ b/stream/stream_slice.c
@@ -136,7 +136,7 @@ static int parse_slice_range(stream_t *stream)
return STREAM_OK;
}
-static int open2(struct stream *stream, struct stream_open_args *args)
+static int open2(struct stream *stream, const struct stream_open_args *args)
{
struct priv *p = talloc_zero(stream, struct priv);
stream->priv = p;
@@ -151,8 +151,9 @@ static int open2(struct stream *stream, struct stream_open_args *args)
return parse_ret;
}
- args->url = stream->path;
- int inner_ret = stream_create_with_args(args, &p->inner);
+ struct stream_open_args args2 = *args;
+ args2.url = stream->path;
+ int inner_ret = stream_create_with_args(&args2, &p->inner);
if (inner_ret != STREAM_OK) {
return inner_ret;
}
diff --git a/sub/dec_sub.c b/sub/dec_sub.c
index ebf3ea3..a283a51 100644
--- a/sub/dec_sub.c
+++ b/sub/dec_sub.c
@@ -57,6 +57,7 @@ struct dec_sub {
struct sh_stream *sh;
int play_dir;
+ int order;
double last_pkt_pts;
bool preload_attempted;
double video_fps;
@@ -161,7 +162,7 @@ static struct sd *init_decoder(struct dec_sub *sub)
// Ownership of attachments goes to the callee, and is released with
// talloc_free() (even on failure).
struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh,
- struct attachment_list *attachments)
+ struct attachment_list *attachments, int order)
{
assert(sh && sh->type == STREAM_SUB);
@@ -174,6 +175,7 @@ struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh,
.codec = sh->codec,
.attachments = talloc_steal(sub, attachments),
.play_dir = 1,
+ .order = order,
.last_pkt_pts = MP_NOPTS_VALUE,
.last_vo_pts = MP_NOPTS_VALUE,
.start = MP_NOPTS_VALUE,
@@ -210,6 +212,7 @@ static void update_segment(struct dec_sub *sub)
talloc_free(sub->sd);
sub->sd = new;
update_subtitle_speed(sub);
+ sub_control(sub, SD_CTRL_SET_TOP, &sub->order);
} else {
// We'll just keep the current decoder, and feed it possibly
// invalid data (not our fault if it crashes or something).
@@ -449,3 +452,8 @@ void sub_set_play_dir(struct dec_sub *sub, int dir)
sub->play_dir = dir;
pthread_mutex_unlock(&sub->lock);
}
+
+bool sub_is_secondary_visible(struct dec_sub *sub)
+{
+ return !!sub->opts->sec_sub_visibility;
+}
diff --git a/sub/dec_sub.h b/sub/dec_sub.h
index f998b59..6257e74 100644
--- a/sub/dec_sub.h
+++ b/sub/dec_sub.h
@@ -10,7 +10,6 @@ struct sh_stream;
struct mpv_global;
struct demux_packet;
struct mp_recorder_sink;
-
struct dec_sub;
struct sd;
@@ -38,7 +37,7 @@ struct attachment_list {
};
struct dec_sub *sub_create(struct mpv_global *global, struct sh_stream *sh,
- struct attachment_list *attachments);
+ struct attachment_list *attachments, int order);
void sub_destroy(struct dec_sub *sub);
bool sub_can_preload(struct dec_sub *sub);
@@ -52,6 +51,7 @@ void sub_reset(struct dec_sub *sub);
void sub_select(struct dec_sub *sub, bool selected);
void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink);
void sub_set_play_dir(struct dec_sub *sub, int dir);
+bool sub_is_secondary_visible(struct dec_sub *sub);
int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg);
diff --git a/sub/filter_jsre.c b/sub/filter_jsre.c
new file mode 100644
index 0000000..af4fbbe
--- /dev/null
+++ b/sub/filter_jsre.c
@@ -0,0 +1,137 @@
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <mujs.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+#include "misc/bstr.h"
+#include "options/options.h"
+#include "sd.h"
+
+
+// p_NAME are protected functions (never throw) which interact with the JS VM.
+// return 0 on successful interaction, not-0 on (caught) js-error.
+// on error: stack is the same as on entry + an error value
+
+// js: global[n] = new RegExp(str, flags)
+static int p_regcomp(js_State *J, int n, const char *str, int flags)
+{
+ if (js_try(J))
+ return 1;
+
+ js_pushnumber(J, n); // n
+ js_newregexp(J, str, flags); // n regex
+ js_setglobal(J, js_tostring(J, -2)); // n (and global[n] is the regex)
+ js_pop(J, 1);
+
+ js_endtry(J);
+ return 0;
+}
+
+// js: found = global[n].test(text)
+static int p_regexec(js_State *J, int n, const char *text, int *found)
+{
+ if (js_try(J))
+ return 1;
+
+ js_pushnumber(J, n); // n
+ js_getglobal(J, js_tostring(J, -1)); // n global[n]
+ js_getproperty(J, -1, "test"); // n global[n] global[n].test
+ js_rot2(J); // n global[n].test global[n] (n, test(), and its `this')
+ js_pushstring(J, text); // n global[n].test global[n] text
+ js_call(J, 1); // n test-result
+ *found = js_toboolean(J, -1);
+ js_pop(J, 2); // the result and n
+
+ js_endtry(J);
+ return 0;
+}
+
+// protected. caller should pop the error after using the result string.
+static const char *get_err(js_State *J)
+{
+ return js_trystring(J, -1, "unknown error");
+}
+
+
+struct priv {
+ js_State *J;
+ int num_regexes;
+ int offset;
+};
+
+static void destruct_priv(void *p)
+{
+ js_freestate(((struct priv *)p)->J);
+}
+
+static bool jsre_init(struct sd_filter *ft)
+{
+ if (strcmp(ft->codec, "ass") != 0)
+ return false;
+
+ if (!ft->opts->rf_enable)
+ return false;
+
+ struct priv *p = talloc_zero(ft, struct priv);
+ ft->priv = p;
+
+ p->J = js_newstate(0, 0, JS_STRICT);
+ if (!p->J) {
+ MP_ERR(ft, "jsre: VM init error\n");
+ return false;
+ }
+ talloc_set_destructor(p, destruct_priv);
+
+ for (int n = 0; ft->opts->jsre_items && ft->opts->jsre_items[n]; n++) {
+ char *item = ft->opts->jsre_items[n];
+
+ int err = p_regcomp(p->J, p->num_regexes, item, JS_REGEXP_I | JS_REGEXP_M);
+ if (err) {
+ MP_ERR(ft, "jsre: %s -- '%s'\n", get_err(p->J), item);
+ js_pop(p->J, 1);
+ continue;
+ }
+
+ p->num_regexes += 1;
+ }
+
+ if (!p->num_regexes)
+ return false;
+
+ p->offset = sd_ass_fmt_offset(ft->event_format);
+ return true;
+}
+
+static struct demux_packet *jsre_filter(struct sd_filter *ft,
+ struct demux_packet *pkt)
+{
+ struct priv *p = ft->priv;
+ char *text = bstrto0(NULL, sd_ass_pkt_text(ft, pkt, p->offset));
+ bool drop = false;
+
+ if (ft->opts->rf_plain)
+ sd_ass_to_plaintext(text, strlen(text), text);
+
+ for (int n = 0; n < p->num_regexes; n++) {
+ int found, err = p_regexec(p->J, n, text, &found);
+ if (err == 0 && found) {
+ int level = ft->opts->rf_warn ? MSGL_WARN : MSGL_V;
+ MP_MSG(ft, level, "jsre: regex %d => drop: '%s'\n", n, text);
+ drop = true;
+ break;
+ } else if (err) {
+ MP_WARN(ft, "jsre: test regex %d: %s.\n", n, get_err(p->J));
+ js_pop(p->J, 1);
+ }
+ }
+
+ talloc_free(text);
+ return drop ? NULL : pkt;
+}
+
+const struct sd_filter_functions sd_filter_jsre = {
+ .init = jsre_init,
+ .filter = jsre_filter,
+};
diff --git a/sub/filter_regex.c b/sub/filter_regex.c
index a5aa03a..8e29991 100644
--- a/sub/filter_regex.c
+++ b/sub/filter_regex.c
@@ -30,7 +30,7 @@ static bool rf_init(struct sd_filter *ft)
MP_TARRAY_GROW(p, p->regexes, p->num_regexes);
regex_t *preg = &p->regexes[p->num_regexes];
- int err = regcomp(preg, item, REG_ICASE | REG_EXTENDED | REG_NOSUB);
+ int err = regcomp(preg, item, REG_ICASE | REG_EXTENDED | REG_NOSUB | REG_NEWLINE);
if (err) {
char errbuf[512];
regerror(err, preg, errbuf, sizeof(errbuf));
@@ -44,15 +44,7 @@ static bool rf_init(struct sd_filter *ft)
if (!p->num_regexes)
return false;
- char *headers = ft->event_format;
- while (headers && headers[0]) {
- p->offset += 1;
- headers = strchr(headers, ',');
- if (headers)
- headers += 1;
- }
- p->offset -= 1; // removes Start/End, adds ReadOrder
-
+ p->offset = sd_ass_fmt_offset(ft->event_format);
return true;
}
@@ -68,19 +60,11 @@ static struct demux_packet *rf_filter(struct sd_filter *ft,
struct demux_packet *pkt)
{
struct priv *p = ft->priv;
- char *line = bstrto0(NULL, (bstr){(char *)pkt->buffer, pkt->len});
+ char *text = bstrto0(NULL, sd_ass_pkt_text(ft, pkt, p->offset));
bool drop = false;
- char *text = line;
- for (int n = 0; n < p->offset - 1; n++) {
- text = strchr(text, ',');
- if (!text) {
- MP_WARN(ft, "Malformed event: '%s'\n", line);
- text = line; // shouldn't happen; random fallback
- break;
- }
- text = text + 1;
- }
+ if (ft->opts->rf_plain)
+ sd_ass_to_plaintext(text, strlen(text), text);
for (int n = 0; n < p->num_regexes; n++) {
int err = regexec(&p->regexes[n], text, 0, NULL, 0);
@@ -94,7 +78,7 @@ static struct demux_packet *rf_filter(struct sd_filter *ft,
}
}
- talloc_free(line);
+ talloc_free(text);
return drop ? NULL : pkt;
}
diff --git a/sub/filter_sdh.c b/sub/filter_sdh.c
index 2b544ea..57193e4 100644
--- a/sub/filter_sdh.c
+++ b/sub/filter_sdh.c
@@ -19,6 +19,7 @@
#include <stdlib.h>
#include <string.h>
#include <limits.h>
+#include <stddef.h>
#include "misc/ctype.h"
#include "common/common.h"
@@ -332,10 +333,9 @@ static void remove_leading_hyphen_space(struct sd_filter *sd, int start_pos,
// Filter ASS formatted string for SDH
//
// Parameters:
-// format format line from ASS configuration
-// n_ignored number of comma to skip as preprocessing have removed them
-// data ASS line. null terminated string if length == 0
-// length length of ASS input if not null terminated, 0 otherwise
+// data ASS line
+// length length of ASS line
+// toff Text offset from data. required: 0 <= toff <= length
//
// Returns a talloc allocated string with filtered ASS data (may be the same
// content as original if no SDH was found) which must be released
@@ -343,50 +343,16 @@ static void remove_leading_hyphen_space(struct sd_filter *sd, int start_pos,
//
// Returns NULL if filtering resulted in all of ASS data being removed so no
// subtitle should be output
-static char *filter_SDH(struct sd_filter *sd, char *format, int n_ignored,
- char *data, int length)
+static char *filter_SDH(struct sd_filter *sd, char *data, int length, ptrdiff_t toff)
{
- if (!format) {
- MP_VERBOSE(sd, "SDH filtering not possible - format missing\n");
- return length ? talloc_strndup(NULL, data, length) : talloc_strdup(NULL, data);
- }
-
- // need null terminated string
- char *ass = length ? talloc_strndup(NULL, data, length) : data;
-
- int comma = 0;
- // scan format line to find the number of the field where the text is
- for (char *c = format; *c; c++) {
- if (*c == ',') {
- comma++;
- if (strncasecmp(c + 1, "Text", 4) == 0)
- break;
- }
- }
- // if preprocessed line some fields are skipped
- comma -= n_ignored;
-
struct buffer writebuf;
struct buffer *buf = &writebuf;
+ init_buf(buf, length + 1); // with room for terminating '\0'
- init_buf(buf, strlen(ass) + 1); // with room for terminating '\0'
-
- char *rp = ass;
-
- // locate text field in ASS line
- for (int k = 0; k < comma; k++) {
- while (*rp) {
- char tmp = append(sd, buf, rp[0]);
- rp++;
- if (tmp == ',')
- break;
- }
- }
- if (!*rp) {
- talloc_free(buf->string);
- MP_VERBOSE(sd, "SDH filtering not possible - cannot find text field\n");
- return length ? ass : talloc_strdup(NULL, ass);
- }
+ // pre-text headers into buf, rp is the (null-terminated) remaining text
+ char *ass = talloc_strndup(NULL, data, length), *rp = ass;
+ while (rp - ass < toff)
+ append(sd, buf, *rp++);
bool contains_text = false; // true if non SDH text was found
bool line_with_text = false; // if last line contained text
@@ -454,8 +420,8 @@ static char *filter_SDH(struct sd_filter *sd, char *format, int n_ignored,
} else {
contains_text = true;
}
- if (length)
- talloc_free(ass);
+ talloc_free(ass);
+
if (contains_text) {
// the ASS data contained normal text after filtering
append(sd, buf, '\0'); // '\0' terminate
@@ -475,24 +441,33 @@ static bool sdh_init(struct sd_filter *ft)
if (!ft->opts->sub_filter_SDH)
return false;
+ if (!ft->event_format) {
+ MP_VERBOSE(ft, "SDH filtering not possible - format missing\n");
+ return false;
+ }
+
return true;
}
static struct demux_packet *sdh_filter(struct sd_filter *ft,
struct demux_packet *pkt)
{
- char *line = (char *)pkt->buffer;
- size_t len = pkt->len;
- if (len >= INT_MAX)
- return NULL;
+ bstr text = sd_ass_pkt_text(ft, pkt, sd_ass_fmt_offset(ft->event_format));
+ if (!text.start || !text.len || pkt->len >= INT_MAX)
+ return pkt; // we don't touch it
- line = filter_SDH(ft, ft->event_format, 1, line, len);
+ ptrdiff_t toff = text.start - pkt->buffer;
+ char *line = filter_SDH(ft, (char *)pkt->buffer, (int)pkt->len, toff);
if (!line)
return NULL;
+ if (0 == bstrcmp0((bstr){(char *)pkt->buffer, pkt->len}, line)) {
+ talloc_free(line);
+ return pkt; // unmodified, no need to allocate new packet
+ }
// Stupidly, this copies it again. One could possibly allocate the packet
// for writing in the first place (new_demux_packet()) and use
- // demux_packet_shorten(). Or not allocate anything on no change.
+ // demux_packet_shorten().
struct demux_packet *npkt = new_demux_packet_from(line, strlen(line));
if (npkt)
demux_packet_copy_attribs(npkt, pkt);
diff --git a/sub/lavc_conv.c b/sub/lavc_conv.c
index d47b0c4..8e1d1aa 100644
--- a/sub/lavc_conv.c
+++ b/sub/lavc_conv.c
@@ -75,7 +75,7 @@ struct lavc_conv *lavc_conv_create(struct mp_log *log, const char *codec_name,
AVCodecContext *avctx = NULL;
AVDictionary *opts = NULL;
const char *fmt = get_lavc_format(priv->codec);
- AVCodec *codec = avcodec_find_decoder(mp_codec_to_av_codec_id(fmt));
+ const AVCodec *codec = avcodec_find_decoder(mp_codec_to_av_codec_id(fmt));
if (!codec)
goto error;
avctx = avcodec_alloc_context3(codec);
diff --git a/sub/osd.c b/sub/osd.c
index de63bef..f02693e 100644
--- a/sub/osd.c
+++ b/sub/osd.c
@@ -290,9 +290,12 @@ static struct sub_bitmaps *render_object(struct osd_state *osd,
check_obj_resize(osd, osdres, obj);
- if (obj->type == OSDTYPE_SUB || obj->type == OSDTYPE_SUB2) {
+ if (obj->type == OSDTYPE_SUB) {
if (obj->sub)
res = sub_get_bitmaps(obj->sub, obj->vo_res, format, video_pts);
+ } else if (obj->type == OSDTYPE_SUB2) {
+ if (obj->sub && sub_is_secondary_visible(obj->sub))
+ res = sub_get_bitmaps(obj->sub, obj->vo_res, format, video_pts);
} else if (obj->type == OSDTYPE_EXTERNAL2) {
if (obj->external2 && obj->external2->format) {
res = sub_bitmaps_copy(NULL, obj->external2); // need to be owner
diff --git a/sub/osd_libass.c b/sub/osd_libass.c
index 667431e..c916c53 100644
--- a/sub/osd_libass.c
+++ b/sub/osd_libass.c
@@ -367,6 +367,14 @@ static void get_osd_bar_box(struct osd_state *osd, struct osd_object *obj,
mp_ass_set_style(style, track->PlayResY, opts->osd_style);
+ if (osd->opts->osd_style->back_color.a) {
+ // override the default osd opaque-box into plain outline. Otherwise
+ // the opaque box is not aligned with the bar (even without shadow),
+ // and each bar ass event gets its own opaque box - breaking the bar.
+ style->BackColour = MP_ASS_COLOR(opts->osd_style->shadow_color);
+ style->BorderStyle = 1; // outline
+ }
+
*o_w = track->PlayResX * (opts->osd_bar_w / 100.0);
*o_h = track->PlayResY * (opts->osd_bar_h / 100.0);
@@ -416,6 +424,22 @@ static void update_progbar(struct osd_state *osd, struct osd_object *obj)
talloc_free(buf.start);
struct ass_draw *d = &(struct ass_draw) { .scale = 4 };
+
+ if (osd->opts->osd_style->back_color.a) {
+ // the bar style always ignores the --osd-back-color config - it messes
+ // up the bar. draw an artificial box at the original back color.
+ struct m_color bc = osd->opts->osd_style->back_color;
+ d->text = talloc_asprintf_append(d->text,
+ "{\\pos(%f,%f)\\bord0\\1a&H%02X\\1c&H%02X%02X%02X&}",
+ px, py, 255 - bc.a, (int)bc.b, (int)bc.g, (int)bc.r);
+
+ ass_draw_start(d);
+ ass_draw_rect_cw(d, -border, -border, width + border, height + border);
+ ass_draw_stop(d);
+ add_osd_ass_event(track, "progbar", d->text);
+ ass_draw_reset(d);
+ }
+
// filled area
d->text = talloc_asprintf_append(d->text, "{\\bord0\\pos(%f,%f)}", px, py);
ass_draw_start(d);
diff --git a/sub/sd.h b/sub/sd.h
index 38bab3a..6801a38 100644
--- a/sub/sd.h
+++ b/sub/sd.h
@@ -88,5 +88,22 @@ struct sd_filter_functions {
extern const struct sd_filter_functions sd_filter_sdh;
extern const struct sd_filter_functions sd_filter_regex;
+extern const struct sd_filter_functions sd_filter_jsre;
+
+
+// convenience utils for filters with ass codec
+
+// num commas to skip at an ass-event before the "Text" field (always last)
+// (doesn't change, can be retrieved once on filter init)
+int sd_ass_fmt_offset(const char *event_format);
+
+// the event (pkt->buffer) "Text" content according to the calculated offset.
+// on malformed event: warns and returns (bstr){NULL,0}
+bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset);
+
+// convert \0-terminated "Text" (ass) content to plaintext, possibly in-place.
+// result.start is out, result.len is MIN(out_siz, strlen(in)) or smaller.
+// if there's room: out[result.len] is set to \0. out == in is allowed.
+bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in);
#endif
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index e5e12cb..939c000 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -68,6 +68,9 @@ static const struct sd_filter_functions *const filters[] = {
#if HAVE_POSIX
&sd_filter_regex,
#endif
+#if HAVE_JAVASCRIPT
+ &sd_filter_jsre,
+#endif
NULL,
};
@@ -945,3 +948,37 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
sb->libass.color = MP_ASS_RGBA(rgb[0], rgb[1], rgb[2], a);
}
}
+
+int sd_ass_fmt_offset(const char *evt_fmt)
+{
+ // "Text" is always last (as it's arbitrary content in buf), e.g. format:
+ // "Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"
+ int n = 0;
+ while (evt_fmt && (evt_fmt = strchr(evt_fmt, ',')))
+ evt_fmt++, n++;
+ return n-1; // buffer is without the format's Start/End, with ReadOrder
+}
+
+bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset)
+{
+ // e.g. pkt->buffer ("4" is ReadOrder): "4,0,Default,,0,0,0,,fifth line"
+ bstr txt = {(char *)pkt->buffer, pkt->len}, t0 = txt;
+ while (offset-- > 0) {
+ int n = bstrchr(txt, ',');
+ if (n < 0) { // shouldn't happen
+ MP_WARN(ft, "Malformed event '%.*s'\n", BSTR_P(t0));
+ return (bstr){NULL, 0};
+ }
+ txt = bstr_cut(txt, n+1);
+ }
+ return txt;
+}
+
+bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in)
+{
+ struct buf b = {out, out_siz, 0};
+ ass_to_plaintext(&b, in);
+ if (b.len < out_siz)
+ out[b.len] = 0;
+ return (bstr){out, b.len};
+}
diff --git a/sub/sd_lavc.c b/sub/sd_lavc.c
index 77877fd..e8da942 100644
--- a/sub/sd_lavc.c
+++ b/sub/sd_lavc.c
@@ -91,7 +91,7 @@ static int init(struct sd *sd)
struct sd_lavc_priv *priv = talloc_zero(NULL, struct sd_lavc_priv);
AVCodecContext *ctx = NULL;
- AVCodec *sub_codec = avcodec_find_decoder(cid);
+ const AVCodec *sub_codec = avcodec_find_decoder(cid);
if (!sub_codec)
goto error;
ctx = avcodec_alloc_context3(sub_codec);
diff --git a/test/subtimes.js b/test/subtimes.js
index 7821e0b..be6940a 100644
--- a/test/subtimes.js
+++ b/test/subtimes.js
@@ -5,3 +5,11 @@ function subtimes() {
}
mp.add_key_binding("t", "subtimes", subtimes);
+
+function secondary_subtimes() {
+ mp.msg.info("secondary-sub-start: " + mp.get_property_number("secondary-sub-start"));
+ mp.msg.info("secondary-sub-end: " + mp.get_property_number("secondary-sub-end"));
+ mp.msg.info("secondary-sub-text: " + mp.get_property_native("secondary-sub-text"));
+}
+
+mp.add_key_binding("T", "secondary_subtimes", secondary_subtimes); \ No newline at end of file
diff --git a/version.sh b/version.sh
index dd896e2..047d567 100755
--- a/version.sh
+++ b/version.sh
@@ -54,7 +54,7 @@ fi
NEW_REVISION="#define VERSION \"${VERSION}\""
OLD_REVISION=$(head -n 1 "$version_h" 2> /dev/null)
BUILDDATE="#define BUILDDATE \"$(date)\""
-MPVCOPYRIGHT="#define MPVCOPYRIGHT \"Copyright © 2000-2020 mpv/MPlayer/mplayer2 projects\""
+MPVCOPYRIGHT="#define MPVCOPYRIGHT \"Copyright © 2000-2021 mpv/MPlayer/mplayer2 projects\""
# Update version.h only on revision changes to avoid spurious rebuilds
if test "$NEW_REVISION" != "$OLD_REVISION"; then
diff --git a/video/csputils.c b/video/csputils.c
index f9b6c98..4df754a 100644
--- a/video/csputils.c
+++ b/video/csputils.c
@@ -103,6 +103,7 @@ const struct m_opt_choice_alternatives mp_csp_light_names[] = {
const struct m_opt_choice_alternatives mp_chroma_names[] = {
{"unknown", MP_CHROMA_AUTO},
+ {"uhd", MP_CHROMA_TOPLEFT},
{"mpeg2/4/h264",MP_CHROMA_LEFT},
{"mpeg1/jpeg", MP_CHROMA_CENTER},
{0}
@@ -188,6 +189,8 @@ enum mp_csp_prim avcol_pri_to_mp_csp_prim(int avpri)
case AVCOL_PRI_BT709: return MP_CSP_PRIM_BT_709;
case AVCOL_PRI_BT2020: return MP_CSP_PRIM_BT_2020;
case AVCOL_PRI_BT470M: return MP_CSP_PRIM_BT_470M;
+ case AVCOL_PRI_SMPTE431: return MP_CSP_PRIM_DCI_P3;
+ case AVCOL_PRI_SMPTE432: return MP_CSP_PRIM_DISPLAY_P3;
default: return MP_CSP_PRIM_AUTO;
}
}
@@ -242,6 +245,8 @@ int mp_csp_prim_to_avcol_pri(enum mp_csp_prim prim)
case MP_CSP_PRIM_BT_709: return AVCOL_PRI_BT709;
case MP_CSP_PRIM_BT_2020: return AVCOL_PRI_BT2020;
case MP_CSP_PRIM_BT_470M: return AVCOL_PRI_BT470M;
+ case MP_CSP_PRIM_DCI_P3: return AVCOL_PRI_SMPTE431;
+ case MP_CSP_PRIM_DISPLAY_P3: return AVCOL_PRI_SMPTE432;
default: return AVCOL_PRI_UNSPECIFIED;
}
}
@@ -288,6 +293,7 @@ enum mp_csp_prim mp_csp_guess_primaries(int width, int height)
enum mp_chroma_location avchroma_location_to_mp(int avloc)
{
switch (avloc) {
+ case AVCHROMA_LOC_TOPLEFT: return MP_CHROMA_TOPLEFT;
case AVCHROMA_LOC_LEFT: return MP_CHROMA_LEFT;
case AVCHROMA_LOC_CENTER: return MP_CHROMA_CENTER;
default: return MP_CHROMA_AUTO;
@@ -297,6 +303,7 @@ enum mp_chroma_location avchroma_location_to_mp(int avloc)
int mp_chroma_location_to_av(enum mp_chroma_location mploc)
{
switch (mploc) {
+ case MP_CHROMA_TOPLEFT: return AVCHROMA_LOC_TOPLEFT;
case MP_CHROMA_LEFT: return AVCHROMA_LOC_LEFT;
case MP_CHROMA_CENTER: return AVCHROMA_LOC_CENTER;
default: return AVCHROMA_LOC_UNSPECIFIED;
@@ -309,8 +316,10 @@ void mp_get_chroma_location(enum mp_chroma_location loc, int *x, int *y)
{
*x = 0;
*y = 0;
- if (loc == MP_CHROMA_LEFT)
+ if (loc == MP_CHROMA_LEFT || loc == MP_CHROMA_TOPLEFT)
*x = -1;
+ if (loc == MP_CHROMA_TOPLEFT)
+ *y = -1;
}
void mp_invert_matrix3x3(float m[3][3])
diff --git a/video/csputils.h b/video/csputils.h
index 965c313..3234682 100644
--- a/video/csputils.h
+++ b/video/csputils.h
@@ -186,6 +186,7 @@ bool mp_colorspace_equal(struct mp_colorspace c1, struct mp_colorspace c2);
enum mp_chroma_location {
MP_CHROMA_AUTO,
+ MP_CHROMA_TOPLEFT, // uhd
MP_CHROMA_LEFT, // mpeg2/4, h264
MP_CHROMA_CENTER, // mpeg1, jpeg
MP_CHROMA_COUNT,
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index f965e00..460334b 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -63,8 +63,8 @@ static void uninit_avctx(struct mp_filter *vd);
static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags);
static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
const enum AVPixelFormat *pix_fmt);
-static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param);
+static int hwdec_opt_help(struct mp_log *log, const m_option_t *opt,
+ struct bstr name);
#define HWDEC_DELAY_QUEUE_COUNT 2
@@ -117,7 +117,8 @@ const struct m_sub_options vd_lavc_conf = {
{"no", INT_MAX}, {"yes", 1}), M_RANGE(1, INT_MAX)},
{"vd-lavc-o", OPT_KEYVALUELIST(avopts)},
{"vd-lavc-dr", OPT_FLAG(dr)},
- {"hwdec", OPT_STRING_VALIDATE(hwdec_api, hwdec_validate_opt),
+ {"hwdec", OPT_STRING(hwdec_api),
+ .help = hwdec_opt_help,
.flags = M_OPT_OPTIONAL_PARAM | UPDATE_HWDEC},
{"hwdec-codecs", OPT_STRING(hwdec_codecs)},
{"hwdec-image-format", OPT_IMAGEFORMAT(hwdec_image_format)},
@@ -135,7 +136,7 @@ const struct m_sub_options vd_lavc_conf = {
.framedrop = AVDISCARD_NONREF,
.dr = 1,
.hwdec_api = "no",
- .hwdec_codecs = "h264,vc1,hevc,vp9,av1",
+ .hwdec_codecs = "h264,vc1,hevc,vp8,vp9,av1",
// Maximum number of surfaces the player wants to buffer. This number
// might require adjustment depending on whatever the player does;
// for example, if vo_gpu increases the number of reference surfaces for
@@ -533,33 +534,30 @@ static void select_and_set_hwdec(struct mp_filter *vd)
}
}
-static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+static int hwdec_opt_help(struct mp_log *log, const m_option_t *opt,
+ struct bstr name)
{
- if (bstr_equals0(param, "help")) {
- struct hwdec_info *hwdecs = NULL;
- int num_hwdecs = 0;
- add_all_hwdec_methods(&hwdecs, &num_hwdecs);
+ struct hwdec_info *hwdecs = NULL;
+ int num_hwdecs = 0;
+ add_all_hwdec_methods(&hwdecs, &num_hwdecs);
- mp_info(log, "Valid values (with alternative full names):\n");
+ mp_info(log, "Valid values (with alternative full names):\n");
- for (int n = 0; n < num_hwdecs; n++) {
- struct hwdec_info *hwdec = &hwdecs[n];
+ for (int n = 0; n < num_hwdecs; n++) {
+ struct hwdec_info *hwdec = &hwdecs[n];
- mp_info(log, " %s (%s)\n", hwdec->method_name, hwdec->name);
- }
+ mp_info(log, " %s (%s)\n", hwdec->method_name, hwdec->name);
+ }
- talloc_free(hwdecs);
+ talloc_free(hwdecs);
- mp_info(log, " auto (yes '')\n");
- mp_info(log, " no\n");
- mp_info(log, " auto-safe\n");
- mp_info(log, " auto-copy\n");
- mp_info(log, " auto-copy-safe\n");
+ mp_info(log, " auto (yes '')\n");
+ mp_info(log, " no\n");
+ mp_info(log, " auto-safe\n");
+ mp_info(log, " auto-copy\n");
+ mp_info(log, " auto-copy-safe\n");
- return M_OPT_EXIT;
- }
- return 0;
+ return M_OPT_EXIT;
}
static void force_fallback(struct mp_filter *vd)
@@ -661,7 +659,9 @@ static void init_avctx(struct mp_filter *vd)
if (!ctx->use_hwdec && ctx->vo && lavc_param->dr) {
avctx->opaque = vd;
avctx->get_buffer2 = get_buffer2_direct;
+#if LIBAVCODEC_VERSION_MAJOR < 60
avctx->thread_safe_callbacks = 1;
+#endif
}
avctx->flags |= lavc_param->bitexact ? AV_CODEC_FLAG_BITEXACT : 0;
diff --git a/video/filter/vf_sub.c b/video/filter/vf_sub.c
index 5c49ac5..2ed6a2c 100644
--- a/video/filter/vf_sub.c
+++ b/video/filter/vf_sub.c
@@ -116,9 +116,18 @@ error:
mp_filter_internal_mark_failed(f);
}
+static void vf_sub_destroy(struct mp_filter *f)
+{
+ struct mp_stream_info *info = mp_filter_find_stream_info(f);
+ struct osd_state *osd = info ? info->osd : NULL;
+ if (osd)
+ osd_set_render_subs_in_filter(osd, false);
+}
+
static const struct mp_filter_info vf_sub_filter = {
.name = "sub",
.process = vf_sub_process,
+ .destroy = vf_sub_destroy,
.priv_size = sizeof(struct priv),
};
diff --git a/video/image_writer.c b/video/image_writer.c
index fb297f9..cff8609 100644
--- a/video/image_writer.c
+++ b/video/image_writer.c
@@ -103,7 +103,7 @@ static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp
av_init_packet(&pkt);
- struct AVCodec *codec;
+ const AVCodec *codec;
if (ctx->opts->format == AV_CODEC_ID_WEBP) {
codec = avcodec_find_encoder_by_name("libwebp"); // non-animated encoder
} else {
@@ -251,7 +251,7 @@ static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp
#endif
-static int get_encoder_format(struct AVCodec *codec, int srcfmt, bool highdepth)
+static int get_encoder_format(const AVCodec *codec, int srcfmt, bool highdepth)
{
const enum AVPixelFormat *pix_fmts = codec->pix_fmts;
int current = 0;
@@ -277,7 +277,7 @@ static int get_encoder_format(struct AVCodec *codec, int srcfmt, bool highdepth)
static int get_target_format(struct image_writer_ctx *ctx)
{
- struct AVCodec *codec = avcodec_find_encoder(ctx->opts->format);
+ const AVCodec *codec = avcodec_find_encoder(ctx->opts->format);
if (!codec)
goto unknown;
diff --git a/video/out/cocoa_cb_common.swift b/video/out/cocoa_cb_common.swift
index 476b482..dd0738f 100644
--- a/video/out/cocoa_cb_common.swift
+++ b/video/out/cocoa_cb_common.swift
@@ -83,8 +83,7 @@ class CocoaCB: Common {
}
func updateWindowSize(_ vo: UnsafeMutablePointer<vo>) {
- guard let opts: mp_vo_opts = mpv?.opts,
- let targetScreen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main else
+ guard let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main else
{
log.sendWarning("Couldn't update Window size, no Screen available")
return
@@ -133,11 +132,11 @@ class CocoaCB: Common {
}
override func windowSetToFullScreen() {
- layer?.update()
+ layer?.update(force: true)
}
override func windowSetToWindow() {
- layer?.update()
+ layer?.update(force: true)
}
override func windowDidUpdateFrame() {
diff --git a/video/out/d3d11/context.c b/video/out/d3d11/context.c
index b77e328..57feb27 100644
--- a/video/out/d3d11/context.c
+++ b/video/out/d3d11/context.c
@@ -28,7 +28,7 @@
static int d3d11_validate_adapter(struct mp_log *log,
const struct m_option *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
struct d3d11_opts {
int feature_level;
@@ -61,7 +61,7 @@ const struct m_sub_options d3d11_conf = {
{"d3d11-flip", OPT_FLAG(flip)},
{"d3d11-sync-interval", OPT_INT(sync_interval), M_RANGE(0, 4)},
{"d3d11-adapter", OPT_STRING_VALIDATE(adapter_name,
- d3d11_validate_adapter)},
+ d3d11_validate_adapter)},
{"d3d11-output-format", OPT_CHOICE(output_format,
{"auto", DXGI_FORMAT_UNKNOWN},
{"rgba8", DXGI_FORMAT_R8G8B8A8_UNORM},
@@ -111,8 +111,9 @@ struct priv {
static int d3d11_validate_adapter(struct mp_log *log,
const struct m_option *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
+ struct bstr param = bstr0(*value);
bool help = bstr_equals0(param, "help");
bool adapter_matched = false;
struct bstr listing = { 0 };
diff --git a/video/out/drm_atomic.c b/video/out/drm_atomic.c
index 0fa22f0..bf7012c 100644
--- a/video/out/drm_atomic.c
+++ b/video/out/drm_atomic.c
@@ -165,7 +165,7 @@ struct drm_atomic_context *drm_atomic_create_context(struct mp_log *log, int fd,
plane_res = drmModeGetPlaneResources(fd);
if (!plane_res) {
- mp_err(log, "Cannot retrieve plane ressources: %s\n", mp_strerror(errno));
+ mp_err(log, "Cannot retrieve plane resources: %s\n", mp_strerror(errno));
goto fail;
}
diff --git a/video/out/drm_common.c b/video/out/drm_common.c
index 64c84ca..5e730c3 100644
--- a/video/out/drm_common.c
+++ b/video/out/drm_common.c
@@ -54,27 +54,31 @@
static int vt_switcher_pipe[2];
-static int drm_validate_connector_opt(
- struct mp_log *log, const struct m_option *opt, struct bstr name,
- struct bstr param);
+static int drm_connector_opt_help(
+ struct mp_log *log, const struct m_option *opt, struct bstr name);
+
+static int drm_mode_opt_help(
+ struct mp_log *log, const struct m_option *opt, struct bstr name);
static int drm_validate_mode_opt(
struct mp_log *log, const struct m_option *opt, struct bstr name,
- struct bstr param);
+ const char **value);
static void kms_show_available_modes(
struct mp_log *log, const drmModeConnector *connector);
-static void kms_show_available_connectors(struct mp_log *log, int card_no);
+static void kms_show_available_connectors(struct mp_log *log, int card_no,
+ const char *card_path);
static double mode_get_Hz(const drmModeModeInfo *mode);
#define OPT_BASE_STRUCT struct drm_opts
const struct m_sub_options drm_conf = {
.opts = (const struct m_option[]) {
- {"drm-connector", OPT_STRING_VALIDATE(drm_connector_spec,
- drm_validate_connector_opt)},
- {"drm-mode", OPT_STRING_VALIDATE(drm_mode_spec,
- drm_validate_mode_opt)},
+ {"drm-device", OPT_STRING(drm_device_path), .flags = M_OPT_FILE},
+ {"drm-connector", OPT_STRING(drm_connector_spec),
+ .help = drm_connector_opt_help},
+ {"drm-mode", OPT_STRING_VALIDATE(drm_mode_spec, drm_validate_mode_opt),
+ .help = drm_mode_opt_help},
{"drm-atomic", OPT_CHOICE(drm_atomic, {"no", 0}, {"auto", 1})},
{"drm-draw-plane", OPT_CHOICE(drm_draw_plane,
{"primary", DRM_OPTS_PRIMARY_PLANE},
@@ -122,6 +126,9 @@ static const char *connector_names[] = {
"Virtual", // DRM_MODE_CONNECTOR_VIRTUAL
"DSI", // DRM_MODE_CONNECTOR_DSI
"DPI", // DRM_MODE_CONNECTOR_DPI
+ "Writeback", // DRM_MODE_CONNECTOR_WRITEBACK
+ "SPI", // DRM_MODE_CONNECTOR_SPI
+ "USB", // DRM_MODE_CONNECTOR_USB
};
struct drm_mode_spec {
@@ -142,8 +149,15 @@ struct drm_mode_spec {
static void get_connector_name(const drmModeConnector *connector,
char ret[MAX_CONNECTOR_NAME_LEN])
{
- snprintf(ret, MAX_CONNECTOR_NAME_LEN, "%s-%d",
- connector_names[connector->connector_type],
+ const char *type_name;
+
+ if (connector->connector_type < MP_ARRAY_SIZE(connector_names)) {
+ type_name = connector_names[connector->connector_type];
+ } else {
+ type_name = "UNKNOWN";
+ }
+
+ snprintf(ret, MAX_CONNECTOR_NAME_LEN, "%s-%d", type_name,
connector->connector_type_id);
}
@@ -198,7 +212,8 @@ static bool setup_connector(struct kms *kms, const drmModeRes *res,
connector = get_connector_by_name(kms, res, connector_name);
if (!connector) {
MP_ERR(kms, "No connector with name %s found\n", connector_name);
- kms_show_available_connectors(kms->log, kms->card_no);
+ kms_show_available_connectors(kms->log, kms->card_no,
+ kms->primary_node_path);
return false;
}
} else {
@@ -497,11 +512,62 @@ err:
return false;
}
-static int open_card(int card_no)
+static int open_card_path(const char *path)
{
- char card_path[128];
- snprintf(card_path, sizeof(card_path), DRM_DEV_NAME, DRM_DIR_NAME, card_no);
- return open(card_path, O_RDWR | O_CLOEXEC);
+ return open(path, O_RDWR | O_CLOEXEC);
+}
+
+static char *get_primary_device_path(struct mp_log *log, int *card_no)
+{
+ drmDevice *devices[DRM_MAX_MINOR] = { 0 };
+ int card_count = drmGetDevices2(0, devices, MP_ARRAY_SIZE(devices));
+ char *device_path = NULL;
+ bool card_no_given = (*card_no >= 0);
+
+ if (card_count < 0) {
+ mp_err(log, "Listing DRM devices with drmGetDevices failed! (%s)\n",
+ mp_strerror(errno));
+ goto err;
+ }
+
+ if (card_no_given && *card_no > (card_count - 1)) {
+ mp_err(log, "Card number %d given too high! %d devices located.\n",
+ *card_no, card_count);
+ goto err;
+ }
+
+ for (int i = card_no_given ? *card_no : 0; i < card_count; i++) {
+ drmDevice *dev = devices[i];
+
+ if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) {
+ if (card_no_given) {
+ mp_err(log,
+ "DRM card number %d given, yet it does not have "
+ "a primary node!\n", i);
+ break;
+ }
+
+ continue;
+ }
+
+ const char *primary_node_path = dev->nodes[DRM_NODE_PRIMARY];
+
+ mp_verbose(log, "Picked DRM card %d, primary node %s%s.\n",
+ i, primary_node_path,
+ card_no_given ? "" : " as the default");
+
+ device_path = talloc_strdup(log, primary_node_path);
+ *card_no = i;
+ break;
+ }
+
+ if (!device_path)
+ mp_err(log, "No primary DRM device could be picked!\n");
+
+err:
+ drmFreeDevices(devices, card_count);
+
+ return device_path;
}
static void parse_connector_spec(struct mp_log *log,
@@ -509,33 +575,54 @@ static void parse_connector_spec(struct mp_log *log,
int *card_no, char **connector_name)
{
if (!connector_spec) {
- *card_no = 0;
+ *card_no = -1;
*connector_name = NULL;
return;
}
char *dot_ptr = strchr(connector_spec, '.');
if (dot_ptr) {
+ mp_warn(log, "Warning: Selecting a connector by index with drm-connector "
+ "is deprecated. Use the drm-device option instead.\n");
*card_no = atoi(connector_spec);
*connector_name = talloc_strdup(log, dot_ptr + 1);
} else {
- *card_no = 0;
+ *card_no = -1;
*connector_name = talloc_strdup(log, connector_spec);
}
}
-struct kms *kms_create(struct mp_log *log, const char *connector_spec,
+struct kms *kms_create(struct mp_log *log,
+ const char *drm_device_path,
+ const char *connector_spec,
const char* mode_spec,
int draw_plane, int drmprime_video_plane,
bool use_atomic)
{
int card_no = -1;
char *connector_name = NULL;
+
parse_connector_spec(log, connector_spec, &card_no, &connector_name);
+ if (drm_device_path && card_no != -1)
+ mp_warn(log, "Both DRM device and card number (as part of "
+ "drm-connector) are set! Will prefer given device path "
+ "'%s'!\n",
+ drm_device_path);
+
+ char *primary_node_path = drm_device_path ?
+ talloc_strdup(log, drm_device_path) :
+ get_primary_device_path(log, &card_no);
+
+ if (!primary_node_path) {
+ mp_err(log,
+ "Failed to find a usable DRM primary node!\n");
+ return NULL;
+ }
struct kms *kms = talloc(NULL, struct kms);
*kms = (struct kms) {
.log = mp_log_new(kms, log, "kms"),
- .fd = open_card(card_no),
+ .primary_node_path = primary_node_path,
+ .fd = open_card_path(primary_node_path),
.connector = NULL,
.encoder = NULL,
.mode = {{0}},
@@ -551,12 +638,6 @@ struct kms *kms_create(struct mp_log *log, const char *connector_spec,
goto err;
}
- char *devname = drmGetDeviceNameFromFd(kms->fd);
- if (devname) {
- mp_verbose(log, "Device name: %s\n", devname);
- drmFree(devname);
- }
-
drmVersionPtr ver = drmGetVersion(kms->fd);
if (ver) {
mp_verbose(log, "Driver: %s %d.%d.%d (%s)\n", ver->name,
@@ -606,6 +687,7 @@ err:
drmModeFreeResources(res);
if (connector_name)
talloc_free(connector_name);
+
kms_destroy(kms);
return NULL;
}
@@ -652,12 +734,13 @@ static void kms_show_available_modes(
}
static void kms_show_foreach_connector(struct mp_log *log, int card_no,
+ const char *card_path,
void (*show_fn)(struct mp_log*, int,
const drmModeConnector*))
{
- int fd = open_card(card_no);
+ int fd = open_card_path(card_path);
if (fd < 0) {
- mp_err(log, "Failed to open card %d\n", card_no);
+ mp_err(log, "Failed to open card %d (%s)\n", card_no, card_path);
return;
}
@@ -693,11 +776,13 @@ static void kms_show_connector_name_and_state_callback(
mp_info(log, " %s (%s)\n", other_connector_name, connection_str);
}
-static void kms_show_available_connectors(struct mp_log *log, int card_no)
+static void kms_show_available_connectors(struct mp_log *log, int card_no,
+ const char *card_path)
{
- mp_info(log, "Available connectors for card %d:\n", card_no);
+ mp_info(log, "Available connectors for card %d (%s):\n", card_no,
+ card_path);
kms_show_foreach_connector(
- log, card_no, kms_show_connector_name_and_state_callback);
+ log, card_no, card_path, kms_show_connector_name_and_state_callback);
mp_info(log, "\n");
}
@@ -715,21 +800,45 @@ static void kms_show_connector_modes_callback(struct mp_log *log, int card_no,
mp_info(log, "\n");
}
-static void kms_show_available_connectors_and_modes(struct mp_log *log, int card_no)
+static void kms_show_available_connectors_and_modes(struct mp_log *log,
+ int card_no,
+ const char *card_path)
{
- kms_show_foreach_connector(log, card_no, kms_show_connector_modes_callback);
+ kms_show_foreach_connector(log, card_no, card_path,
+ kms_show_connector_modes_callback);
}
static void kms_show_foreach_card(
- struct mp_log *log, void (*show_fn)(struct mp_log*,int))
+ struct mp_log *log, void (*show_fn)(struct mp_log*,int,const char *))
{
- for (int card_no = 0; card_no < DRM_MAX_MINOR; card_no++) {
- int fd = open_card(card_no);
- if (fd < 0)
- break;
+ drmDevice *devices[DRM_MAX_MINOR] = { 0 };
+ int card_count = drmGetDevices2(0, devices, MP_ARRAY_SIZE(devices));
+ if (card_count < 0) {
+ mp_err(log, "Listing DRM devices with drmGetDevices failed! (%s)\n",
+ mp_strerror(errno));
+ return;
+ }
+
+ for (int i = 0; i < card_count; i++) {
+ drmDevice *dev = devices[i];
+
+ if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY)))
+ continue;
+
+ const char *primary_node_path = dev->nodes[DRM_NODE_PRIMARY];
+
+ int fd = open_card_path(primary_node_path);
+ if (fd < 0) {
+ mp_err(log, "Failed to open primary DRM node path %s!\n",
+ primary_node_path);
+ continue;
+ }
+
close(fd);
- show_fn(log, card_no);
+ show_fn(log, i, primary_node_path);
}
+
+ drmFreeDevices(devices, card_count);
}
static void kms_show_available_cards_and_connectors(struct mp_log *log)
@@ -747,31 +856,28 @@ double kms_get_display_fps(const struct kms *kms)
return mode_get_Hz(&kms->mode.mode);
}
-static int drm_validate_connector_opt(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param)
+static int drm_connector_opt_help(struct mp_log *log, const struct m_option *opt,
+ struct bstr name)
{
- if (bstr_equals0(param, "help")) {
- kms_show_available_cards_and_connectors(log);
- return M_OPT_EXIT;
- }
- return 1;
+ kms_show_available_cards_and_connectors(log);
+ return M_OPT_EXIT;
}
-static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param)
+static int drm_mode_opt_help(struct mp_log *log, const struct m_option *opt,
+ struct bstr name)
{
- if (bstr_equals0(param, "help")) {
- kms_show_available_cards_connectors_and_modes(log);
- return M_OPT_EXIT;
- }
+ kms_show_available_cards_connectors_and_modes(log);
+ return M_OPT_EXIT;
+}
- char *spec = bstrto0(NULL, param);
- if (!parse_mode_spec(spec, NULL)) {
+static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt,
+ struct bstr name, const char **value)
+{
+ const char *param = *value;
+ if (!parse_mode_spec(param, NULL)) {
mp_fatal(log, "Invalid value for option drm-mode. Must be a positive number, a string of the format WxH[@R] or 'help'\n");
- talloc_free(spec);
return M_OPT_INVALID;
}
- talloc_free(spec);
return 1;
}
diff --git a/video/out/drm_common.h b/video/out/drm_common.h
index aa08312..5aa3681 100644
--- a/video/out/drm_common.h
+++ b/video/out/drm_common.h
@@ -29,6 +29,7 @@
struct kms {
struct mp_log *log;
+ char *primary_node_path;
int fd;
drmModeConnector *connector;
drmModeEncoder *encoder;
@@ -46,6 +47,7 @@ struct vt_switcher {
};
struct drm_opts {
+ char *drm_device_path;
char *drm_connector_spec;
char *drm_mode_spec;
int drm_atomic;
@@ -79,7 +81,9 @@ void vt_switcher_acquire(struct vt_switcher *s, void (*handler)(void*),
void vt_switcher_release(struct vt_switcher *s, void (*handler)(void*),
void *user_data);
-struct kms *kms_create(struct mp_log *log, const char *connector_spec,
+struct kms *kms_create(struct mp_log *log,
+ const char *drm_device_path,
+ const char *connector_spec,
const char *mode_spec,
int draw_plane, int drmprime_video_plane,
bool use_atomic);
diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c
index bfbd4e9..4ebc033 100644
--- a/video/out/filter_kernels.c
+++ b/video/out/filter_kernels.c
@@ -199,7 +199,7 @@ static double hamming(params *p, double x)
static double quadric(params *p, double x)
{
- if (x < 0.75) {
+ if (x < 0.5) {
return 0.75 - x * x;
} else if (x < 1.5) {
double t = x - 1.5;
diff --git a/video/out/gpu/context.c b/video/out/gpu/context.c
index 39696fb..6e58cce 100644
--- a/video/out/gpu/context.c
+++ b/video/out/gpu/context.c
@@ -50,6 +50,7 @@ extern const struct ra_ctx_fns ra_ctx_vulkan_wayland;
extern const struct ra_ctx_fns ra_ctx_vulkan_win;
extern const struct ra_ctx_fns ra_ctx_vulkan_xlib;
extern const struct ra_ctx_fns ra_ctx_vulkan_android;
+extern const struct ra_ctx_fns ra_ctx_vulkan_display;
/* Direct3D 11 */
extern const struct ra_ctx_fns ra_ctx_d3d11;
@@ -106,20 +107,25 @@ static const struct ra_ctx_fns *contexts[] = {
#if HAVE_X11
&ra_ctx_vulkan_xlib,
#endif
+ &ra_ctx_vulkan_display,
#endif
};
+int ra_ctx_api_help(struct mp_log *log, const struct m_option *opt,
+ struct bstr name)
+{
+ mp_info(log, "GPU APIs (contexts):\n");
+ mp_info(log, " auto (autodetect)\n");
+ for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++)
+ mp_info(log, " %s (%s)\n", contexts[n]->type, contexts[n]->name);
+ return M_OPT_EXIT;
+}
+
int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
- if (bstr_equals0(param, "help")) {
- mp_info(log, "GPU APIs (contexts):\n");
- mp_info(log, " auto (autodetect)\n");
- for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++)
- mp_info(log, " %s (%s)\n", contexts[n]->type, contexts[n]->name);
- return M_OPT_EXIT;
- }
+ struct bstr param = bstr0(*value);
if (bstr_equals0(param, "auto"))
return 1;
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
@@ -129,16 +135,20 @@ int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt,
return M_OPT_INVALID;
}
+int ra_ctx_context_help(struct mp_log *log, const struct m_option *opt,
+ struct bstr name)
+{
+ mp_info(log, "GPU contexts (APIs):\n");
+ mp_info(log, " auto (autodetect)\n");
+ for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++)
+ mp_info(log, " %s (%s)\n", contexts[n]->name, contexts[n]->type);
+ return M_OPT_EXIT;
+}
+
int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
- if (bstr_equals0(param, "help")) {
- mp_info(log, "GPU contexts (APIs):\n");
- mp_info(log, " auto (autodetect)\n");
- for (int n = 0; n < MP_ARRAY_SIZE(contexts); n++)
- mp_info(log, " %s (%s)\n", contexts[n]->name, contexts[n]->type);
- return M_OPT_EXIT;
- }
+ struct bstr param = bstr0(*value);
if (bstr_equals0(param, "auto"))
return 1;
for (int i = 0; i < MP_ARRAY_SIZE(contexts); i++) {
diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h
index 8c35eb0..ca71150 100644
--- a/video/out/gpu/context.h
+++ b/video/out/gpu/context.h
@@ -100,7 +100,11 @@ struct ra_ctx *ra_ctx_create(struct vo *vo, const char *context_type,
void ra_ctx_destroy(struct ra_ctx **ctx);
struct m_option;
+int ra_ctx_api_help(struct mp_log *log, const struct m_option *opt,
+ struct bstr name);
int ra_ctx_validate_api(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
+int ra_ctx_context_help(struct mp_log *log, const struct m_option *opt,
+ struct bstr name);
int ra_ctx_validate_context(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
diff --git a/video/out/gpu/hwdec.c b/video/out/gpu/hwdec.c
index db75c64..4fb6240 100644
--- a/video/out/gpu/hwdec.c
+++ b/video/out/gpu/hwdec.c
@@ -106,8 +106,9 @@ struct ra_hwdec *ra_hwdec_load_driver(struct ra *ra, struct mp_log *log,
}
int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
+ struct bstr param = bstr0(*value);
bool help = bstr_equals0(param, "help");
if (help)
mp_info(log, "Available hwdecs:\n");
diff --git a/video/out/gpu/hwdec.h b/video/out/gpu/hwdec.h
index 3a1ae3e..050a358 100644
--- a/video/out/gpu/hwdec.h
+++ b/video/out/gpu/hwdec.h
@@ -109,7 +109,7 @@ struct ra_hwdec *ra_hwdec_load_driver(struct ra *ra, struct mp_log *log,
bool is_auto);
int ra_hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
void ra_hwdec_uninit(struct ra_hwdec *hwdec);
diff --git a/video/out/gpu/lcms.c b/video/out/gpu/lcms.c
index 0f3a0bf..704f1fb 100644
--- a/video/out/gpu/lcms.c
+++ b/video/out/gpu/lcms.c
@@ -67,8 +67,9 @@ static bool parse_3dlut_size(const char *arg, int *p1, int *p2, int *p3)
}
static int validate_3dlut_size_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
+ struct bstr param = bstr0(*value);
int p1, p2, p3;
char s[20];
snprintf(s, sizeof(s), "%.*s", BSTR_P(param));
@@ -83,11 +84,12 @@ const struct m_sub_options mp_icc_conf = {
{"icc-profile-auto", OPT_FLAG(profile_auto)},
{"icc-cache-dir", OPT_STRING(cache_dir), .flags = M_OPT_FILE},
{"icc-intent", OPT_INT(intent)},
- {"icc-contrast", OPT_CHOICE(contrast, {"inf", -1}),
+ {"icc-force-contrast", OPT_CHOICE(contrast, {"no", 0}, {"inf", -1}),
M_RANGE(0, 1000000)},
{"icc-3dlut-size", OPT_STRING_VALIDATE(size_str, validate_3dlut_size_opt)},
{"3dlut-size", OPT_REPLACED("icc-3dlut-size")},
{"icc-cache", OPT_REMOVED("see icc-cache-dir")},
+ {"icc-contrast", OPT_REMOVED("see icc-force-contrast")},
{0}
},
.size = sizeof(struct mp_icc_opts),
@@ -271,48 +273,46 @@ static cmsHPROFILE get_vid_profile(struct gl_lcms *p, cmsContext cms,
break;
case MP_CSP_TRC_BT_1886: {
- // To build an appropriate BT.1886 transformation we need access to
- // the display's black point, so we LittleCMS' detection function.
- // Relative colorimetric is used since we want to approximate the
- // BT.1886 to the target device's actual black point even in e.g.
- // perceptual mode
- const int intent = MP_INTENT_RELATIVE_COLORIMETRIC;
- cmsCIEXYZ bp_XYZ;
- if (!cmsDetectBlackPoint(&bp_XYZ, disp_profile, intent, 0))
- return false;
-
- // Map this XYZ value back into the (linear) source space
- cmsToneCurve *linear = cmsBuildGamma(cms, 1.0);
- cmsHPROFILE rev_profile = cmsCreateRGBProfileTHR(cms, &wp_xyY, &prim_xyY,
- (cmsToneCurve*[3]){linear, linear, linear});
- cmsHPROFILE xyz_profile = cmsCreateXYZProfile();
- cmsHTRANSFORM xyz2src = cmsCreateTransformTHR(cms,
- xyz_profile, TYPE_XYZ_DBL, rev_profile, TYPE_RGB_DBL,
- intent, 0);
- cmsFreeToneCurve(linear);
- cmsCloseProfile(rev_profile);
- cmsCloseProfile(xyz_profile);
- if (!xyz2src)
- return false;
-
double src_black[3];
- cmsDoTransform(xyz2src, &bp_XYZ, src_black, 1);
- cmsDeleteTransform(xyz2src);
-
- // Contrast limiting
- if (p->opts->contrast > 0) {
+ if (p->opts->contrast < 0) {
+ // User requested infinite contrast, return 2.4 profile
+ tonecurve[0] = cmsBuildGamma(cms, 2.4);
+ break;
+ } else if (p->opts->contrast > 0) {
+ MP_VERBOSE(p, "Using specified contrast: %d\n", p->opts->contrast);
for (int i = 0; i < 3; i++)
- src_black[i] = MPMAX(src_black[i], 1.0 / p->opts->contrast);
- }
-
- // Built-in contrast failsafe
- double contrast = 3.0 / (src_black[0] + src_black[1] + src_black[2]);
- MP_VERBOSE(p, "Detected ICC profile contrast: %f\n", contrast);
- if (contrast > 100000 && !p->opts->contrast) {
- MP_WARN(p, "ICC profile detected contrast very high (>100000),"
- " falling back to contrast 1000 for sanity. Set the"
- " icc-contrast option to silence this warning.\n");
- src_black[0] = src_black[1] = src_black[2] = 1.0 / 1000;
+ src_black[i] = 1.0 / p->opts->contrast;
+ } else {
+ // To build an appropriate BT.1886 transformation we need access to
+ // the display's black point, so we use LittleCMS' detection
+ // function. Relative colorimetric is used since we want to
+ // approximate the BT.1886 to the target device's actual black
+ // point even in e.g. perceptual mode
+ const int intent = MP_INTENT_RELATIVE_COLORIMETRIC;
+ cmsCIEXYZ bp_XYZ;
+ if (!cmsDetectBlackPoint(&bp_XYZ, disp_profile, intent, 0))
+ return false;
+
+ // Map this XYZ value back into the (linear) source space
+ cmsHPROFILE rev_profile;
+ cmsToneCurve *linear = cmsBuildGamma(cms, 1.0);
+ rev_profile = cmsCreateRGBProfileTHR(cms, &wp_xyY, &prim_xyY,
+ (cmsToneCurve*[3]){linear, linear, linear});
+ cmsHPROFILE xyz_profile = cmsCreateXYZProfile();
+ cmsHTRANSFORM xyz2src = cmsCreateTransformTHR(cms,
+ xyz_profile, TYPE_XYZ_DBL, rev_profile, TYPE_RGB_DBL,
+ intent, 0);
+ cmsFreeToneCurve(linear);
+ cmsCloseProfile(rev_profile);
+ cmsCloseProfile(xyz_profile);
+ if (!xyz2src)
+ return false;
+
+ cmsDoTransform(xyz2src, &bp_XYZ, src_black, 1);
+ cmsDeleteTransform(xyz2src);
+
+ double contrast = 3.0 / (src_black[0] + src_black[1] + src_black[2]);
+ MP_VERBOSE(p, "Detected ICC profile contrast: %f\n", contrast);
}
// Build the parametric BT.1886 transfer curve, one per channel
diff --git a/video/out/gpu/shader_cache.c b/video/out/gpu/shader_cache.c
index 5e96de9..c19a7e8 100644
--- a/video/out/gpu/shader_cache.c
+++ b/video/out/gpu/shader_cache.c
@@ -17,7 +17,7 @@
#include "utils.h"
// Force cache flush if more than this number of shaders is created.
-#define SC_MAX_ENTRIES 48
+#define SC_MAX_ENTRIES 256
union uniform_val {
float f[9]; // RA_VARTYPE_FLOAT
@@ -458,6 +458,26 @@ void gl_sc_blend(struct gl_shader_cache *sc,
sc->params.blend_dst_alpha = blend_dst_alpha;
}
+const char *gl_sc_bvec(struct gl_shader_cache *sc, int dims)
+{
+ static const char *bvecs[] = {
+ [1] = "bool",
+ [2] = "bvec2",
+ [3] = "bvec3",
+ [4] = "bvec4",
+ };
+
+ static const char *vecs[] = {
+ [1] = "float",
+ [2] = "vec2",
+ [3] = "vec3",
+ [4] = "vec4",
+ };
+
+ assert(dims > 0 && dims < MP_ARRAY_SIZE(bvecs));
+ return sc->ra->glsl_version >= 130 ? bvecs[dims] : vecs[dims];
+}
+
static const char *vao_glsl_type(const struct ra_renderpass_input *e)
{
// pretty dumb... too dumb, but works for us
diff --git a/video/out/gpu/shader_cache.h b/video/out/gpu/shader_cache.h
index 547c6b6..3c87513 100644
--- a/video/out/gpu/shader_cache.h
+++ b/video/out/gpu/shader_cache.h
@@ -43,6 +43,10 @@ void gl_sc_uniform_mat2(struct gl_shader_cache *sc, char *name,
bool transpose, float *v);
void gl_sc_uniform_mat3(struct gl_shader_cache *sc, char *name,
bool transpose, float *v);
+
+// Return the correct bvecN() variant for using mix() in this GLSL version
+const char *gl_sc_bvec(struct gl_shader_cache *sc, int dims);
+
void gl_sc_blend(struct gl_shader_cache *sc,
enum ra_blend blend_src_rgb,
enum ra_blend blend_dst_rgb,
diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c
index 851289e..e4c4ee9 100644
--- a/video/out/gpu/video.c
+++ b/video/out/gpu/video.c
@@ -317,7 +317,7 @@ static const struct gl_video_opts gl_video_opts_def = {
},
.scaler_resizes_only = 1,
.scaler_lut_size = 6,
- .interpolation_threshold = 0.0001,
+ .interpolation_threshold = 0.01,
.alpha_mode = ALPHA_BLEND_TILES,
.background = {0, 0, 0, 255},
.gamma = 1.0f,
@@ -337,13 +337,13 @@ static const struct gl_video_opts gl_video_opts_def = {
};
static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
static int validate_window_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
static int validate_error_diffusion_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param);
+ struct bstr name, const char **value);
#define OPT_BASE_STRUCT struct gl_video_opts
@@ -664,6 +664,11 @@ static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim,
talloc_free(lut3d);
+ if (!p->lut_3d_texture) {
+ p->use_lut_3d = false;
+ return false;
+ }
+
return true;
}
@@ -952,9 +957,6 @@ static void init_video(struct gl_video *p)
params.w, params.h);
plane->tex = ra_tex_create(p->ra, &params);
- if (!plane->tex)
- abort(); // shit happens
-
p->use_integer_conversion |= format->ctype == RA_CTYPE_UINT;
}
}
@@ -1191,16 +1193,8 @@ static void dispatch_compute(struct gl_video *p, int w, int h,
if (!s->tex)
continue;
- // We need to rescale the coordinates to the true texture size
- char *tex_scale = mp_tprintf(32, "tex_scale%d", n);
- gl_sc_uniform_vec2(p->sc, tex_scale, (float[2]){
- (float)s->w / s->tex->params.w,
- (float)s->h / s->tex->params.h,
- });
-
- PRELUDE("#define texmap%d_raw(id) (tex_scale%d * outcoord(id))\n", n, n);
- PRELUDE("#define texmap%d(id) (texture_rot%d * texmap%d_raw(id) + "
- "pixel_size%d * texture_off%d)\n", n, n, n, n, n);
+ PRELUDE("#define texmap%d(id) (texture_rot%d * outcoord(id) + "
+ "pixel_size%d * texture_off%d)\n", n, n, n, n);
PRELUDE("#define texcoord%d texmap%d(gl_GlobalInvocationID)\n", n, n);
}
@@ -2341,26 +2335,29 @@ static void pass_convert_yuv(struct gl_video *p)
// as per the BT.2020 specification, table 4. This is a non-linear
// transformation because (constant) luminance receives non-equal
// contributions from the three different channels.
- GLSLF("// constant luminance conversion\n");
- GLSL(color.br = color.br * mix(vec2(1.5816, 0.9936),
- vec2(1.9404, 1.7184),
- lessThanEqual(color.br, vec2(0)))
- + color.gg;)
+ GLSLF("// constant luminance conversion \n"
+ "color.br = color.br * mix(vec2(1.5816, 0.9936), \n"
+ " vec2(1.9404, 1.7184), \n"
+ " %s(lessThanEqual(color.br, vec2(0))))\n"
+ " + color.gg; \n",
+ gl_sc_bvec(p->sc, 2));
// Expand channels to camera-linear light. This shader currently just
// assumes everything uses the BT.2020 12-bit gamma function, since the
// difference between 10 and 12-bit is negligible for anything other
// than 12-bit content.
- GLSL(color.rgb = mix(color.rgb * vec3(1.0/4.5),
- pow((color.rgb + vec3(0.0993))*vec3(1.0/1.0993),
- vec3(1.0/0.45)),
- lessThanEqual(vec3(0.08145), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(1.0/4.5), \n"
+ " pow((color.rgb + vec3(0.0993))*vec3(1.0/1.0993), \n"
+ " vec3(1.0/0.45)), \n"
+ " %s(lessThanEqual(vec3(0.08145), color.rgb))); \n",
+ gl_sc_bvec(p->sc, 3));
// Calculate the green channel from the expanded RYcB
// The BT.2020 specification says Yc = 0.2627*R + 0.6780*G + 0.0593*B
GLSL(color.g = (color.g - 0.2627*color.r - 0.0593*color.b)*1.0/0.6780;)
// Recompress to receive the R'G'B' result, same as other systems
- GLSL(color.rgb = mix(color.rgb * vec3(4.5),
- vec3(1.0993) * pow(color.rgb, vec3(0.45)) - vec3(0.0993),
- lessThanEqual(vec3(0.0181), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(4.5), \n"
+ " vec3(1.0993) * pow(color.rgb, vec3(0.45)) - vec3(0.0993), \n"
+ " %s(lessThanEqual(vec3(0.0181), color.rgb))); \n",
+ gl_sc_bvec(p->sc, 3));
}
p->components = 3;
@@ -3578,6 +3575,10 @@ static bool pass_upload_image(struct gl_video *p, struct mp_image *mpi, uint64_t
timer_pool_start(p->upload_timer);
for (int n = 0; n < p->plane_count; n++) {
struct texplane *plane = &vimg->planes[n];
+ if (!plane->tex) {
+ timer_pool_stop(p->upload_timer);
+ goto error;
+ }
struct ra_tex_upload_params params = {
.tex = plane->tex,
@@ -4078,8 +4079,9 @@ void gl_video_configure_queue(struct gl_video *p, struct vo *vo)
}
static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
+ struct bstr param = bstr0(*value);
char s[20] = {0};
int r = 1;
bool tscale = bstr_equals0(name, "tscale");
@@ -4110,8 +4112,9 @@ static int validate_scaler_opt(struct mp_log *log, const m_option_t *opt,
}
static int validate_window_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
+ struct bstr param = bstr0(*value);
char s[20] = {0};
int r = 1;
if (bstr_equals0(param, "help")) {
@@ -4135,8 +4138,9 @@ static int validate_window_opt(struct mp_log *log, const m_option_t *opt,
}
static int validate_error_diffusion_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
+ struct bstr param = bstr0(*value);
char s[20] = {0};
int r = 1;
if (bstr_equals0(param, "help")) {
diff --git a/video/out/gpu/video_shaders.c b/video/out/gpu/video_shaders.c
index da48929..d39b867 100644
--- a/video/out/gpu/video_shaders.c
+++ b/video/out/gpu/video_shaders.c
@@ -354,9 +354,10 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
switch (trc) {
case MP_CSP_TRC_SRGB:
- GLSL(color.rgb = mix(color.rgb * vec3(1.0/12.92),
- pow((color.rgb + vec3(0.055))/vec3(1.055), vec3(2.4)),
- lessThan(vec3(0.04045), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(1.0/12.92), \n"
+ " pow((color.rgb + vec3(0.055))/vec3(1.055), vec3(2.4)), \n"
+ " %s(lessThan(vec3(0.04045), color.rgb))); \n",
+ gl_sc_bvec(sc, 3));
break;
case MP_CSP_TRC_BT_1886:
GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
@@ -380,9 +381,10 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSL(color.rgb = pow(color.rgb, vec3(2.8));)
break;
case MP_CSP_TRC_PRO_PHOTO:
- GLSL(color.rgb = mix(color.rgb * vec3(1.0/16.0),
- pow(color.rgb, vec3(1.8)),
- lessThan(vec3(0.03125), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(1.0/16.0), \n"
+ " pow(color.rgb, vec3(1.8)), \n"
+ " %s(lessThan(vec3(0.03125), color.rgb))); \n",
+ gl_sc_bvec(sc, 3));
break;
case MP_CSP_TRC_PQ:
GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", PQ_M2);
@@ -397,16 +399,16 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
case MP_CSP_TRC_HLG:
GLSLF("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,\n"
" exp((color.rgb - vec3(%f)) * vec3(1.0/%f)) + vec3(%f),\n"
- " lessThan(vec3(0.5), color.rgb));\n",
- HLG_C, HLG_A, HLG_B);
+ " %s(lessThan(vec3(0.5), color.rgb)));\n",
+ HLG_C, HLG_A, HLG_B, gl_sc_bvec(sc, 3));
GLSLF("color.rgb *= vec3(1.0/%f);\n", MP_REF_WHITE_HLG);
break;
case MP_CSP_TRC_V_LOG:
GLSLF("color.rgb = mix((color.rgb - vec3(0.125)) * vec3(1.0/5.6), \n"
" pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
" - vec3(%f), \n"
- " lessThanEqual(vec3(0.181), color.rgb)); \n",
- VLOG_D, VLOG_C, VLOG_B);
+ " %s(lessThanEqual(vec3(0.181), color.rgb))); \n",
+ VLOG_D, VLOG_C, VLOG_B, gl_sc_bvec(sc, 3));
break;
case MP_CSP_TRC_S_LOG1:
GLSLF("color.rgb = pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f))\n"
@@ -417,8 +419,8 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSLF("color.rgb = mix((color.rgb - vec3(%f)) * vec3(1.0/%f), \n"
" (pow(vec3(10.0), (color.rgb - vec3(%f)) * vec3(1.0/%f)) \n"
" - vec3(%f)) * vec3(1.0/%f), \n"
- " lessThanEqual(vec3(%f), color.rgb)); \n",
- SLOG_Q, SLOG_P, SLOG_C, SLOG_A, SLOG_B, SLOG_K2, SLOG_Q);
+ " %s(lessThanEqual(vec3(%f), color.rgb))); \n",
+ SLOG_Q, SLOG_P, SLOG_C, SLOG_A, SLOG_B, SLOG_K2, gl_sc_bvec(sc, 3), SLOG_Q);
break;
default:
abort();
@@ -444,10 +446,11 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
switch (trc) {
case MP_CSP_TRC_SRGB:
- GLSL(color.rgb = mix(color.rgb * vec3(12.92),
- vec3(1.055) * pow(color.rgb, vec3(1.0/2.4))
- - vec3(0.055),
- lessThanEqual(vec3(0.0031308), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(12.92), \n"
+ " vec3(1.055) * pow(color.rgb, vec3(1.0/2.4)) \n"
+ " - vec3(0.055), \n"
+ " %s(lessThanEqual(vec3(0.0031308), color.rgb))); \n",
+ gl_sc_bvec(sc, 3));
break;
case MP_CSP_TRC_BT_1886:
GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));)
@@ -471,9 +474,10 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.8));)
break;
case MP_CSP_TRC_PRO_PHOTO:
- GLSL(color.rgb = mix(color.rgb * vec3(16.0),
- pow(color.rgb, vec3(1.0/1.8)),
- lessThanEqual(vec3(0.001953), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(16.0), \n"
+ " pow(color.rgb, vec3(1.0/1.8)), \n"
+ " %s(lessThanEqual(vec3(0.001953), color.rgb))); \n",
+ gl_sc_bvec(sc, 3));
break;
case MP_CSP_TRC_PQ:
GLSLF("color.rgb *= vec3(1.0/%f);\n", 10000 / MP_REF_WHITE);
@@ -487,15 +491,15 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSLF("color.rgb *= vec3(%f);\n", MP_REF_WHITE_HLG);
GLSLF("color.rgb = mix(vec3(0.5) * sqrt(color.rgb),\n"
" vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),\n"
- " lessThan(vec3(1.0), color.rgb));\n",
- HLG_A, HLG_B, HLG_C);
+ " %s(lessThan(vec3(1.0), color.rgb)));\n",
+ HLG_A, HLG_B, HLG_C, gl_sc_bvec(sc, 3));
break;
case MP_CSP_TRC_V_LOG:
GLSLF("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125), \n"
" vec3(%f) * log(color.rgb + vec3(%f)) \n"
" + vec3(%f), \n"
- " lessThanEqual(vec3(0.01), color.rgb)); \n",
- VLOG_C / M_LN10, VLOG_B, VLOG_D);
+ " %s(lessThanEqual(vec3(0.01), color.rgb))); \n",
+ VLOG_C / M_LN10, VLOG_B, VLOG_D, gl_sc_bvec(sc, 3));
break;
case MP_CSP_TRC_S_LOG1:
GLSLF("color.rgb = vec3(%f) * log(color.rgb + vec3(%f)) + vec3(%f);\n",
@@ -505,8 +509,8 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSLF("color.rgb = mix(vec3(%f) * color.rgb + vec3(%f), \n"
" vec3(%f) * log(vec3(%f) * color.rgb + vec3(%f)) \n"
" + vec3(%f), \n"
- " lessThanEqual(vec3(0.0), color.rgb)); \n",
- SLOG_P, SLOG_Q, SLOG_A / M_LN10, SLOG_K2, SLOG_B, SLOG_C);
+ " %s(lessThanEqual(vec3(0.0), color.rgb))); \n",
+ SLOG_P, SLOG_Q, SLOG_A / M_LN10, SLOG_K2, SLOG_B, SLOG_C, gl_sc_bvec(sc, 3));
break;
default:
abort();
@@ -537,9 +541,10 @@ static void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light,
// This OOTF is defined by encoding the result as 709 and then decoding
// it as 1886; although this is called 709_1886 we actually use the
// more precise (by one decimal) values from BT.2020 instead
- GLSL(color.rgb = mix(color.rgb * vec3(4.5),
- vec3(1.0993) * pow(color.rgb, vec3(0.45)) - vec3(0.0993),
- lessThan(vec3(0.0181), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(4.5), \n"
+ " vec3(1.0993) * pow(color.rgb, vec3(0.45)) - vec3(0.0993), \n"
+ " %s(lessThan(vec3(0.0181), color.rgb))); \n",
+ gl_sc_bvec(sc, 3));
GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
break;
case MP_CSP_LIGHT_SCENE_1_2:
@@ -570,10 +575,11 @@ static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light ligh
}
case MP_CSP_LIGHT_SCENE_709_1886:
GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));)
- GLSL(color.rgb = mix(color.rgb * vec3(1.0/4.5),
- pow((color.rgb + vec3(0.0993)) * vec3(1.0/1.0993),
- vec3(1/0.45)),
- lessThan(vec3(0.08145), color.rgb));)
+ GLSLF("color.rgb = mix(color.rgb * vec3(1.0/4.5), \n"
+ " pow((color.rgb + vec3(0.0993)) * vec3(1.0/1.0993), \n"
+ " vec3(1/0.45)), \n"
+ " %s(lessThan(vec3(0.08145), color.rgb))); \n",
+ gl_sc_bvec(sc, 3));
break;
case MP_CSP_LIGHT_SCENE_1_2:
GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.2));)
@@ -718,7 +724,8 @@ static void pass_tone_map(struct gl_shader_cache *sc,
"max(1e-6, sig_peak - 1.0);\n");
GLSLF("float scale = (b*b + 2.0*b*j + j*j) / (b-a);\n");
GLSLF("sig = mix(sig, scale * (sig + vec3(a)) / (sig + vec3(b)),"
- " greaterThan(sig, vec3(j)));\n");
+ " %s(greaterThan(sig, vec3(j))));\n",
+ gl_sc_bvec(sc, 3));
GLSLF("}\n");
break;
@@ -751,7 +758,8 @@ static void pass_tone_map(struct gl_shader_cache *sc,
GLSL(float scale = pow(cutoff / sig_peak, gamma.x) / cutoff;)
GLSLF("sig = mix(scale * sig,"
" pow(sig / sig_peak, vec3(gamma)),"
- " greaterThan(sig, vec3(cutoff)));\n");
+ " %s(greaterThan(sig, vec3(cutoff))));\n",
+ gl_sc_bvec(sc, 3));
break;
}
@@ -784,7 +792,8 @@ static void pass_tone_map(struct gl_shader_cache *sc,
"vec3 pb = (2.0 * tb3 - 3.0 * tb2 + vec3(1.0)) * vec3(ks) + \n"
" (tb3 - 2.0 * tb2 + tb) * vec3(1.0 - ks) + \n"
" (-2.0 * tb3 + 3.0 * tb2) * vec3(maxLum); \n"
- "sig = mix(pb, sig_pq.rgb, lessThan(sig_pq.rgb, vec3(ks))); \n");
+ "sig = mix(pb, sig_pq.rgb, %s(lessThan(sig_pq.rgb, vec3(ks)))); \n",
+ gl_sc_bvec(sc, 3));
// Convert back from PQ space to linear light
GLSLF("sig *= vec3(sig_pq.a); \n"
"sig = pow(sig, vec3(1.0/%f)); \n"
@@ -879,7 +888,8 @@ void pass_color_map(struct gl_shader_cache *sc, bool is_linear,
float coeff = cmin / (cmin - luma);
color.rgb = mix(color.rgb, vec3(luma), coeff);
})
- GLSL(float cmax = max(max(color.r, color.g), color.b);)
+ GLSLF("float cmax = 1.0/%f * max(max(color.r, color.g), color.b);\n",
+ dst.sig_peak);
GLSL(if (cmax > 1.0) color.rgb /= cmax;)
}
}
@@ -937,7 +947,7 @@ struct deband_opts {
const struct deband_opts deband_opts_def = {
.iterations = 1,
- .threshold = 64.0,
+ .threshold = 32.0,
.range = 16.0,
.grain = 48.0,
};
@@ -990,8 +1000,8 @@ void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
// the difference is below the given threshold
GLSLF("avg = average(%f, h);\n", i * opts->range);
GLSL(diff = abs(color - avg);)
- GLSLF("color = mix(avg, color, greaterThan(diff, vec4(%f)));\n",
- opts->threshold / (i * 16384.0));
+ GLSLF("color = mix(avg, color, %s(greaterThan(diff, vec4(%f))));\n",
+ gl_sc_bvec(sc, 4), opts->threshold / (i * 16384.0));
}
// Add some random noise to smooth out residual differences
diff --git a/video/out/hwdec/hwdec_cuda_vk.c b/video/out/hwdec/hwdec_cuda_vk.c
index b8eb153..0378dd2 100644
--- a/video/out/hwdec/hwdec_cuda_vk.c
+++ b/video/out/hwdec/hwdec_cuda_vk.c
@@ -63,8 +63,6 @@ static bool cuda_ext_vk_init(struct ra_hwdec_mapper *mapper,
.d = 0,
.format = ra_pl_fmt_get(format),
.sampleable = true,
- .sample_mode = format->linear_filter ? PL_TEX_SAMPLE_LINEAR
- : PL_TEX_SAMPLE_NEAREST,
.export_handle = p_owner->handle_type,
};
diff --git a/video/out/hwdec/hwdec_vaapi.c b/video/out/hwdec/hwdec_vaapi.c
index 3ed9602..2a8e181 100644
--- a/video/out/hwdec/hwdec_vaapi.c
+++ b/video/out/hwdec/hwdec_vaapi.c
@@ -249,7 +249,7 @@ static int mapper_map(struct ra_hwdec_mapper *mapper)
CHECK_VA_STATUS(mapper, "vaSyncSurface()");
p->surface_acquired = true;
- if (!p_owner->interop_map(mapper))
+ if (!p_owner->interop_map(mapper, p_owner->probing_formats))
goto err;
if (p->desc.fourcc == VA_FOURCC_YV12)
diff --git a/video/out/hwdec/hwdec_vaapi.h b/video/out/hwdec/hwdec_vaapi.h
index 471283a..b060ba3 100644
--- a/video/out/hwdec/hwdec_vaapi.h
+++ b/video/out/hwdec/hwdec_vaapi.h
@@ -33,7 +33,7 @@ struct priv_owner {
const struct ra_imgfmt_desc *desc);
void (*interop_uninit)(const struct ra_hwdec_mapper *mapper);
- bool (*interop_map)(struct ra_hwdec_mapper *mapper);
+ bool (*interop_map)(struct ra_hwdec_mapper *mapper, bool probing);
void (*interop_unmap)(struct ra_hwdec_mapper *mapper);
};
diff --git a/video/out/hwdec/hwdec_vaapi_gl.c b/video/out/hwdec/hwdec_vaapi_gl.c
index 1617167..f29a9d9 100644
--- a/video/out/hwdec/hwdec_vaapi_gl.c
+++ b/video/out/hwdec/hwdec_vaapi_gl.c
@@ -137,7 +137,7 @@ static void vaapi_gl_mapper_uninit(const struct ra_hwdec_mapper *mapper)
p_mapper->desc.layers[n].pitch[plane]); \
} while (0)
-static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper)
+static bool vaapi_gl_map(struct ra_hwdec_mapper *mapper, bool probing)
{
struct priv *p_mapper = mapper->priv;
struct vaapi_gl_mapper_priv *p = p_mapper->interop_mapper_priv;
diff --git a/video/out/hwdec/hwdec_vaapi_vk.c b/video/out/hwdec/hwdec_vaapi_vk.c
index 1cee9e8..b6af805 100644
--- a/video/out/hwdec/hwdec_vaapi_vk.c
+++ b/video/out/hwdec/hwdec_vaapi_vk.c
@@ -21,8 +21,9 @@
#include "config.h"
#include "hwdec_vaapi.h"
#include "video/out/placebo/ra_pl.h"
+#include "video/out/placebo/utils.h"
-static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper)
+static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper, bool probing)
{
struct priv *p = mapper->priv;
const struct pl_gpu *gpu = ra_pl_get(mapper->ra);
@@ -43,14 +44,10 @@ static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper)
int fd = p->desc.objects[id].fd;
uint32_t size = p->desc.objects[id].size;
uint32_t offset = p->desc.layers[n].offset[0];
+ uint32_t pitch = p->desc.layers[n].pitch[0];
-#if PL_API_VER >= 88
// AMD drivers do not return the size in the surface description, so we
- // need to query it manually. The reason we guard this logic behind
- // PL_API_VER >= 88 is that the same drivers also require DRM format
- // modifier support in order to not produce corrupted textures, so
- // having this #ifdef merely exists to protect users from combining
- // too-new mpv with too-old libplacebo.
+ // need to query it manually.
if (size == 0) {
size = lseek(fd, 0, SEEK_END);
if (size == -1) {
@@ -65,7 +62,6 @@ static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper)
return false;
}
}
-#endif
struct pl_tex_params tex_params = {
.w = mp_image_plane_w(&p->layout, n),
@@ -73,8 +69,6 @@ static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper)
.d = 0,
.format = format->priv,
.sampleable = true,
- .sample_mode = format->linear_filter ? PL_TEX_SAMPLE_LINEAR
- : PL_TEX_SAMPLE_NEAREST,
.import_handle = PL_HANDLE_DMA_BUF,
.shared_mem = (struct pl_shared_mem) {
.handle = {
@@ -82,13 +76,14 @@ static bool vaapi_vk_map(struct ra_hwdec_mapper *mapper)
},
.size = size,
.offset = offset,
-#if PL_API_VER >= 88
.drm_format_mod = p->desc.objects[id].drm_format_modifier,
-#endif
+ .stride_w = pitch,
},
};
+ mppl_ctx_set_log(gpu->ctx, mapper->ra->log, probing);
const struct pl_tex *pltex = pl_tex_create(gpu, &tex_params);
+ mppl_ctx_set_log(gpu->ctx, mapper->ra->log, false);
if (!pltex) {
return false;
}
diff --git a/video/out/mac/common.swift b/video/out/mac/common.swift
index cb1b74b..6289c6b 100644
--- a/video/out/mac/common.swift
+++ b/video/out/mac/common.swift
@@ -100,6 +100,7 @@ class Common: NSObject {
}
window.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level))
+ window.setOnAllWorkspaces(Bool(mpv.opts.all_workspaces))
window.keepAspect = Bool(mpv.opts.keepaspect_window)
window.title = title
window.border = Bool(mpv.opts.border)
@@ -186,8 +187,7 @@ class Common: NSObject {
func startDisplayLink(_ vo: UnsafeMutablePointer<vo>) {
CVDisplayLinkCreateWithActiveCGDisplays(&link)
- guard let opts: mp_vo_opts = mpv?.opts,
- let screen = getScreenBy(id: Int(opts.screen_id)) ?? NSScreen.main,
+ guard let screen = getTargetScreen(forFullscreen: false) ?? NSScreen.main,
let link = self.link else
{
log.sendWarning("Couldn't start DisplayLink, no MPVHelper, Screen or DisplayLink available")
@@ -409,9 +409,27 @@ class Common: NSObject {
return NSScreen.screens[screenID]
}
+ func getScreenBy(name screenName: String?) -> NSScreen? {
+ for screen in NSScreen.screens {
+ if screen.displayName == screenName {
+ return screen
+ }
+ }
+ return nil
+ }
+
func getTargetScreen(forFullscreen fs: Bool) -> NSScreen? {
- let screenID = fs ? (mpv?.opts.fsscreen_id ?? 0) : (mpv?.opts.screen_id ?? 0)
- return getScreenBy(id: Int(screenID))
+ guard let mpv = mpv else {
+ log.sendWarning("Unexpected nil value in getTargetScreen")
+ return nil
+ }
+
+ let screenID = fs ? mpv.opts.fsscreen_id : mpv.opts.screen_id
+ var name: String?
+ if let screenName = fs ? mpv.opts.fsscreen_name : mpv.opts.screen_name {
+ name = String(cString: screenName)
+ }
+ return getScreenBy(id: Int(screenID)) ?? getScreenBy(name: name)
}
func getCurrentScreen() -> NSScreen? {
@@ -420,25 +438,34 @@ class Common: NSObject {
NSScreen.main
}
- func getWindowGeometry(forScreen targetScreen: NSScreen,
+ func getWindowGeometry(forScreen screen: NSScreen,
videoOut vo: UnsafeMutablePointer<vo>) -> NSRect {
- let r = targetScreen.convertRectToBacking(targetScreen.frame)
- var screenRC: mp_rect = mp_rect(x0: Int32(0),
- y0: Int32(0),
- x1: Int32(r.size.width),
- y1: Int32(r.size.height))
+ let r = screen.convertRectToBacking(screen.frame)
+ let targetFrame = (mpv?.macOpts.macos_geometry_calculation ?? Int32(FRAME_VISIBLE)) == FRAME_VISIBLE
+ ? screen.visibleFrame : screen.frame
+ let rv = screen.convertRectToBacking(targetFrame)
+
+ // convert origin to be relative to target screen
+ var originY = rv.origin.y - r.origin.y
+ let originX = rv.origin.x - r.origin.x
+ // flip the y origin, mp_rect expects the origin at the top-left
+ // macOS' windowing system operates from the bottom-left
+ originY = -(originY + rv.size.height)
+ var screenRC: mp_rect = mp_rect(x0: Int32(originX),
+ y0: Int32(originY),
+ x1: Int32(originX + rv.size.width),
+ y1: Int32(originY + rv.size.height))
var geo: vo_win_geometry = vo_win_geometry()
- vo_calc_window_geometry2(vo, &screenRC, Double(targetScreen.backingScaleFactor), &geo)
+ vo_calc_window_geometry2(vo, &screenRC, Double(screen.backingScaleFactor), &geo)
+ vo_apply_window_geometry(vo, &geo)
- // flip y coordinates
- geo.win.y1 = Int32(r.size.height) - geo.win.y1
- geo.win.y0 = Int32(r.size.height) - geo.win.y0
-
- let wr = NSMakeRect(CGFloat(geo.win.x0), CGFloat(geo.win.y1),
- CGFloat(geo.win.x1 - geo.win.x0),
- CGFloat(geo.win.y0 - geo.win.y1))
- return targetScreen.convertRectFromBacking(wr)
+ let height = CGFloat(geo.win.y1 - geo.win.y0)
+ let width = CGFloat(geo.win.x1 - geo.win.x0)
+ // flip the y origin again
+ let y = CGFloat(-geo.win.y1)
+ let x = CGFloat(geo.win.x0)
+ return screen.convertRectFromBacking(NSMakeRect(x, y, width, height))
}
func getInitProperties(_ vo: UnsafeMutablePointer<vo>) -> (MPVHelper, NSScreen, NSRect) {
@@ -446,7 +473,7 @@ class Common: NSObject {
log.sendError("Something went wrong, no MPVHelper was initialized")
exit(1)
}
- guard let targetScreen = getScreenBy(id: Int(mpv.opts.screen_id)) ?? NSScreen.main else {
+ guard let targetScreen = getTargetScreen(forFullscreen: false) ?? NSScreen.main else {
log.sendError("Something went wrong, no Screen was found")
exit(1)
}
@@ -508,42 +535,40 @@ class Common: NSObject {
events.pointee |= Int32(checkEvents())
return VO_TRUE
case VOCTRL_VO_OPTS_CHANGED:
- var o: UnsafeMutableRawPointer?
- while mpv.nextChangedOption(property: &o) {
- guard let opt = o else {
- log.sendError("No changed options was retrieved")
- return VO_TRUE
- }
- if opt == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.border) {
+ var opt: UnsafeMutableRawPointer?
+ while mpv.nextChangedOption(property: &opt) {
+ switch opt {
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.border):
DispatchQueue.main.async {
self.window?.border = Bool(mpv.opts.border)
}
- }
- if opt == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.fullscreen) {
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.fullscreen):
DispatchQueue.main.async {
self.window?.toggleFullScreen(nil)
}
- }
- if opt == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.ontop) ||
- opt == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.ontop_level) {
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.ontop): fallthrough
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.ontop_level):
DispatchQueue.main.async {
self.window?.setOnTop(Bool(mpv.opts.ontop), Int(mpv.opts.ontop_level))
}
- }
- if opt == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.keepaspect_window) {
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.all_workspaces):
+ DispatchQueue.main.async {
+ self.window?.setOnAllWorkspaces(Bool(mpv.opts.all_workspaces))
+ }
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.keepaspect_window):
DispatchQueue.main.async {
self.window?.keepAspect = Bool(mpv.opts.keepaspect_window)
}
- }
- if opt == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.window_minimized) {
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.window_minimized):
DispatchQueue.main.async {
self.window?.setMinimized(Bool(mpv.opts.window_minimized))
}
- }
- if opt == UnsafeMutableRawPointer(&mpv.optsPtr.pointee.window_maximized) {
+ case MPVHelper.getPointer(&mpv.optsPtr.pointee.window_maximized):
DispatchQueue.main.async {
self.window?.setMaximized(Bool(mpv.opts.window_maximized))
}
+ default:
+ break
}
}
return VO_TRUE
@@ -624,6 +649,17 @@ class Common: NSObject {
SWIFT_TARRAY_STRING_APPEND(nil, &array, &count, nil)
dnames.pointee = array
return VO_TRUE
+ case VOCTRL_GET_DISPLAY_RES:
+ guard let screen = getCurrentScreen() else {
+ log.sendWarning("No Screen available to retrieve frame")
+ return VO_NOTAVAIL
+ }
+ let sizeData = data.assumingMemoryBound(to: Int32.self)
+ let size = UnsafeMutableBufferPointer(start: sizeData, count: 2)
+ let frame = screen.convertRectToBacking(screen.frame)
+ size[0] = Int32(frame.size.width)
+ size[1] = Int32(frame.size.height)
+ return VO_TRUE
case VOCTRL_GET_FOCUSED:
let focus = data.assumingMemoryBound(to: CBool.self)
focus.pointee = NSApp.isActive
@@ -653,19 +689,14 @@ class Common: NSObject {
return
}
- var o: UnsafeMutableRawPointer?
- while mpv.nextChangedMacOption(property: &o) {
- guard let opt = o else {
- log.sendWarning("Could not retrieve changed mac option")
- return
- }
-
+ var opt: UnsafeMutableRawPointer?
+ while mpv.nextChangedMacOption(property: &opt) {
switch opt {
- case UnsafeMutableRawPointer(&mpv.macOptsPtr.pointee.macos_title_bar_appearance):
+ case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_appearance):
titleBar?.set(appearance: Int(mpv.macOpts.macos_title_bar_appearance))
- case UnsafeMutableRawPointer(&mpv.macOptsPtr.pointee.macos_title_bar_material):
+ case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_material):
titleBar?.set(material: Int(mpv.macOpts.macos_title_bar_material))
- case UnsafeMutableRawPointer(&mpv.macOptsPtr.pointee.macos_title_bar_color):
+ case MPVHelper.getPointer(&mpv.macOptsPtr.pointee.macos_title_bar_color):
titleBar?.set(color: mpv.macOpts.macos_title_bar_color)
default:
break
diff --git a/video/out/mac/title_bar.swift b/video/out/mac/title_bar.swift
index 623fe8f..e49c2bb 100644
--- a/video/out/mac/title_bar.swift
+++ b/video/out/mac/title_bar.swift
@@ -151,7 +151,7 @@ class TitleBar: NSVisualEffectView {
}
}
- @objc func hide() {
+ @objc func hide(_ duration: TimeInterval = 0.20) {
guard let window = common.window else { return }
if window.isInFullscreen && !window.isAnimating {
alphaValue = 0
@@ -159,7 +159,7 @@ class TitleBar: NSVisualEffectView {
return
}
NSAnimationContext.runAnimationGroup({ (context) -> Void in
- context.duration = 0.20
+ context.duration = duration
systemBar?.animator().alphaValue = 0
animator().alphaValue = 0
}, completionHandler: {
diff --git a/video/out/mac/window.swift b/video/out/mac/window.swift
index d692d0d..8b6c5f0 100644
--- a/video/out/mac/window.swift
+++ b/video/out/mac/window.swift
@@ -30,7 +30,7 @@ class Window: NSWindow, NSWindowDelegate {
var isInFullscreen: Bool = false
var isAnimating: Bool = false
var isMoving: Bool = false
- var forceTargetScreen: Bool = false
+ var previousStyleMask: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
var unfsContentFramePixel: NSRect { get { return convertToBacking(unfsContentFrame ?? NSRect(x: 0, y: 0, width: 160, height: 90)) } }
var framePixel: NSRect { get { return convertToBacking(frame) } }
@@ -61,6 +61,7 @@ class Window: NSWindow, NSWindowDelegate {
set {
let responder = firstResponder
let windowTitle = title
+ previousStyleMask = super.styleMask
super.styleMask = newValue
makeFirstResponder(responder)
title = windowTitle
@@ -163,22 +164,26 @@ class Window: NSWindow, NSWindowDelegate {
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = getFsAnimationDuration(duration - 0.05)
window.animator().setFrame(tScreen.frame, display: true)
- }, completionHandler: { })
+ }, completionHandler: nil)
}
func window(_ window: NSWindow, startCustomAnimationToExitFullScreenWithDuration duration: TimeInterval) {
guard let tScreen = targetScreen, let currentScreen = screen else { return }
let newFrame = calculateWindowPosition(for: tScreen, withoutBounds: tScreen == screen)
let intermediateFrame = aspectFit(rect: newFrame, in: currentScreen.frame)
- common.view?.layerContentsPlacement = .scaleProportionallyToFill
- common.titleBar?.hide()
- styleMask.remove(.fullScreen)
- setFrame(intermediateFrame, display: true)
+ common.titleBar?.hide(0.0)
NSAnimationContext.runAnimationGroup({ (context) -> Void in
- context.duration = getFsAnimationDuration(duration - 0.05)
- window.animator().setFrame(newFrame, display: true)
- }, completionHandler: { })
+ context.duration = 0.0
+ common.view?.layerContentsPlacement = .scaleProportionallyToFill
+ window.animator().setFrame(intermediateFrame, display: true)
+ }, completionHandler: {
+ NSAnimationContext.runAnimationGroup({ (context) -> Void in
+ context.duration = self.getFsAnimationDuration(duration - 0.05)
+ self.styleMask.remove(.fullScreen)
+ window.animator().setFrame(newFrame, display: true)
+ }, completionHandler: nil)
+ })
}
func windowDidEnterFullScreen(_ notification: Notification) {
@@ -225,7 +230,14 @@ class Window: NSWindow, NSWindowDelegate {
func setToFullScreen() {
guard let targetFrame = targetScreen?.frame else { return }
- styleMask.insert(.fullScreen)
+
+ if #available(macOS 11.0, *) {
+ styleMask = .borderless
+ common.titleBar?.hide(0.0)
+ } else {
+ styleMask.insert(.fullScreen)
+ }
+
NSApp.presentationOptions = [.autoHideMenuBar, .autoHideDock]
setFrame(targetFrame, display: true)
endAnimation()
@@ -236,10 +248,17 @@ class Window: NSWindow, NSWindowDelegate {
func setToWindow() {
guard let tScreen = targetScreen else { return }
+
+ if #available(macOS 11.0, *) {
+ styleMask = previousStyleMask
+ common.titleBar?.hide(0.0)
+ } else {
+ styleMask.remove(.fullScreen)
+ }
+
let newFrame = calculateWindowPosition(for: tScreen, withoutBounds: targetScreen == screen)
NSApp.presentationOptions = []
setFrame(newFrame, display: true)
- styleMask.remove(.fullScreen)
endAnimation()
isInFullscreen = false
mpv?.setOption(fullscreen: isInFullscreen)
@@ -274,6 +293,14 @@ class Window: NSWindow, NSWindowDelegate {
}
}
+ func setOnAllWorkspaces(_ state: Bool) {
+ if state {
+ collectionBehavior.insert(.canJoinAllSpaces)
+ } else {
+ collectionBehavior.remove(.canJoinAllSpaces)
+ }
+ }
+
func setMinimized(_ stateWanted: Bool) {
if isMiniaturized == stateWanted { return }
diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c
index 8fcc94d..1a5efe0 100644
--- a/video/out/opengl/common.c
+++ b/video/out/opengl/common.c
@@ -486,7 +486,6 @@ static const struct gl_functions gl_functions[] = {
#undef DEF_FN
#undef DEF_FN_NAME
-
// Fill the GL struct with function pointers and extensions from the current
// GL context. Called by the backend.
// get_fn: function to resolve function names
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index c491039..59a7eda 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -21,15 +21,9 @@
#include "utils.h"
// 0-terminated list of desktop GL versions a backend should try to
-// initialize. The first entry is the most preferred version.
-const int mpgl_preferred_gl_versions[] = {
- 440,
- 430,
- 400,
- 330,
+// initialize. Each entry is the minimum required version.
+const int mpgl_min_required_gl_versions[] = {
320,
- 310,
- 300,
210,
0
};
@@ -40,19 +34,12 @@ enum {
FLUSH_AUTO,
};
-enum {
- GLES_AUTO = 0,
- GLES_YES,
- GLES_NO,
-};
-
struct opengl_opts {
int use_glfinish;
int waitvsync;
int vsync_pattern[2];
int swapinterval;
int early_flush;
- int restrict_version;
int gles_mode;
};
@@ -64,7 +51,7 @@ const struct m_sub_options opengl_conf = {
{"opengl-swapinterval", OPT_INT(swapinterval)},
{"opengl-check-pattern-a", OPT_INT(vsync_pattern[0])},
{"opengl-check-pattern-b", OPT_INT(vsync_pattern[1])},
- {"opengl-restrict", OPT_INT(restrict_version)},
+ {"opengl-restrict", OPT_REMOVED(NULL)},
{"opengl-es", OPT_CHOICE(gles_mode,
{"auto", GLES_AUTO}, {"yes", GLES_YES}, {"no", GLES_NO})},
{"opengl-early-flush", OPT_CHOICE(early_flush,
@@ -100,29 +87,17 @@ struct priv {
int num_vsync_fences;
};
-bool ra_gl_ctx_test_version(struct ra_ctx *ctx, int version, bool es)
+enum gles_mode ra_gl_ctx_get_glesmode(struct ra_ctx *ctx)
{
- bool ret;
- struct opengl_opts *opts;
void *tmp = talloc_new(NULL);
- opts = mp_get_config_group(tmp, ctx->global, &opengl_conf);
-
- // Version too high
- if (opts->restrict_version && version >= opts->restrict_version) {
- ret = false;
- goto done;
- }
+ struct opengl_opts *opts;
+ enum gles_mode mode;
- switch (opts->gles_mode) {
- case GLES_YES: ret = es; goto done;
- case GLES_NO: ret = !es; goto done;
- case GLES_AUTO: ret = true; goto done;
- default: abort();
- }
+ opts = mp_get_config_group(tmp, ctx->global, &opengl_conf);
+ mode = opts->gles_mode;
-done:
talloc_free(tmp);
- return ret;
+ return mode;
}
void ra_gl_ctx_uninit(struct ra_ctx *ctx)
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
index 2dd2517..222661a 100644
--- a/video/out/opengl/context.h
+++ b/video/out/opengl/context.h
@@ -4,12 +4,16 @@
#include "video/out/gpu/context.h"
#include "common.h"
-extern const int mpgl_preferred_gl_versions[];
+extern const int mpgl_min_required_gl_versions[];
-// Returns whether or not a candidate GL version should be accepted or not
-// (based on the --opengl opts). Implementations may call this before
-// ra_gl_ctx_init if they wish to probe for multiple possible GL versions.
-bool ra_gl_ctx_test_version(struct ra_ctx *ctx, int version, bool es);
+enum gles_mode {
+ GLES_AUTO = 0,
+ GLES_YES,
+ GLES_NO,
+};
+
+// Returns the gles mode based on the --opengl opts.
+enum gles_mode ra_gl_ctx_get_glesmode(struct ra_ctx *ctx);
// These are a set of helpers for ra_ctx providers based on ra_gl.
// The init function also initializes ctx->ra and ctx->swapchain, so the user
diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c
index 86ce571..99cc611 100644
--- a/video/out/opengl/context_drm_egl.c
+++ b/video/out/opengl/context_drm_egl.c
@@ -44,8 +44,6 @@
#define EGL_PLATFORM_GBM_KHR 0x31D7
#endif
-#define USE_MASTER 0
-
#ifndef EGL_EXT_platform_base
typedef EGLDisplay (EGLAPIENTRYP PFNEGLGETPLATFORMDISPLAYEXTPROC)
(EGLenum platform, void *native_display, const EGLint *attrib_list);
@@ -423,15 +421,11 @@ static void release_vt(void *data)
struct ra_ctx *ctx = data;
MP_VERBOSE(ctx->vo, "Releasing VT\n");
crtc_release(ctx);
- if (USE_MASTER) {
- //this function enables support for switching to x, weston etc.
- //however, for whatever reason, it can be called only by root users.
- //until things change, this is commented.
- struct priv *p = ctx->priv;
- if (drmDropMaster(p->kms->fd)) {
- MP_WARN(ctx->vo, "Failed to drop DRM master: %s\n",
- mp_strerror(errno));
- }
+
+ const struct priv *p = ctx->priv;
+ if (drmDropMaster(p->kms->fd)) {
+ MP_WARN(ctx->vo, "Failed to drop DRM master: %s\n",
+ mp_strerror(errno));
}
}
@@ -439,12 +433,11 @@ static void acquire_vt(void *data)
{
struct ra_ctx *ctx = data;
MP_VERBOSE(ctx->vo, "Acquiring VT\n");
- if (USE_MASTER) {
- struct priv *p = ctx->priv;
- if (drmSetMaster(p->kms->fd)) {
- MP_WARN(ctx->vo, "Failed to acquire DRM master: %s\n",
- mp_strerror(errno));
- }
+
+ const struct priv *p = ctx->priv;
+ if (drmSetMaster(p->kms->fd)) {
+ MP_WARN(ctx->vo, "Failed to acquire DRM master: %s\n",
+ mp_strerror(errno));
}
crtc_setup(ctx);
@@ -726,11 +719,6 @@ static void drm_egl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
static bool drm_egl_init(struct ra_ctx *ctx)
{
- if (ctx->opts.probing) {
- MP_VERBOSE(ctx, "DRM EGL backend can be activated only manually.\n");
- return false;
- }
-
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
p->ev.version = DRM_EVENT_CONTEXT_VERSION;
p->ev.page_flip_handler = &drm_pflip_cb;
@@ -744,7 +732,9 @@ static bool drm_egl_init(struct ra_ctx *ctx)
}
MP_VERBOSE(ctx, "Initializing KMS\n");
- p->kms = kms_create(ctx->log, ctx->vo->opts->drm_opts->drm_connector_spec,
+ p->kms = kms_create(ctx->log,
+ ctx->vo->opts->drm_opts->drm_device_path,
+ ctx->vo->opts->drm_opts->drm_connector_spec,
ctx->vo->opts->drm_opts->drm_mode_spec,
ctx->vo->opts->drm_opts->drm_draw_plane,
ctx->vo->opts->drm_opts->drm_drmprime_video_plane,
@@ -880,6 +870,11 @@ static int drm_egl_control(struct ra_ctx *ctx, int *events, int request,
*(double*)arg = fps;
return VO_TRUE;
}
+ case VOCTRL_GET_DISPLAY_RES: {
+ ((int *)arg)[0] = p->kms->mode.mode.hdisplay;
+ ((int *)arg)[1] = p->kms->mode.mode.vdisplay;
+ return VO_TRUE;
+ }
case VOCTRL_PAUSE:
ctx->vo->want_redraw = true;
p->paused = true;
diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c
index 4d9d993..1accd36 100644
--- a/video/out/opengl/context_glx.c
+++ b/video/out/opengl/context_glx.c
@@ -69,65 +69,28 @@ static void glx_uninit(struct ra_ctx *ctx)
vo_x11_uninit(ctx->vo);
}
-static bool create_context_x11_old(struct ra_ctx *ctx, GL *gl)
-{
- struct priv *p = ctx->priv;
- Display *display = ctx->vo->x11->display;
- struct vo *vo = ctx->vo;
-
- if (p->context)
- return true;
-
- if (!p->vinfo) {
- MP_FATAL(vo, "Can't create a legacy GLX context without X visual\n");
- return false;
- }
-
- GLXContext new_context = glXCreateContext(display, p->vinfo, NULL, True);
- if (!new_context) {
- MP_FATAL(vo, "Could not create GLX context!\n");
- return false;
- }
-
- if (!glXMakeCurrent(display, ctx->vo->x11->window, new_context)) {
- MP_FATAL(vo, "Could not set GLX context!\n");
- glXDestroyContext(display, new_context);
- return false;
- }
-
- const char *glxstr = glXQueryExtensionsString(display, ctx->vo->x11->screen);
-
- mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
-
- p->context = new_context;
-
- return true;
-}
-
typedef GLXContext (*glXCreateContextAttribsARBProc)
(Display*, GLXFBConfig, GLXContext, Bool, const int*);
-static bool create_context_x11_gl3(struct ra_ctx *ctx, GL *gl, int gl_version,
- bool es)
+static bool create_context_x11(struct ra_ctx *ctx, GL *gl, bool es)
{
struct priv *p = ctx->priv;
struct vo *vo = ctx->vo;
- if (p->context)
- return true;
-
- if (!ra_gl_ctx_test_version(ctx, gl_version, es))
- return false;
-
glXCreateContextAttribsARBProc glXCreateContextAttribsARB =
(glXCreateContextAttribsARBProc)
glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB");
const char *glxstr =
glXQueryExtensionsString(vo->x11->display, vo->x11->screen);
- bool have_ctx_ext = glxstr && !!strstr(glxstr, "GLX_ARB_create_context");
+ if (!glxstr) {
+ MP_ERR(ctx, "GLX did not advertise any extensions\n");
+ return false;
+ }
- if (!(have_ctx_ext && glXCreateContextAttribsARB)) {
+ if (!strstr(glxstr, "GLX_ARB_create_context") ||
+ !glXCreateContextAttribsARB) {
+ MP_ERR(ctx, "GLX does not support GLX_ARB_create_context\n");
return false;
}
@@ -136,22 +99,48 @@ static bool create_context_x11_gl3(struct ra_ctx *ctx, GL *gl, int gl_version,
if (es) {
profile_mask = GLX_CONTEXT_ES2_PROFILE_BIT_EXT;
- if (!(glxstr && strstr(glxstr, "GLX_EXT_create_context_es2_profile")))
+ if (!strstr(glxstr, "GLX_EXT_create_context_es2_profile"))
return false;
}
int context_attribs[] = {
- GLX_CONTEXT_MAJOR_VERSION_ARB, MPGL_VER_GET_MAJOR(gl_version),
- GLX_CONTEXT_MINOR_VERSION_ARB, MPGL_VER_GET_MINOR(gl_version),
+ GLX_CONTEXT_MAJOR_VERSION_ARB, 0,
+ GLX_CONTEXT_MINOR_VERSION_ARB, 0,
GLX_CONTEXT_PROFILE_MASK_ARB, profile_mask,
GLX_CONTEXT_FLAGS_ARB, ctx_flags,
None
};
- vo_x11_silence_xlib(1);
- GLXContext context = glXCreateContextAttribsARB(vo->x11->display,
- p->fbc, 0, True,
- context_attribs);
- vo_x11_silence_xlib(-1);
+
+ GLXContext context;
+
+ if (!es) {
+ for (int n = 0; mpgl_min_required_gl_versions[n]; n++) {
+ int version = mpgl_min_required_gl_versions[n];
+ MP_VERBOSE(ctx, "Creating OpenGL %d.%d context...\n",
+ MPGL_VER_P(version));
+
+ context_attribs[1] = MPGL_VER_GET_MAJOR(version);
+ context_attribs[3] = MPGL_VER_GET_MINOR(version);
+
+ vo_x11_silence_xlib(1);
+ context = glXCreateContextAttribsARB(vo->x11->display,
+ p->fbc, 0, True,
+ context_attribs);
+ vo_x11_silence_xlib(-1);
+
+ if (context)
+ break;
+ }
+ } else {
+ context_attribs[1] = 2;
+
+ vo_x11_silence_xlib(1);
+ context = glXCreateContextAttribsARB(vo->x11->display,
+ p->fbc, 0, True,
+ context_attribs);
+ vo_x11_silence_xlib(-1);
+ }
+
if (!context)
return false;
@@ -312,20 +301,12 @@ static bool glx_init(struct ra_ctx *ctx)
goto uninit;
bool success = false;
- for (int n = 0; mpgl_preferred_gl_versions[n]; n++) {
- int version = mpgl_preferred_gl_versions[n];
- MP_VERBOSE(ctx, "Creating OpenGL %d.%d context...\n",
- MPGL_VER_P(version));
- if (version >= 300) {
- success = create_context_x11_gl3(ctx, gl, version, false);
- } else {
- success = create_context_x11_old(ctx, gl);
- }
- if (success)
- break;
- }
- if (!success) // try again for GLES
- success = create_context_x11_gl3(ctx, gl, 200, true);
+ enum gles_mode mode = ra_gl_ctx_get_glesmode(ctx);
+
+ if (mode == GLES_NO || mode == GLES_AUTO)
+ success = create_context_x11(ctx, gl, false);
+ if (!success && (mode == GLES_YES || mode == GLES_AUTO))
+ success = create_context_x11(ctx, gl, true);
if (success && !glXIsDirect(vo->x11->display, p->context))
gl->mpgl_caps |= MPGL_CAP_SW;
if (!success)
diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c
index 9b50dda..b402ca9 100644
--- a/video/out/opengl/context_wayland.c
+++ b/video/out/opengl/context_wayland.c
@@ -25,9 +25,6 @@
#include "egl_helpers.h"
#include "utils.h"
-// Generated from presentation-time.xml
-#include "generated/wayland/presentation-time.h"
-
#define EGL_PLATFORM_WAYLAND_EXT 0x31D8
struct priv {
@@ -39,79 +36,6 @@ struct priv {
struct wl_egl_window *egl_window;
};
-static const struct wp_presentation_feedback_listener feedback_listener;
-
-static void feedback_sync_output(void *data, struct wp_presentation_feedback *fback,
- struct wl_output *output)
-{
-}
-
-static void feedback_presented(void *data, struct wp_presentation_feedback *fback,
- uint32_t tv_sec_hi, uint32_t tv_sec_lo,
- uint32_t tv_nsec, uint32_t refresh_nsec,
- uint32_t seq_hi, uint32_t seq_lo,
- uint32_t flags)
-{
- struct vo_wayland_state *wl = data;
- vo_wayland_sync_shift(wl);
-
- if (fback)
- wp_presentation_feedback_destroy(fback);
-
- // Very similar to oml_sync_control, in this case we assume that every
- // time the compositor receives feedback, a buffer swap has been already
- // been performed.
- //
- // Notes:
- // - tv_sec_lo + tv_sec_hi is the equivalent of oml's ust
- // - seq_lo + seq_hi is the equivalent of oml's msc
- // - these values are updated everytime the compositor receives feedback.
-
- int index = last_available_sync(wl);
- if (index < 0) {
- queue_new_sync(wl);
- index = 0;
- }
- int64_t sec = (uint64_t) tv_sec_lo + ((uint64_t) tv_sec_hi << 32);
- wl->sync[index].ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000;
- wl->sync[index].msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32);
- wl->sync[index].filled = true;
-}
-
-static void feedback_discarded(void *data, struct wp_presentation_feedback *fback)
-{
-}
-
-static const struct wp_presentation_feedback_listener feedback_listener = {
- feedback_sync_output,
- feedback_presented,
- feedback_discarded,
-};
-
-static const struct wl_callback_listener frame_listener;
-
-static void frame_callback(void *data, struct wl_callback *callback, uint32_t time)
-{
- struct vo_wayland_state *wl = data;
-
- if (callback)
- wl_callback_destroy(callback);
-
- wl->frame_callback = wl_surface_frame(wl->surface);
- wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
-
- if (wl->presentation) {
- wl->feedback = wp_presentation_feedback(wl->presentation, wl->surface);
- wp_presentation_feedback_add_listener(wl->feedback, &feedback_listener, wl);
- }
-
- wl->frame_wait = false;
-}
-
-static const struct wl_callback_listener frame_listener = {
- frame_callback,
-};
-
static void resize(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@@ -123,8 +47,6 @@ static void resize(struct ra_ctx *ctx)
const int32_t height = wl->scaling * mp_rect_h(wl->geometry);
vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha);
- wl_surface_set_buffer_scale(wl->surface, wl->scaling);
-
if (p->egl_window)
wl_egl_window_resize(p->egl_window, width, height, 0, 0);
@@ -136,14 +58,8 @@ static bool wayland_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_
{
struct ra_ctx *ctx = sw->ctx;
struct vo_wayland_state *wl = ctx->vo->wl;
-
bool render = !wl->hidden || wl->opts->disable_vsync;
-
- if (wl->frame_wait && wl->presentation)
- vo_wayland_sync_clear(wl);
-
- if (render)
- wl->frame_wait = true;
+ wl->frame_wait = true;
return render ? ra_gl_ctx_start_frame(sw, out_fbo) : false;
}
@@ -160,12 +76,12 @@ static void wayland_egl_swap_buffers(struct ra_swapchain *sw)
vo_wayland_wait_frame(wl);
if (wl->presentation)
- wayland_sync_swap(wl);
+ vo_wayland_sync_swap(wl);
}
static const struct ra_swapchain_fns wayland_egl_swapchain = {
- .start_frame = wayland_egl_start_frame,
- .swap_buffers = wayland_egl_swap_buffers,
+ .start_frame = wayland_egl_start_frame,
+ .swap_buffers = wayland_egl_swap_buffers,
};
static void wayland_egl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
@@ -209,9 +125,6 @@ static bool egl_create_context(struct ra_ctx *ctx)
ra_add_native_resource(ctx->ra, "wl", wl->display);
- wl->frame_callback = wl_surface_frame(wl->surface);
- wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
-
return true;
}
diff --git a/video/out/opengl/egl_helpers.c b/video/out/opengl/egl_helpers.c
index ad026ba..0766be3 100644
--- a/video/out/opengl/egl_helpers.c
+++ b/video/out/opengl/egl_helpers.c
@@ -45,6 +45,15 @@
typedef intptr_t EGLAttrib;
#endif
+// Not every EGL provider (like RPI) has these.
+#ifndef EGL_CONTEXT_FLAGS_KHR
+#define EGL_CONTEXT_FLAGS_KHR EGL_NONE
+#endif
+
+#ifndef EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR
+#define EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR 0
+#endif
+
struct mp_egl_config_attr {
int attrib;
const char *name;
@@ -78,9 +87,22 @@ static void dump_egl_config(struct mp_log *log, int msgl, EGLDisplay display,
}
}
-// es_version: 0 (core), 2 or 3
+static void *mpegl_get_proc_address(void *ctx, const char *name)
+{
+ void *p = eglGetProcAddress(name);
+#if defined(__GLIBC__) && HAVE_LIBDL
+ // Some crappy ARM/Linux things do not provide EGL 1.5, so above call does
+ // not necessarily return function pointers for core functions. Try to get
+ // them from a loaded GLES lib. As POSIX leaves RTLD_DEFAULT "reserved",
+ // use it only with glibc.
+ if (!p)
+ p = dlsym(RTLD_DEFAULT, name);
+#endif
+ return p;
+}
+
static bool create_context(struct ra_ctx *ctx, EGLDisplay display,
- int es_version, struct mpegl_cb cb,
+ bool es, struct mpegl_cb cb,
EGLContext *out_context, EGLConfig *out_config)
{
int msgl = ctx->opts.probing ? MSGL_V : MSGL_FATAL;
@@ -89,23 +111,14 @@ static bool create_context(struct ra_ctx *ctx, EGLDisplay display,
EGLint rend;
const char *name;
- switch (es_version) {
- case 0:
+ if (!es) {
api = EGL_OPENGL_API;
rend = EGL_OPENGL_BIT;
name = "Desktop OpenGL";
- break;
- case 2:
- api = EGL_OPENGL_ES_API;
- rend = EGL_OPENGL_ES2_BIT;
- name = "GLES 2.x";
- break;
- case 3:
+ } else {
api = EGL_OPENGL_ES_API;
- rend = EGL_OPENGL_ES3_BIT;
- name = "GLES 3.x";
- break;
- default: abort();
+ rend = EGL_OPENGL_ES2_BIT | EGL_OPENGL_ES3_BIT;
+ name = "GLES 2.x +";
}
MP_VERBOSE(ctx, "Trying to create %s context.\n", name);
@@ -157,29 +170,19 @@ static bool create_context(struct ra_ctx *ctx, EGLDisplay display,
MP_DBG(ctx, "Chosen EGLConfig:\n");
dump_egl_config(ctx->log, MSGL_DEBUG, display, config);
+ int ctx_flags = ctx->opts.debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0;
EGLContext *egl_ctx = NULL;
- if (es_version) {
- if (!ra_gl_ctx_test_version(ctx, MPGL_VER(es_version, 0), true))
- return false;
-
- EGLint attrs[] = {
- EGL_CONTEXT_CLIENT_VERSION, es_version,
- EGL_NONE
- };
-
- egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
- } else {
- for (int n = 0; mpgl_preferred_gl_versions[n]; n++) {
- int ver = mpgl_preferred_gl_versions[n];
- if (!ra_gl_ctx_test_version(ctx, ver, false))
- continue;
+ if (!es) {
+ for (int n = 0; mpgl_min_required_gl_versions[n]; n++) {
+ int ver = mpgl_min_required_gl_versions[n];
EGLint attrs[] = {
EGL_CONTEXT_MAJOR_VERSION, MPGL_VER_GET_MAJOR(ver),
EGL_CONTEXT_MINOR_VERSION, MPGL_VER_GET_MINOR(ver),
EGL_CONTEXT_OPENGL_PROFILE_MASK,
ver >= 320 ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT : 0,
+ EGL_CONTEXT_FLAGS_KHR, ctx_flags,
EGL_NONE
};
@@ -187,13 +190,17 @@ static bool create_context(struct ra_ctx *ctx, EGLDisplay display,
if (egl_ctx)
break;
}
+ }
+ if (!egl_ctx) {
+ // Fallback for EGL 1.4 without EGL_KHR_create_context or GLES
+ // Add the context flags only for GLES - GL has been attempted above
+ EGLint attrs[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ es ? EGL_CONTEXT_FLAGS_KHR : EGL_NONE, ctx_flags,
+ EGL_NONE
+ };
- if (!egl_ctx && ra_gl_ctx_test_version(ctx, 140, false)) {
- // Fallback for EGL 1.4 without EGL_KHR_create_context.
- EGLint attrs[] = { EGL_NONE };
-
- egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
- }
+ egl_ctx = eglCreateContext(display, config, EGL_NO_CONTEXT, attrs);
}
if (!egl_ctx) {
@@ -233,11 +240,15 @@ bool mpegl_create_context_cb(struct ra_ctx *ctx, EGLDisplay display,
MP_VERBOSE(ctx, "EGL_VERSION=%s\nEGL_VENDOR=%s\nEGL_CLIENT_APIS=%s\n",
STR_OR_ERR(version), STR_OR_ERR(vendor), STR_OR_ERR(apis));
- int es[] = {0, 3, 2}; // preference order
- for (int i = 0; i < MP_ARRAY_SIZE(es); i++) {
- if (create_context(ctx, display, es[i], cb, out_context, out_config))
- return true;
- }
+ enum gles_mode mode = ra_gl_ctx_get_glesmode(ctx);
+
+ if ((mode == GLES_NO || mode == GLES_AUTO) &&
+ create_context(ctx, display, false, cb, out_context, out_config))
+ return true;
+
+ if ((mode == GLES_YES || mode == GLES_AUTO) &&
+ create_context(ctx, display, true, cb, out_context, out_config))
+ return true;
int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
MP_MSG(ctx, msgl, "Could not create a GL context.\n");
@@ -252,20 +263,6 @@ static int GLAPIENTRY swap_interval(int interval)
return !eglSwapInterval(display, interval);
}
-static void *mpegl_get_proc_address(void *ctx, const char *name)
-{
- void *p = eglGetProcAddress(name);
-#if defined(__GLIBC__) && HAVE_LIBDL
- // Some crappy ARM/Linux things do not provide EGL 1.5, so above call does
- // not necessarily return function pointers for core functions. Try to get
- // them from a loaded GLES lib. As POSIX leaves RTLD_DEFAULT "reserved",
- // use it only with glibc.
- if (!p)
- p = dlsym(RTLD_DEFAULT, name);
-#endif
- return p;
-}
-
// Load gl version and function pointers into *gl.
// Expects a current EGL context set.
void mpegl_load_functions(struct GL *gl, struct mp_log *log)
diff --git a/video/out/placebo/ra_pl.c b/video/out/placebo/ra_pl.c
index f8df590..ccff9df 100644
--- a/video/out/placebo/ra_pl.c
+++ b/video/out/placebo/ra_pl.c
@@ -22,9 +22,7 @@ const struct pl_gpu *ra_pl_get(const struct ra *ra)
return ra->fns == &ra_fns_pl ? get_gpu(ra) : NULL;
}
-#if PL_API_VER >= 60
static struct pl_timer *get_active_timer(const struct ra *ra);
-#endif
struct ra *ra_create_pl(const struct pl_gpu *gpu, struct mp_log *log)
{
@@ -144,8 +142,9 @@ bool mppl_wrap_tex(struct ra *ra, const struct pl_tex *pltex,
.blit_dst = pltex->params.blit_dst,
.host_mutable = pltex->params.host_writable,
.downloadable = pltex->params.host_readable,
- .src_linear = pltex->params.sample_mode == PL_TEX_SAMPLE_LINEAR,
- .src_repeat = pltex->params.address_mode == PL_TEX_ADDRESS_REPEAT,
+ // These don't exist upstream, so just pick something reasonable
+ .src_linear = pltex->params.format->caps & PL_FMT_CAP_LINEAR,
+ .src_repeat = false,
},
.priv = (void *) pltex,
};
@@ -157,32 +156,6 @@ static struct ra_tex *tex_create_pl(struct ra *ra,
const struct ra_tex_params *params)
{
const struct pl_gpu *gpu = get_gpu(ra);
-
- // Check size limits
- bool ok = false;
- switch (params->dimensions) {
- case 1:
- ok = params->w <= gpu->limits.max_tex_1d_dim;
- break;
-
- case 2:
- ok = params->w <= gpu->limits.max_tex_2d_dim &&
- params->h <= gpu->limits.max_tex_2d_dim;
- break;
-
- case 3:
- ok = params->w <= gpu->limits.max_tex_2d_dim &&
- params->h <= gpu->limits.max_tex_2d_dim &&
- params->d <= gpu->limits.max_tex_2d_dim;
- break;
- };
-
- if (!ok) {
- MP_ERR(ra, "Texture size %dx%dx%d exceeds dimension limits!\n",
- params->w, params->h, params->d);
- return NULL;
- }
-
const struct pl_tex *pltex = pl_tex_create(gpu, &(struct pl_tex_params) {
.w = params->w,
.h = params->dimensions >= 2 ? params->h : 0,
@@ -195,10 +168,6 @@ static struct ra_tex *tex_create_pl(struct ra *ra,
.blit_dst = params->blit_dst || params->render_dst,
.host_writable = params->host_mutable,
.host_readable = params->downloadable,
- .sample_mode = params->src_linear ? PL_TEX_SAMPLE_LINEAR
- : PL_TEX_SAMPLE_NEAREST,
- .address_mode = params->src_repeat ? PL_TEX_ADDRESS_REPEAT
- : PL_TEX_ADDRESS_CLAMP,
.initial_data = params->initial_data,
});
@@ -209,6 +178,10 @@ static struct ra_tex *tex_create_pl(struct ra *ra,
return NULL;
}
+ // Keep track of these, so we can correctly bind them later
+ ratex->params.src_repeat = params->src_repeat;
+ ratex->params.src_linear = params->src_linear;
+
return ratex;
}
@@ -230,30 +203,31 @@ static bool tex_upload_pl(struct ra *ra, const struct ra_tex_upload_params *para
.buf = params->buf ? params->buf->priv : NULL,
.buf_offset = params->buf_offset,
.ptr = (void *) params->src,
-#if PL_API_VER >= 60
.timer = get_active_timer(ra),
-#endif
};
const struct pl_buf *staging = NULL;
-
if (params->tex->params.dimensions == 2) {
- size_t texel_size = tex->params.format->texel_size;
- pl_params.stride_w = params->stride / texel_size;
- size_t stride = pl_params.stride_w * texel_size;
- int lines = tex->params.h;
if (params->rc) {
pl_params.rc = (struct pl_rect3d) {
.x0 = params->rc->x0, .x1 = params->rc->x1,
.y0 = params->rc->y0, .y1 = params->rc->y1,
};
- lines = pl_rect_h(pl_params.rc);
}
+#if PL_API_VER >= 168
+ pl_params.row_pitch = params->stride;
+#else
+ // Older libplacebo uses texel-sized strides, so we have to manually
+ // compensate for possibly misaligned sources (typically rgb24).
+ size_t texel_size = tex->params.format->texel_size;
+ pl_params.stride_w = params->stride / texel_size;
+ size_t stride = pl_params.stride_w * texel_size;
+
if (stride != params->stride) {
// Fall back to uploading via a staging buffer prepared in CPU
+ int lines = params->rc ? pl_rect_h(pl_params.rc) : tex->params.h;
staging = pl_buf_create(gpu, &(struct pl_buf_params) {
- .type = PL_BUF_TEX_TRANSFER,
.size = lines * stride,
.memory_type = PL_BUF_MEM_HOST,
.host_mapped = true,
@@ -270,6 +244,7 @@ static bool tex_upload_pl(struct ra *ra, const struct ra_tex_upload_params *para
pl_params.buf = staging;
pl_params.buf_offset = 0;
}
+#endif
}
bool ok = pl_tex_upload(gpu, &pl_params);
@@ -280,18 +255,20 @@ static bool tex_upload_pl(struct ra *ra, const struct ra_tex_upload_params *para
static bool tex_download_pl(struct ra *ra, struct ra_tex_download_params *params)
{
const struct pl_tex *tex = params->tex->priv;
- size_t texel_size = tex->params.format->texel_size;
struct pl_tex_transfer_params pl_params = {
.tex = tex,
.ptr = params->dst,
- .stride_w = params->stride / texel_size,
-#if PL_API_VER >= 60
.timer = get_active_timer(ra),
-#endif
};
- uint8_t *staging = NULL;
+#if PL_API_VER >= 168
+ pl_params.row_pitch = params->stride;
+ return pl_tex_download(get_gpu(ra), &pl_params);
+#else
+ size_t texel_size = tex->params.format->texel_size;
+ pl_params.stride_w = params->stride / texel_size;
size_t stride = pl_params.stride_w * texel_size;
+ uint8_t *staging = NULL;
if (stride != params->stride) {
staging = talloc_size(NULL, tex->params.h * stride);
pl_params.ptr = staging;
@@ -308,33 +285,16 @@ static bool tex_download_pl(struct ra *ra, struct ra_tex_download_params *params
talloc_free(staging);
return ok;
+#endif
}
static struct ra_buf *buf_create_pl(struct ra *ra,
const struct ra_buf_params *params)
{
- static const enum pl_buf_type buf_type[] = {
- [RA_BUF_TYPE_TEX_UPLOAD] = PL_BUF_TEX_TRANSFER,
- [RA_BUF_TYPE_SHADER_STORAGE] = PL_BUF_STORAGE,
- [RA_BUF_TYPE_UNIFORM] = PL_BUF_UNIFORM,
- [RA_BUF_TYPE_SHARED_MEMORY] = 0,
- };
-
- const struct pl_gpu *gpu = get_gpu(ra);
- size_t max_size[] = {
- [PL_BUF_TEX_TRANSFER] = gpu->limits.max_xfer_size,
- [PL_BUF_UNIFORM] = gpu->limits.max_ubo_size,
- [PL_BUF_STORAGE] = gpu->limits.max_ssbo_size,
- };
-
- if (params->size > max_size[buf_type[params->type]]) {
- MP_ERR(ra, "Buffer size %zu exceeds size limits!\n", params->size);
- return NULL;
- }
-
- const struct pl_buf *plbuf = pl_buf_create(gpu, &(struct pl_buf_params) {
- .type = buf_type[params->type],
+ const struct pl_buf *plbuf = pl_buf_create(get_gpu(ra), &(struct pl_buf_params) {
.size = params->size,
+ .uniform = params->type == RA_BUF_TYPE_UNIFORM,
+ .storable = params->type == RA_BUF_TYPE_SHADER_STORAGE,
.host_mapped = params->host_mapped,
.host_writable = params->host_mutable,
.initial_data = params->initial_data,
@@ -399,7 +359,14 @@ static void blit_pl(struct ra *ra, struct ra_tex *dst, struct ra_tex *src,
pldst.y1 = MPMIN(MPMAX(dst_rc->y1, 0), dst->params.h);
}
- pl_tex_blit(get_gpu(ra), dst->priv, src->priv, pldst, plsrc);
+ pl_tex_blit(get_gpu(ra), &(struct pl_tex_blit_params) {
+ .src = src->priv,
+ .dst = dst->priv,
+ .src_rc = plsrc,
+ .dst_rc = pldst,
+ .sample_mode = src->params.src_linear ? PL_TEX_SAMPLE_LINEAR
+ : PL_TEX_SAMPLE_NEAREST,
+ });
}
static const enum pl_var_type var_type[RA_VARTYPE_COUNT] = {
@@ -627,9 +594,15 @@ static void renderpass_run_pl(struct ra *ra,
struct pl_desc_binding bind;
switch (inp->type) {
case RA_VARTYPE_TEX:
- case RA_VARTYPE_IMG_W:
- bind.object = (* (struct ra_tex **) val->data)->priv;
+ case RA_VARTYPE_IMG_W: {
+ struct ra_tex *tex = *((struct ra_tex **) val->data);
+ bind.object = tex->priv;
+ bind.sample_mode = tex->params.src_linear ? PL_TEX_SAMPLE_LINEAR
+ : PL_TEX_SAMPLE_NEAREST;
+ bind.address_mode = tex->params.src_repeat ? PL_TEX_ADDRESS_REPEAT
+ : PL_TEX_ADDRESS_CLAMP;
break;
+ }
case RA_VARTYPE_BUF_RO:
case RA_VARTYPE_BUF_RW:
bind.object = (* (struct ra_buf **) val->data)->priv;
@@ -647,9 +620,7 @@ static void renderpass_run_pl(struct ra *ra,
.num_var_updates = p->num_varups,
.desc_bindings = p->binds,
.push_constants = params->push_constants,
-#if PL_API_VER >= 60
.timer = get_active_timer(ra),
-#endif
};
if (p->pl_pass->params.type == PL_PASS_RASTER) {
@@ -666,8 +637,6 @@ static void renderpass_run_pl(struct ra *ra,
pl_pass_run(get_gpu(ra), &pl_params);
}
-#if PL_API_VER >= 60
-
struct ra_timer_pl {
// Because libpplacebo only supports one operation per timer, we need
// to use multiple pl_timers to sum up multiple passes/transfers
@@ -739,8 +708,6 @@ static struct pl_timer *get_active_timer(const struct ra *ra)
return t->timers[t->idx_timers++];
}
-#endif // PL_API_VER >= 60
-
static struct ra_fns ra_fns_pl = {
.destroy = destroy_ra_pl,
.tex_create = tex_create_pl,
@@ -759,11 +726,9 @@ static struct ra_fns ra_fns_pl = {
.renderpass_create = renderpass_create_pl,
.renderpass_destroy = renderpass_destroy_pl,
.renderpass_run = renderpass_run_pl,
-#if PL_API_VER >= 60
.timer_create = timer_create_pl,
.timer_destroy = timer_destroy_pl,
.timer_start = timer_start_pl,
.timer_stop = timer_stop_pl,
-#endif
};
diff --git a/video/out/placebo/utils.h b/video/out/placebo/utils.h
index 03bcb0f..e6b43fc 100644
--- a/video/out/placebo/utils.h
+++ b/video/out/placebo/utils.h
@@ -4,6 +4,7 @@
#include "common/msg.h"
#include <libplacebo/common.h>
+#include <libplacebo/context.h>
void mppl_ctx_set_log(struct pl_context *ctx, struct mp_log *log, bool probing);
diff --git a/video/out/vo.h b/video/out/vo.h
index 7efec53..8e17b3c 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -120,6 +120,7 @@ enum mp_voctrl {
VOCTRL_GET_AMBIENT_LUX, // int*
VOCTRL_GET_DISPLAY_FPS, // double*
VOCTRL_GET_HIDPI_SCALE, // double*
+ VOCTRL_GET_DISPLAY_RES, // int[2]
/* private to vo_gpu */
VOCTRL_EXTERNAL_RESIZE,
diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c
index a2c6fc8..129aa12 100644
--- a/video/out/vo_drm.c
+++ b/video/out/vo_drm.c
@@ -45,7 +45,6 @@
#define BYTES_PER_PIXEL 4
#define BITS_PER_PIXEL 32
-#define USE_MASTER 0
struct framebuffer {
uint32_t width;
@@ -255,25 +254,19 @@ static void release_vt(void *data)
{
struct vo *vo = data;
crtc_release(vo);
- if (USE_MASTER) {
- //this function enables support for switching to x, weston etc.
- //however, for whatever reason, it can be called only by root users.
- //until things change, this is commented.
- struct priv *p = vo->priv;
- if (drmDropMaster(p->kms->fd)) {
- MP_WARN(vo, "Failed to drop DRM master: %s\n", mp_strerror(errno));
- }
+
+ const struct priv *p = vo->priv;
+ if (drmDropMaster(p->kms->fd)) {
+ MP_WARN(vo, "Failed to drop DRM master: %s\n", mp_strerror(errno));
}
}
static void acquire_vt(void *data)
{
struct vo *vo = data;
- if (USE_MASTER) {
- struct priv *p = vo->priv;
- if (drmSetMaster(p->kms->fd)) {
- MP_WARN(vo, "Failed to acquire DRM master: %s\n", mp_strerror(errno));
- }
+ const struct priv *p = vo->priv;
+ if (drmSetMaster(p->kms->fd)) {
+ MP_WARN(vo, "Failed to acquire DRM master: %s\n", mp_strerror(errno));
}
crtc_setup(vo);
@@ -573,6 +566,7 @@ static int preinit(struct vo *vo)
}
p->kms = kms_create(vo->log,
+ vo->opts->drm_opts->drm_device_path,
vo->opts->drm_opts->drm_connector_spec,
vo->opts->drm_opts->drm_mode_spec,
0, 0, false);
@@ -654,6 +648,11 @@ static int control(struct vo *vo, uint32_t request, void *arg)
*(double*)arg = fps;
return VO_TRUE;
}
+ case VOCTRL_GET_DISPLAY_RES: {
+ ((int *)arg)[0] = p->kms->mode.mode.hdisplay;
+ ((int *)arg)[1] = p->kms->mode.mode.vdisplay;
+ return VO_TRUE;
+ }
case VOCTRL_PAUSE:
vo->want_redraw = true;
p->paused = true;
diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c
index 5d28a30..38b29c3 100644
--- a/video/out/vo_gpu.c
+++ b/video/out/vo_gpu.c
@@ -292,8 +292,10 @@ static int preinit(struct vo *vo)
p->log = vo->log;
struct ra_ctx_opts opts = p->opts;
- struct gl_video_opts *gl_opts = mp_get_config_group(p->ctx, vo->global, &gl_video_conf);
+ struct gl_video_opts *gl_opts =
+ mp_get_config_group(p->ctx, vo->global, &gl_video_conf);
opts.want_alpha = gl_opts->alpha_mode == 1;
+ talloc_free(gl_opts);
p->ctx = ra_ctx_create(vo, p->context_type, p->context_name, opts);
if (!p->ctx)
@@ -321,8 +323,12 @@ err_out:
#define OPT_BASE_STRUCT struct gpu_priv
static const m_option_t options[] = {
- {"gpu-context", OPT_STRING_VALIDATE(context_name, ra_ctx_validate_context)},
- {"gpu-api", OPT_STRING_VALIDATE(context_type, ra_ctx_validate_api)},
+ {"gpu-context",
+ OPT_STRING_VALIDATE(context_name, ra_ctx_validate_context),
+ .help = ra_ctx_context_help},
+ {"gpu-api",
+ OPT_STRING_VALIDATE(context_type, ra_ctx_validate_api),
+ .help = ra_ctx_api_help},
{"gpu-debug", OPT_FLAG(opts.debug)},
{"gpu-sw", OPT_FLAG(opts.allow_sw)},
{0}
diff --git a/video/out/vo_rpi.c b/video/out/vo_rpi.c
index 4d5de1d..0d89cfa 100644
--- a/video/out/vo_rpi.c
+++ b/video/out/vo_rpi.c
@@ -69,6 +69,7 @@ struct priv {
double osd_pts;
struct mp_osd_res osd_res;
+ struct m_config_cache *opts_cache;
struct mp_egl_rpi egl;
struct gl_video *gl_video;
@@ -720,11 +721,28 @@ fail:
return NULL;
}
+static void set_fullscreen(struct vo *vo) {
+ struct priv *p = vo->priv;
+
+ if (p->renderer_enabled)
+ set_geometry(vo);
+ vo->want_redraw = true;
+}
+
static int control(struct vo *vo, uint32_t request, void *data)
{
struct priv *p = vo->priv;
switch (request) {
+ case VOCTRL_VO_OPTS_CHANGED: {
+ void *opt;
+ while (m_config_cache_get_next_changed(p->opts_cache, &opt)) {
+ struct mp_vo_opts *opts = p->opts_cache->opts;
+ if (&opts->fullscreen == opt)
+ set_fullscreen(vo);
+ }
+ return VO_TRUE;
+ }
case VOCTRL_SET_PANSCAN:
if (p->renderer_enabled)
resize(vo);
@@ -748,6 +766,10 @@ static int control(struct vo *vo, uint32_t request, void *data)
case VOCTRL_GET_DISPLAY_FPS:
*(double *)data = p->display_fps;
return VO_TRUE;
+ case VOCTRL_GET_DISPLAY_RES:
+ ((int *)data)[0] = p->w;
+ ((int *)data)[1] = p->h;
+ return VO_TRUE;
}
return VO_NOTIMPL;
@@ -782,6 +804,10 @@ static void destroy_dispmanx(struct vo *vo)
disable_renderer(vo);
destroy_overlays(vo);
+ if (p->update)
+ vc_dispmanx_update_submit_sync(p->update);
+ p->update = 0;
+
if (p->display) {
vc_dispmanx_vsync_callback(p->display, NULL, NULL);
vc_dispmanx_display_close(p->display);
@@ -834,9 +860,6 @@ static void uninit(struct vo *vo)
destroy_dispmanx(vo);
- if (p->update)
- vc_dispmanx_update_submit_sync(p->update);
-
if (p->renderer)
mmal_component_release(p->renderer);
@@ -866,6 +889,8 @@ static int preinit(struct vo *vo)
pthread_mutex_init(&p->display_mutex, NULL);
pthread_cond_init(&p->display_cond, NULL);
+ p->opts_cache = m_config_cache_alloc(p, vo->global, &vo_sub_opts);
+
if (recreate_dispmanx(vo) < 0)
goto fail;
diff --git a/video/out/vo_sixel.c b/video/out/vo_sixel.c
index 8318c51..c9cc157 100644
--- a/video/out/vo_sixel.c
+++ b/video/out/vo_sixel.c
@@ -63,19 +63,19 @@ struct priv {
int opt_pad_x;
int opt_rows;
int opt_cols;
+ int opt_clear;
// Internal data
sixel_output_t *output;
sixel_dither_t *dither;
sixel_dither_t *testdither;
uint8_t *buffer;
+ bool skip_frame_draw;
- // The dimensions that will be actually
- // be used after processing user inputs
- int top;
- int left;
- int width;
- int height;
+ int left, top; // image origin cell (1 based)
+ int width, height; // actual image px size - always reflects dst_rect.
+ int num_cols, num_rows; // terminal size in cells
+ int canvas_ok; // whether canvas vo->dwidth and vo->dheight are positive
int previous_histgram_colors;
@@ -115,7 +115,7 @@ static int detect_scene_change(struct vo* vo)
}
-static void dealloc_dithers_and_buffer(struct vo* vo)
+static void dealloc_dithers_and_buffers(struct vo* vo)
{
struct priv* priv = vo->priv;
@@ -124,6 +124,11 @@ static void dealloc_dithers_and_buffer(struct vo* vo)
priv->buffer = NULL;
}
+ if (priv->frame) {
+ talloc_free(priv->frame);
+ priv->frame = NULL;
+ }
+
if (priv->dither) {
sixel_dither_unref(priv->dither);
priv->dither = NULL;
@@ -139,15 +144,15 @@ static SIXELSTATUS prepare_static_palette(struct vo* vo)
{
struct priv* priv = vo->priv;
- if (priv->dither) {
- sixel_dither_set_body_only(priv->dither, 1);
- } else {
+ if (!priv->dither) {
priv->dither = sixel_dither_get(BUILTIN_XTERM256);
if (priv->dither == NULL)
return SIXEL_FALSE;
sixel_dither_set_diffusion_type(priv->dither, priv->opt_diffuse);
}
+
+ sixel_dither_set_body_only(priv->dither, 0);
return SIXEL_OK;
}
@@ -159,7 +164,8 @@ static SIXELSTATUS prepare_dynamic_palette(struct vo *vo)
/* create histgram and construct color palette
* with median cut algorithm. */
status = sixel_dither_initialize(priv->testdither, priv->buffer,
- priv->width, priv->height, 3,
+ priv->width, priv->height,
+ SIXEL_PIXELFORMAT_RGB888,
LARGE_NORM, REP_CENTER_BOX,
QUALITY_LOW);
if (SIXEL_FAILED(status))
@@ -179,22 +185,18 @@ static SIXELSTATUS prepare_dynamic_palette(struct vo *vo)
sixel_dither_set_diffusion_type(priv->dither, priv->opt_diffuse);
} else {
- if (priv->dither == NULL) {
+ if (priv->dither == NULL)
return SIXEL_FALSE;
- }
- sixel_dither_set_body_only(priv->dither, 1);
}
+ sixel_dither_set_body_only(priv->dither, 0);
return status;
}
-static void resize(struct vo *vo)
+static void update_canvas_dimensions(struct vo *vo)
{
// this function sets the vo canvas size in pixels vo->dwidth, vo->dheight,
- // and the output scaled size in priv->width, priv->height
- // and the scaling rectangles in pixels priv->src_rect, priv->dst_rect
- // as well as image positioning in cells priv->top, priv->left.
- // no other scaling/rendering size values are required past this point.
+ // and the number of rows and columns available in priv->num_rows/cols
struct priv *priv = vo->priv;
int num_rows = TERMINAL_FALLBACK_ROWS;
int num_cols = TERMINAL_FALLBACK_COLS;
@@ -254,6 +256,19 @@ static void resize(struct vo *vo)
vo->dheight = total_px_height * (num_rows - 1) / num_rows / 6 * 6;
vo->dwidth = total_px_width;
+ priv->num_rows = num_rows;
+ priv->num_cols = num_cols;
+
+ priv->canvas_ok = vo->dwidth > 0 && vo->dheight > 0;
+}
+
+static void set_sixel_output_parameters(struct vo *vo)
+{
+ // This function sets output scaled size in priv->width, priv->height
+ // and the scaling rectangles in pixels priv->src_rect, priv->dst_rect
+ // as well as image positioning in cells priv->top, priv->left.
+ struct priv *priv = vo->priv;
+
vo_get_src_dst_rects(vo, &priv->src_rect, &priv->dst_rect, &priv->osd);
// priv->width and priv->height are the width and height of dst_rect
@@ -265,15 +280,14 @@ static void resize(struct vo *vo)
// top/left values must be greater than 1. If it is set, then
// the image will be rendered from there and no further centering is done.
priv->top = (priv->opt_top > 0) ? priv->opt_top :
- num_rows * priv->dst_rect.y0 / vo->dheight + 1;
+ priv->num_rows * priv->dst_rect.y0 / vo->dheight + 1;
priv->left = (priv->opt_left > 0) ? priv->opt_left :
- num_cols * priv->dst_rect.x0 / vo->dwidth + 1;
+ priv->num_cols * priv->dst_rect.x0 / vo->dwidth + 1;
}
-static int reconfig(struct vo *vo, struct mp_image_params *params)
+static int update_sixel_swscaler(struct vo *vo, struct mp_image_params *params)
{
struct priv *priv = vo->priv;
- resize(vo);
priv->sws->src = *params;
priv->sws->src.w = mp_rect_w(priv->src_rect);
@@ -286,6 +300,8 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
.p_h = 1,
};
+ dealloc_dithers_and_buffers(vo);
+
priv->frame = mp_image_alloc(IMGFMT, priv->width, priv->height);
if (!priv->frame)
return -1;
@@ -293,17 +309,15 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
if (mp_sws_reinit(priv->sws) < 0)
return -1;
- printf(ESC_HIDE_CURSOR);
- printf(ESC_CLEAR_SCREEN);
- vo->want_redraw = true;
-
- dealloc_dithers_and_buffer(vo);
- SIXELSTATUS status = sixel_dither_new(&priv->testdither,
- priv->opt_reqcolors, NULL);
- if (SIXEL_FAILED(status)) {
- MP_ERR(vo, "reconfig: Failed to create new dither: %s\n",
- sixel_helper_format_error(status));
- return -1;
+ // create testdither only if dynamic palette mode is set
+ if (!priv->opt_fixedpal) {
+ SIXELSTATUS status = sixel_dither_new(&priv->testdither,
+ priv->opt_reqcolors, NULL);
+ if (SIXEL_FAILED(status)) {
+ MP_ERR(vo, "update_sixel_swscaler: Failed to create new dither: %s\n",
+ sixel_helper_format_error(status));
+ return -1;
+ }
}
priv->buffer =
@@ -312,28 +326,82 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
return 0;
}
-static void draw_image(struct vo *vo, mp_image_t *mpi)
+static int reconfig(struct vo *vo, struct mp_image_params *params)
+{
+ struct priv *priv = vo->priv;
+ int ret = 0;
+ update_canvas_dimensions(vo);
+ if (priv->canvas_ok) { // if too small - succeed but skip the rendering
+ set_sixel_output_parameters(vo);
+ ret = update_sixel_swscaler(vo, params);
+ }
+
+ printf(ESC_CLEAR_SCREEN);
+ vo->want_redraw = true;
+
+ return ret;
+}
+
+static void draw_frame(struct vo *vo, struct vo_frame *frame)
{
struct priv *priv = vo->priv;
- struct mp_image src = *mpi;
SIXELSTATUS status;
+ struct mp_image *mpi = NULL;
+
+ int prev_rows = priv->num_rows;
+ int prev_cols = priv->num_cols;
+ int prev_height = vo->dheight;
+ int prev_width = vo->dwidth;
+ bool resized = false;
+ update_canvas_dimensions(vo);
+ if (!priv->canvas_ok)
+ return;
- struct mp_rect src_rc = priv->src_rect;
- src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, mpi->fmt.align_x);
- src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, mpi->fmt.align_y);
- mp_image_crop_rc(&src, src_rc);
+ if (prev_rows != priv->num_rows || prev_cols != priv->num_cols ||
+ prev_width != vo->dwidth || prev_height != vo->dheight)
+ {
+ set_sixel_output_parameters(vo);
+ // Not checking for vo->config_ok because draw_frame is never called
+ // with a failed reconfig.
+ update_sixel_swscaler(vo, vo->params);
- // Downscale the image
- mp_sws_scale(priv->sws, priv->frame, &src);
+ printf(ESC_CLEAR_SCREEN);
+ resized = true;
+ }
+
+ if (frame->repeat && !frame->redraw && !resized) {
+ // Frame is repeated, and no need to update OSD either
+ priv->skip_frame_draw = true;
+ return;
+ } else {
+ // Either frame is new, or OSD has to be redrawn
+ priv->skip_frame_draw = false;
+ }
+
+ // Normal case where we have to draw the frame and the image is not NULL
+ if (frame->current) {
+ mpi = mp_image_new_ref(frame->current);
+ struct mp_rect src_rc = priv->src_rect;
+ src_rc.x0 = MP_ALIGN_DOWN(src_rc.x0, mpi->fmt.align_x);
+ src_rc.y0 = MP_ALIGN_DOWN(src_rc.y0, mpi->fmt.align_y);
+ mp_image_crop_rc(mpi, src_rc);
+
+ // scale/pan to our dest rect
+ mp_sws_scale(priv->sws, priv->frame, mpi);
+ } else {
+ // Image is NULL, so need to clear image and draw OSD
+ mp_image_clear(priv->frame, 0, 0, priv->width, priv->height);
+ }
struct mp_osd_res dim = {
.w = priv->width,
.h = priv->height
};
osd_draw_on_image(vo->osd, dim, mpi ? mpi->pts : 0, 0, priv->frame);
+
// Copy from mpv to RGB format as required by libsixel
- memcpy_pic(priv->buffer, priv->frame->planes[0], priv->width * depth, priv->height,
- priv->width * depth, priv->frame->stride[0]);
+ memcpy_pic(priv->buffer, priv->frame->planes[0], priv->width * depth,
+ priv->height, priv->width * depth, priv->frame->stride[0]);
// Even if either of these prepare palette functions fail, on re-running them
// they should try to re-initialize the dithers, so it shouldn't dereference
@@ -346,11 +414,12 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
}
if (SIXEL_FAILED(status)) {
- MP_WARN(vo, "draw_image: prepare_palette returned error: %s\n",
+ MP_WARN(vo, "draw_frame: prepare_palette returned error: %s\n",
sixel_helper_format_error(status));
}
- talloc_free(mpi);
+ if (mpi)
+ talloc_free(mpi);
}
static int sixel_write(char *data, int size, void *priv)
@@ -361,6 +430,12 @@ static int sixel_write(char *data, int size, void *priv)
static void flip_page(struct vo *vo)
{
struct priv* priv = vo->priv;
+ if (!priv->canvas_ok)
+ return;
+
+ // If frame is repeated and no update required, then we skip encoding
+ if (priv->skip_frame_draw)
+ return;
// Make sure that image and dither are valid before drawing
if (priv->buffer == NULL || priv->dither == NULL)
@@ -369,8 +444,7 @@ static void flip_page(struct vo *vo)
// Go to the offset row and column, then display the image
printf(ESC_GOTOXY, priv->top, priv->left);
sixel_encode(priv->buffer, priv->width, priv->height,
- PIXELFORMAT_RGB888,
- priv->dither, priv->output);
+ depth, priv->dither, priv->output);
fflush(stdout);
}
@@ -400,18 +474,17 @@ static int preinit(struct vo *vo)
printf(ESC_USE_GLOBAL_COLOR_REG);
priv->dither = NULL;
- status = sixel_dither_new(&priv->testdither, priv->opt_reqcolors, NULL);
- if (SIXEL_FAILED(status)) {
- MP_ERR(vo, "preinit: Failed to create new dither: %s\n",
- sixel_helper_format_error(status));
- return -1;
+ // create testdither only if dynamic palette mode is set
+ if (!priv->opt_fixedpal) {
+ status = sixel_dither_new(&priv->testdither, priv->opt_reqcolors, NULL);
+ if (SIXEL_FAILED(status)) {
+ MP_ERR(vo, "preinit: Failed to create new dither: %s\n",
+ sixel_helper_format_error(status));
+ return -1;
+ }
}
- resize(vo);
- priv->buffer =
- talloc_array(NULL, uint8_t, depth * priv->width * priv->height);
-
priv->previous_histgram_colors = 0;
return 0;
@@ -424,15 +497,9 @@ static int query_format(struct vo *vo, int format)
static int control(struct vo *vo, uint32_t request, void *data)
{
- if (request == VOCTRL_SET_PANSCAN) {
- if (!reconfig(vo, vo->params)) {
- return VO_TRUE;
- } else {
- return VO_FALSE;
- }
- } else {
- return VO_NOTIMPL;
- }
+ if (request == VOCTRL_SET_PANSCAN)
+ return (vo->config_ok && !reconfig(vo, vo->params)) ? VO_TRUE : VO_FALSE;
+ return VO_NOTIMPL;
}
@@ -442,8 +509,10 @@ static void uninit(struct vo *vo)
printf(ESC_RESTORE_CURSOR);
- printf(ESC_CLEAR_SCREEN);
- printf(ESC_GOTOXY, 1, 1);
+ if (priv->opt_clear) {
+ printf(ESC_CLEAR_SCREEN);
+ printf(ESC_GOTOXY, 1, 1);
+ }
fflush(stdout);
if (priv->output) {
@@ -451,24 +520,24 @@ static void uninit(struct vo *vo)
priv->output = NULL;
}
- dealloc_dithers_and_buffer(vo);
+ dealloc_dithers_and_buffers(vo);
}
#define OPT_BASE_STRUCT struct priv
const struct vo_driver video_out_sixel = {
.name = "sixel",
- .description = "libsixel",
+ .description = "terminal graphics using sixels",
.preinit = preinit,
.query_format = query_format,
.reconfig = reconfig,
.control = control,
- .draw_image = draw_image,
+ .draw_frame = draw_frame,
.flip_page = flip_page,
.uninit = uninit,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv) {
- .opt_diffuse = DIFFUSE_ATKINSON,
+ .opt_diffuse = DIFFUSE_AUTO,
.opt_width = 0,
.opt_height = 0,
.opt_reqcolors = 256,
@@ -480,6 +549,7 @@ const struct vo_driver video_out_sixel = {
.opt_pad_x = -1,
.opt_rows = 0,
.opt_cols = 0,
+ .opt_clear = 1,
},
.options = (const m_option_t[]) {
{"dither", OPT_CHOICE(opt_diffuse,
@@ -503,6 +573,7 @@ const struct vo_driver video_out_sixel = {
{"pad-x", OPT_INT(opt_pad_x)},
{"rows", OPT_INT(opt_rows)},
{"cols", OPT_INT(opt_cols)},
+ {"exit-clear", OPT_FLAG(opt_clear), },
{0}
},
.options_prefix = "vo-sixel",
diff --git a/video/out/vo_tct.c b/video/out/vo_tct.c
index b0965e1..ac224b9 100644
--- a/video/out/vo_tct.c
+++ b/video/out/vo_tct.c
@@ -28,6 +28,7 @@
#include "options/m_config.h"
#include "config.h"
#include "osdep/terminal.h"
+#include "osdep/io.h"
#include "vo.h"
#include "sub/osd.h"
#include "video/sws_utils.h"
@@ -42,10 +43,10 @@
#define ESC_CLEAR_SCREEN "\033[2J"
#define ESC_CLEAR_COLORS "\033[0m"
#define ESC_GOTOXY "\033[%d;%df"
-#define ESC_COLOR_BG "\033[48;2;%d;%d;%dm"
-#define ESC_COLOR_FG "\033[38;2;%d;%d;%dm"
-#define ESC_COLOR256_BG "\033[48;5;%dm"
-#define ESC_COLOR256_FG "\033[38;5;%dm"
+#define ESC_COLOR_BG "\033[48;2"
+#define ESC_COLOR_FG "\033[38;2"
+#define ESC_COLOR256_BG "\033[48;5"
+#define ESC_COLOR256_FG "\033[38;5"
#define DEFAULT_WIDTH 80
#define DEFAULT_HEIGHT 25
@@ -73,16 +74,21 @@ static const struct m_sub_options vo_tct_conf = {
.size = sizeof(struct vo_tct_opts),
};
+struct lut_item {
+ char str[4];
+ int width;
+};
+
struct priv {
struct vo_tct_opts *opts;
size_t buffer_size;
- char *buffer;
int swidth;
int sheight;
struct mp_image *frame;
struct mp_rect src;
struct mp_rect dst;
struct mp_sws_context *sws;
+ struct lut_item lut[256];
};
// Convert RGB24 to xterm-256 8-bit value
@@ -114,11 +120,41 @@ static int rgb_to_x256(uint8_t r, uint8_t g, uint8_t b)
return color_err <= gray_err ? 16 + color_index() : 232 + gray_index;
}
+static void print_seq3(struct lut_item *lut, const char* prefix,
+ uint8_t r, uint8_t g, uint8_t b)
+{
+// The fwrite implementation is about 25% faster than the printf code
+// (even if we use *.s with the lut values), however,
+// on windows we need to use printf in order to translate escape sequences and
+// UTF8 output for the console.
+#ifndef _WIN32
+ fputs(prefix, stdout);
+ fwrite(lut[r].str, lut[r].width, 1, stdout);
+ fwrite(lut[g].str, lut[g].width, 1, stdout);
+ fwrite(lut[b].str, lut[b].width, 1, stdout);
+ fputc('m', stdout);
+#else
+ printf("%s;%d;%d;%dm", prefix, (int)r, (int)g, (int)b);
+#endif
+}
+
+static void print_seq1(struct lut_item *lut, const char* prefix, uint8_t c)
+{
+#ifndef _WIN32
+ fputs(prefix, stdout);
+ fwrite(lut[c].str, lut[c].width, 1, stdout);
+ fputc('m', stdout);
+#else
+ printf("%s;%dm", prefix, (int)c);
+#endif
+}
+
+
static void write_plain(
const int dwidth, const int dheight,
const int swidth, const int sheight,
const unsigned char *source, const int source_stride,
- bool term256)
+ bool term256, struct lut_item *lut)
{
assert(source);
const int tx = (dwidth - swidth) / 2;
@@ -131,9 +167,9 @@ static void write_plain(
unsigned char g = *row++;
unsigned char r = *row++;
if (term256) {
- printf(ESC_COLOR256_BG, rgb_to_x256(r, g, b));
+ print_seq1(lut, ESC_COLOR256_BG, rgb_to_x256(r, g, b));
} else {
- printf(ESC_COLOR_BG, r, g, b);
+ print_seq3(lut, ESC_COLOR_BG, r, g, b);
}
printf(" ");
}
@@ -146,7 +182,7 @@ static void write_half_blocks(
const int dwidth, const int dheight,
const int swidth, const int sheight,
unsigned char *source, int source_stride,
- bool term256)
+ bool term256, struct lut_item *lut)
{
assert(source);
const int tx = (dwidth - swidth) / 2;
@@ -163,11 +199,11 @@ static void write_half_blocks(
unsigned char g_down = *row_down++;
unsigned char r_down = *row_down++;
if (term256) {
- printf(ESC_COLOR256_BG, rgb_to_x256(r_up, g_up, b_up));
- printf(ESC_COLOR256_FG, rgb_to_x256(r_down, g_down, b_down));
+ print_seq1(lut, ESC_COLOR256_BG, rgb_to_x256(r_up, g_up, b_up));
+ print_seq1(lut, ESC_COLOR256_FG, rgb_to_x256(r_down, g_down, b_down));
} else {
- printf(ESC_COLOR_BG, r_up, g_up, b_up);
- printf(ESC_COLOR_FG, r_down, g_down, b_down);
+ print_seq3(lut, ESC_COLOR_BG, r_up, g_up, b_up);
+ print_seq3(lut, ESC_COLOR_FG, r_down, g_down, b_down);
}
printf("\xe2\x96\x84"); // UTF8 bytes of U+2584 (lower half block)
}
@@ -200,9 +236,6 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
p->swidth = p->dst.x1 - p->dst.x0;
p->sheight = p->dst.y1 - p->dst.y0;
- if (p->buffer)
- free(p->buffer);
-
p->sws->src = *params;
p->sws->dst = (struct mp_image_params) {
.imgfmt = IMGFMT,
@@ -213,6 +246,8 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
};
const int mul = (p->opts->algo == ALGO_PLAIN ? 1 : 2);
+ if (p->frame)
+ talloc_free(p->frame);
p->frame = mp_image_alloc(IMGFMT, p->swidth, p->sheight * mul);
if (!p->frame)
return -1;
@@ -238,16 +273,23 @@ static void draw_image(struct vo *vo, mp_image_t *mpi)
static void flip_page(struct vo *vo)
{
struct priv *p = vo->priv;
+
+ int width, height;
+ get_win_size(vo, &width, &height);
+
+ if (vo->dwidth != width || vo->dheight != height)
+ reconfig(vo, vo->params);
+
if (p->opts->algo == ALGO_PLAIN) {
write_plain(
vo->dwidth, vo->dheight, p->swidth, p->sheight,
p->frame->planes[0], p->frame->stride[0],
- p->opts->term256);
+ p->opts->term256, p->lut);
} else {
write_half_blocks(
vo->dwidth, vo->dheight, p->swidth, p->sheight,
p->frame->planes[0], p->frame->stride[0],
- p->opts->term256);
+ p->opts->term256, p->lut);
}
fflush(stdout);
}
@@ -258,8 +300,8 @@ static void uninit(struct vo *vo)
printf(ESC_CLEAR_SCREEN);
printf(ESC_GOTOXY, 0, 0);
struct priv *p = vo->priv;
- if (p->buffer)
- talloc_free(p->buffer);
+ if (p->frame)
+ talloc_free(p->frame);
}
static int preinit(struct vo *vo)
@@ -273,6 +315,13 @@ static int preinit(struct vo *vo)
p->sws = mp_sws_alloc(vo);
p->sws->log = vo->log;
mp_sws_enable_cmdline_opts(p->sws, vo->global);
+
+ for (int i = 0; i < 256; ++i) {
+ char buff[8];
+ p->lut[i].width = sprintf(buff, ";%d", i);
+ memcpy(p->lut[i].str, buff, 4); // some strings may not end on a null byte, but that's ok.
+ }
+
return 0;
}
diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c
index 6778494..cae28cd 100644
--- a/video/out/vo_vdpau.c
+++ b/video/out/vo_vdpau.c
@@ -478,7 +478,17 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
VdpStatus vdp_st;
if (!check_preemption(vo))
- return -1;
+ {
+ /*
+ * When prempted, leave the reconfig() immediately
+ * without reconfiguring the vo_window and without
+ * initializing the vdpau objects. When recovered
+ * from preemption, if there is a difference between
+ * the VD thread parameters and the VO thread parameters
+ * the reconfig() is triggered again.
+ */
+ return 0;
+ }
VdpChromaType chroma_type = VDP_CHROMA_TYPE_420;
mp_vdpau_get_format(params->imgfmt, &chroma_type, NULL);
diff --git a/video/out/vo_wlshm.c b/video/out/vo_wlshm.c
index f6c3771..4744e50 100644
--- a/video/out/vo_wlshm.c
+++ b/video/out/vo_wlshm.c
@@ -23,6 +23,7 @@
#include <libswscale/swscale.h>
+#include "osdep/endian.h"
#include "sub/osd.h"
#include "video/fmt-conversion.h"
#include "video/mp_image.h"
@@ -73,25 +74,6 @@ static void buffer_destroy(void *p)
munmap(buf->mpi.planes[0], buf->size);
}
-static const struct wl_callback_listener frame_listener;
-
-static void frame_callback(void *data, struct wl_callback *callback, uint32_t time)
-{
- struct vo_wayland_state *wl = data;
-
- if (callback)
- wl_callback_destroy(callback);
-
- wl->frame_callback = wl_surface_frame(wl->surface);
- wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
-
- wl->frame_wait = false;
-}
-
-static const struct wl_callback_listener frame_listener = {
- frame_callback,
-};
-
static int allocate_memfd(size_t size)
{
int fd = memfd_create("mpv", MFD_CLOEXEC | MFD_ALLOW_SEALING);
@@ -142,10 +124,6 @@ static struct buffer *buffer_create(struct vo *vo, int width, int height)
if (!buf->buffer)
goto error4;
wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
- if (!wl->frame_callback) {
- wl->frame_callback = wl_surface_frame(wl->surface);
- wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
- }
close(fd);
talloc_set_destructor(buf, buffer_destroy);
@@ -207,7 +185,7 @@ static int resize(struct vo *vo)
vo->dheight = height;
vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd);
p->sws->dst = (struct mp_image_params) {
- .imgfmt = IMGFMT_BGR0,
+ .imgfmt = MP_SELECT_LE_BE(IMGFMT_BGR0, IMGFMT_0RGB),
.w = width,
.h = height,
.p_w = 1,
@@ -240,12 +218,12 @@ static void draw_image(struct vo *vo, struct mp_image *src)
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
struct buffer *buf;
+ bool render = !wl->hidden || wl->opts->disable_vsync;
+ wl->frame_wait = true;
- if (wl->hidden)
+ if (!render)
return;
- wl->frame_wait = true;
-
buf = p->free_buffers;
if (buf) {
p->free_buffers = buf->next;
@@ -298,6 +276,19 @@ static void flip_page(struct vo *vo)
if (!wl->opts->disable_vsync)
vo_wayland_wait_frame(wl);
+
+ if (wl->presentation)
+ vo_wayland_sync_swap(wl);
+}
+
+static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
+{
+ struct vo_wayland_state *wl = vo->wl;
+ if (wl->presentation) {
+ info->vsync_duration = wl->vsync_duration;
+ info->skipped_vsyncs = wl->last_skipped_vsyncs;
+ info->last_queue_display_time = wl->last_queue_display_time;
+ }
}
static void uninit(struct vo *vo)
@@ -313,11 +304,6 @@ static void uninit(struct vo *vo)
vo_wayland_uninit(vo);
}
-#define OPT_BASE_STRUCT struct priv
-static const m_option_t options[] = {
- {0}
-};
-
const struct vo_driver video_out_wlshm = {
.description = "Wayland SHM video output (software scaling)",
.name = "wlshm",
@@ -327,9 +313,9 @@ const struct vo_driver video_out_wlshm = {
.control = control,
.draw_image = draw_image,
.flip_page = flip_page,
+ .get_vsync = get_vsync,
.wakeup = vo_wayland_wakeup,
.wait_events = vo_wayland_wait_events,
.uninit = uninit,
.priv_size = sizeof(struct priv),
- .options = options,
};
diff --git a/video/out/vulkan/context.c b/video/out/vulkan/context.c
index 3518d3e..7c9f2f6 100644
--- a/video/out/vulkan/context.c
+++ b/video/out/vulkan/context.c
@@ -31,8 +31,9 @@ struct vulkan_opts {
};
static int vk_validate_dev(struct mp_log *log, const struct m_option *opt,
- struct bstr name, struct bstr param)
+ struct bstr name, const char **value)
{
+ struct bstr param = bstr0(*value);
int ret = M_OPT_INVALID;
VkResult res;
@@ -171,9 +172,7 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
.async_compute = p->opts->async_compute,
.queue_count = p->opts->queue_count,
.device_name = p->opts->device,
-#if PL_API_VER >= 24
.disable_events = p->opts->disable_events,
-#endif
});
if (!vk->vulkan)
goto error;
@@ -188,11 +187,9 @@ bool ra_vk_ctx_init(struct ra_ctx *ctx, struct mpvk_ctx *vk,
.surface = vk->surface,
.present_mode = preferred_mode,
.swapchain_depth = ctx->vo->opts->swapchain_depth,
-#if PL_API_VER >= 29
// mpv already handles resize events, so gracefully allow suboptimal
// swapchains to exist in order to make resizing even smoother
.allow_suboptimal = true,
-#endif
};
if (p->opts->swap_mode >= 0) // user override
@@ -220,6 +217,20 @@ bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height)
return ok;
}
+char *ra_vk_ctx_get_device_name(struct ra_ctx *ctx)
+{
+ /*
+ * This implementation is a bit odd because it has to work even if the
+ * ctx hasn't been initialised yet. A context implementation may need access
+ * to the device name before it can fully initialise the ctx.
+ */
+ struct vulkan_opts *opts = mp_get_config_group(NULL, ctx->global,
+ &vulkan_conf);
+ char *device_name = talloc_strdup(NULL, opts->device);
+ talloc_free(opts);
+ return device_name;
+}
+
static int color_depth(struct ra_swapchain *sw)
{
return 0; // TODO: implement this somehow?
diff --git a/video/out/vulkan/context.h b/video/out/vulkan/context.h
index 6ae64bb..d85b3fe 100644
--- a/video/out/vulkan/context.h
+++ b/video/out/vulkan/context.h
@@ -25,3 +25,6 @@ bool ra_vk_ctx_resize(struct ra_ctx *ctx, int width, int height);
// May be called on a ra_ctx of any type.
struct mpvk_ctx *ra_vk_ctx_get(struct ra_ctx *ctx);
+
+// Get the user requested Vulkan device name.
+char *ra_vk_ctx_get_device_name(struct ra_ctx *ctx); \ No newline at end of file
diff --git a/video/out/vulkan/context_display.c b/video/out/vulkan/context_display.c
new file mode 100644
index 0000000..5545b33
--- /dev/null
+++ b/video/out/vulkan/context_display.c
@@ -0,0 +1,395 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "options/m_config.h"
+
+#include "context.h"
+#include "utils.h"
+
+struct vulkan_display_opts {
+ int display;
+ int mode;
+ int plane;
+};
+
+struct mode_selector {
+ // Indexes of selected display/mode/plane.
+ int display_idx;
+ int mode_idx;
+ int plane_idx;
+
+ // Must be freed with talloc_free
+ VkDisplayModePropertiesKHR *out_mode_props;
+};
+
+/**
+ * If a selector is passed, verify that it is valid and return the matching
+ * mode properties. If null is passed, walk all modes and print them out.
+ */
+static bool walk_display_properties(struct mp_log *log,
+ int msgl_err,
+ VkPhysicalDevice device,
+ struct mode_selector *selector) {
+ bool ret = false;
+ VkResult res;
+
+ int msgl_info = selector ? MSGL_TRACE : MSGL_INFO;
+
+ // Use a dummy as parent for all other allocations.
+ void *tmp = talloc_new(NULL);
+
+ VkPhysicalDeviceProperties prop;
+ vkGetPhysicalDeviceProperties(device, &prop);
+ mp_msg(log, msgl_info, " '%s' (GPU ID %x:%x)\n", prop.deviceName,
+ (unsigned)prop.vendorID, (unsigned)prop.deviceID);
+
+ // Count displays. This must be done before enumerating planes with the
+ // Intel driver, or it will not enumerate any planes. WTF.
+ int num_displays = 0;
+ vkGetPhysicalDeviceDisplayPropertiesKHR(device, &num_displays, NULL);
+ if (!num_displays) {
+ mp_msg(log, msgl_info, " No available displays for device.\n");
+ goto done;
+ }
+ if (selector && selector->display_idx + 1 > num_displays) {
+ mp_msg(log, msgl_err, "Selected display (%d) not present.\n",
+ selector->display_idx);
+ goto done;
+ }
+
+ // Enumerate Planes
+ int num_planes = 0;
+ vkGetPhysicalDeviceDisplayPlanePropertiesKHR(device, &num_planes, NULL);
+ if (!num_planes) {
+ mp_msg(log, msgl_info, " No available planes for device.\n");
+ goto done;
+ }
+ if (selector && selector->plane_idx + 1 > num_planes) {
+ mp_msg(log, msgl_err, "Selected plane (%d) not present.\n",
+ selector->plane_idx);
+ goto done;
+ }
+
+ VkDisplayPlanePropertiesKHR *planes =
+ talloc_array(tmp, VkDisplayPlanePropertiesKHR, num_planes);
+ res = vkGetPhysicalDeviceDisplayPlanePropertiesKHR(device, &num_planes,
+ planes);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating planes\n");
+ goto done;
+ }
+
+ // Allocate zeroed arrays so that planes with no displays have a null entry.
+ VkDisplayKHR **planes_to_displays =
+ talloc_zero_array(tmp, VkDisplayKHR *, num_planes);
+ for (int j = 0; j < num_planes; j++) {
+ int num_displays_for_plane = 0;
+ vkGetDisplayPlaneSupportedDisplaysKHR(device, j,
+ &num_displays_for_plane, NULL);
+ if (!num_displays_for_plane)
+ continue;
+
+ // Null terminated array
+ VkDisplayKHR *displays =
+ talloc_zero_array(planes_to_displays, VkDisplayKHR,
+ num_displays_for_plane + 1);
+ res = vkGetDisplayPlaneSupportedDisplaysKHR(device, j,
+ &num_displays_for_plane,
+ displays);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating plane displays\n");
+ continue;
+ }
+ planes_to_displays[j] = displays;
+ }
+
+ // Enumerate Displays and Modes
+ VkDisplayPropertiesKHR *props =
+ talloc_array(tmp, VkDisplayPropertiesKHR, num_displays);
+ res = vkGetPhysicalDeviceDisplayPropertiesKHR(device, &num_displays, props);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating display properties\n");
+ goto done;
+ }
+
+ for (int j = 0; j < num_displays; j++) {
+ if (selector && selector->display_idx != j)
+ continue;
+
+ mp_msg(log, msgl_info, " Display %d: '%s' (%dx%d)\n",
+ j,
+ props[j].displayName,
+ props[j].physicalResolution.width,
+ props[j].physicalResolution.height);
+
+ VkDisplayKHR display = props[j].display;
+
+ mp_msg(log, msgl_info, " Modes:\n");
+
+ int num_modes = 0;
+ vkGetDisplayModePropertiesKHR(device, display, &num_modes, NULL);
+ if (!num_modes) {
+ mp_msg(log, msgl_info, " No available modes for display.\n");
+ continue;
+ }
+ if (selector && selector->mode_idx + 1 > num_modes) {
+ mp_msg(log, msgl_err, "Selected mode (%d) not present.\n",
+ selector->mode_idx);
+ goto done;
+ }
+
+ VkDisplayModePropertiesKHR *modes =
+ talloc_array(tmp, VkDisplayModePropertiesKHR, num_modes);
+ res = vkGetDisplayModePropertiesKHR(device, display, &num_modes, modes);
+ if (res != VK_SUCCESS) {
+ mp_msg(log, msgl_err, " Failed enumerating display modes\n");
+ continue;
+ }
+
+ for (int k = 0; k < num_modes; k++) {
+ if (selector && selector->mode_idx != k)
+ continue;
+
+ mp_msg(log, msgl_info, " Mode %02d: %dx%d (%02d.%03d Hz)\n", k,
+ modes[k].parameters.visibleRegion.width,
+ modes[k].parameters.visibleRegion.height,
+ modes[k].parameters.refreshRate / 1000,
+ modes[k].parameters.refreshRate % 1000);
+
+ if (selector)
+ selector->out_mode_props = talloc_dup(NULL, &modes[k]);
+ }
+
+ int found_plane = -1;
+ mp_msg(log, msgl_info, " Planes:\n");
+ for (int k = 0; k < num_planes; k++) {
+ VkDisplayKHR *displays = planes_to_displays[k];
+ if (!displays) {
+ // This plane is not connected to any displays.
+ continue;
+ }
+ for (int d = 0; displays[d]; d++) {
+ if (displays[d] == display) {
+ if (selector && selector->plane_idx != k)
+ continue;
+
+ mp_msg(log, msgl_info, " Plane: %d\n", k);
+ found_plane = k;
+ }
+ }
+ }
+ if (selector && selector->plane_idx != found_plane) {
+ mp_msg(log, msgl_err,
+ "Selected plane (%d) not available on selected display.\n",
+ selector->plane_idx);
+ goto done;
+ }
+ }
+ ret = true;
+done:
+ talloc_free(tmp);
+ return ret;
+}
+
+static int print_display_info(struct mp_log *log, const struct m_option *opt,
+ struct bstr name) {
+ VkResult res;
+ VkPhysicalDevice *devices = NULL;
+
+ // Create a dummy instance to list the resources
+ VkInstanceCreateInfo info = {
+ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ .enabledExtensionCount = 1,
+ .ppEnabledExtensionNames = (const char*[]) {
+ VK_KHR_DISPLAY_EXTENSION_NAME
+ },
+ };
+
+ VkInstance inst = NULL;
+ res = vkCreateInstance(&info, NULL, &inst);
+ if (res != VK_SUCCESS) {
+ mp_warn(log, "Unable to create Vulkan instance.\n");
+ goto done;
+ }
+
+ uint32_t num_devices = 0;
+ vkEnumeratePhysicalDevices(inst, &num_devices, NULL);
+ if (!num_devices) {
+ mp_info(log, "No Vulkan devices detected.\n");
+ goto done;
+ }
+
+ devices = talloc_array(NULL, VkPhysicalDevice, num_devices);
+ vkEnumeratePhysicalDevices(inst, &num_devices, devices);
+ if (res != VK_SUCCESS) {
+ mp_warn(log, "Failed enumerating physical devices.\n");
+ goto done;
+ }
+
+ mp_info(log, "Vulkan Devices:\n");
+ for (int i = 0; i < num_devices; i++) {
+ walk_display_properties(log, MSGL_WARN, devices[i], NULL);
+ }
+
+done:
+ talloc_free(devices);
+ vkDestroyInstance(inst, NULL);
+ return M_OPT_EXIT;
+}
+
+#define OPT_BASE_STRUCT struct vulkan_display_opts
+const struct m_sub_options vulkan_display_conf = {
+ .opts = (const struct m_option[]) {
+ {"vulkan-display-display", OPT_INT(display),
+ .help = print_display_info,
+ },
+ {"vulkan-display-mode", OPT_INT(mode),
+ .help = print_display_info,
+ },
+ {"vulkan-display-plane", OPT_INT(plane),
+ .help = print_display_info,
+ },
+ {0}
+ },
+ .size = sizeof(struct vulkan_display_opts),
+ .defaults = &(struct vulkan_display_opts) {
+ .display = 0,
+ .mode = 0,
+ .plane = 0,
+ },
+};
+
+struct priv {
+ struct mpvk_ctx vk;
+ struct vulkan_display_opts *opts;
+ uint32_t width;
+ uint32_t height;
+};
+
+static void display_uninit(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ ra_vk_ctx_uninit(ctx);
+ mpvk_uninit(&p->vk);
+}
+
+static bool display_init(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
+ struct mpvk_ctx *vk = &p->vk;
+ int msgl = ctx->opts.probing ? MSGL_V : MSGL_ERR;
+ VkResult res;
+ bool ret = false;
+
+ VkDisplayModePropertiesKHR *mode = NULL;
+
+ p->opts = mp_get_config_group(p, ctx->global, &vulkan_display_conf);
+ int display_idx = p->opts->display;
+ int mode_idx = p->opts->mode;
+ int plane_idx = p->opts->plane;
+
+ if (!mpvk_init(vk, ctx, VK_KHR_DISPLAY_EXTENSION_NAME))
+ goto error;
+
+ char *device_name = ra_vk_ctx_get_device_name(ctx);
+ struct pl_vulkan_device_params vulkan_params = {
+ .instance = vk->vkinst->instance,
+ .device_name = device_name,
+ };
+ VkPhysicalDevice device = pl_vulkan_choose_device(vk->ctx, &vulkan_params);
+ talloc_free(device_name);
+ if (!device) {
+ MP_MSG(ctx, msgl, "Failed to open physical device.\n");
+ goto error;
+ }
+
+ struct mode_selector selector = {
+ .display_idx = display_idx,
+ .mode_idx = mode_idx,
+ .plane_idx = plane_idx,
+
+ };
+ if (!walk_display_properties(ctx->log, msgl, device, &selector))
+ goto error;
+ mode = selector.out_mode_props;
+
+ VkDisplaySurfaceCreateInfoKHR xinfo = {
+ .sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR,
+ .displayMode = mode->displayMode,
+ .imageExtent = mode->parameters.visibleRegion,
+ .planeIndex = plane_idx,
+ .transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+ .alphaMode = VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR,
+ };
+
+ res = vkCreateDisplayPlaneSurfaceKHR(vk->vkinst->instance, &xinfo, NULL,
+ &vk->surface);
+ if (res != VK_SUCCESS) {
+ MP_MSG(ctx, msgl, "Failed creating Display surface\n");
+ goto error;
+ }
+
+ p->width = mode->parameters.visibleRegion.width;
+ p->height = mode->parameters.visibleRegion.height;
+
+ struct ra_vk_ctx_params params = {0};
+ if (!ra_vk_ctx_init(ctx, vk, params, VK_PRESENT_MODE_FIFO_KHR))
+ goto error;
+
+ ret = true;
+
+done:
+ talloc_free(mode);
+ return ret;
+
+error:
+ display_uninit(ctx);
+ goto done;
+}
+
+static bool display_reconfig(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ return ra_vk_ctx_resize(ctx, p->width, p->height);
+}
+
+static int display_control(struct ra_ctx *ctx, int *events, int request, void *arg)
+{
+ return VO_NOTIMPL;
+}
+
+static void display_wakeup(struct ra_ctx *ctx)
+{
+ // TODO
+}
+
+static void display_wait_events(struct ra_ctx *ctx, int64_t until_time_us)
+{
+ // TODO
+}
+
+const struct ra_ctx_fns ra_ctx_vulkan_display = {
+ .type = "vulkan",
+ .name = "displayvk",
+ .reconfig = display_reconfig,
+ .control = display_control,
+ .wakeup = display_wakeup,
+ .wait_events = display_wait_events,
+ .init = display_init,
+ .uninit = display_uninit,
+};
diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c
index 98bc8af..d2e6309 100644
--- a/video/out/vulkan/context_wayland.c
+++ b/video/out/vulkan/context_wayland.c
@@ -22,97 +22,15 @@
#include "context.h"
#include "utils.h"
-// Generated from presentation-time.xml
-#include "generated/wayland/presentation-time.h"
-
struct priv {
struct mpvk_ctx vk;
};
-static const struct wp_presentation_feedback_listener feedback_listener;
-
-static void feedback_sync_output(void *data, struct wp_presentation_feedback *fback,
- struct wl_output *output)
-{
-}
-
-static void feedback_presented(void *data, struct wp_presentation_feedback *fback,
- uint32_t tv_sec_hi, uint32_t tv_sec_lo,
- uint32_t tv_nsec, uint32_t refresh_nsec,
- uint32_t seq_hi, uint32_t seq_lo,
- uint32_t flags)
-{
- struct vo_wayland_state *wl = data;
- vo_wayland_sync_shift(wl);
-
- if (fback)
- wp_presentation_feedback_destroy(fback);
-
- // Very similar to oml_sync_control, in this case we assume that every
- // time the compositor receives feedback, a buffer swap has been already
- // been performed.
- //
- // Notes:
- // - tv_sec_lo + tv_sec_hi is the equivalent of oml's ust
- // - seq_lo + seq_hi is the equivalent of oml's msc
- // - these values are updated everytime the compositor receives feedback.
-
- int index = last_available_sync(wl);
- if (index < 0) {
- queue_new_sync(wl);
- index = 0;
- }
- int64_t sec = (uint64_t) tv_sec_lo + ((uint64_t) tv_sec_hi << 32);
- wl->sync[index].ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000;
- wl->sync[index].msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32);
- wl->sync[index].filled = true;
-}
-
-static void feedback_discarded(void *data, struct wp_presentation_feedback *fback)
-{
-}
-
-static const struct wp_presentation_feedback_listener feedback_listener = {
- feedback_sync_output,
- feedback_presented,
- feedback_discarded,
-};
-
-static const struct wl_callback_listener frame_listener;
-
-static void frame_callback(void *data, struct wl_callback *callback, uint32_t time)
-{
- struct vo_wayland_state *wl = data;
-
- if (callback)
- wl_callback_destroy(callback);
-
- wl->frame_callback = wl_surface_frame(wl->surface);
- wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
-
- if (wl->presentation) {
- wl->feedback = wp_presentation_feedback(wl->presentation, wl->surface);
- wp_presentation_feedback_add_listener(wl->feedback, &feedback_listener, wl);
- }
-
- wl->frame_wait = false;
-}
-
-static const struct wl_callback_listener frame_listener = {
- frame_callback,
-};
-
static bool wayland_vk_start_frame(struct ra_ctx *ctx)
{
struct vo_wayland_state *wl = ctx->vo->wl;
-
bool render = !wl->hidden || wl->opts->disable_vsync;
-
- if (wl->frame_wait && wl->presentation)
- vo_wayland_sync_clear(wl);
-
- if (render)
- wl->frame_wait = true;
+ wl->frame_wait = true;
return render;
}
@@ -125,7 +43,7 @@ static void wayland_vk_swap_buffers(struct ra_ctx *ctx)
vo_wayland_wait_frame(wl);
if (wl->presentation)
- wayland_sync_swap(wl);
+ vo_wayland_sync_swap(wl);
}
static void wayland_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
@@ -189,9 +107,6 @@ static bool wayland_vk_init(struct ra_ctx *ctx)
ra_add_native_resource(ctx->ra, "wl", ctx->vo->wl->display);
- ctx->vo->wl->frame_callback = wl_surface_frame(ctx->vo->wl->surface);
- wl_callback_add_listener(ctx->vo->wl->frame_callback, &frame_listener, ctx->vo->wl);
-
return true;
error:
@@ -209,7 +124,6 @@ static bool resize(struct ra_ctx *ctx)
const int32_t height = wl->scaling * mp_rect_h(wl->geometry);
vo_wayland_set_opaque_region(wl, ctx->opts.want_alpha);
- wl_surface_set_buffer_scale(wl->surface, wl->scaling);
return ra_vk_ctx_resize(ctx, width, height);
}
diff --git a/video/out/w32_common.c b/video/out/w32_common.c
index f977854..b899922 100644
--- a/video/out/w32_common.c
+++ b/video/out/w32_common.c
@@ -97,14 +97,17 @@ struct vo_w32_state {
// Has the window seen a WM_DESTROY? If so, don't call DestroyWindow again.
bool destroyed;
+ bool focused;
+
// whether the window position and size were intialized
bool window_bounds_initialized;
bool current_fs;
bool toggle_fs; // whether the current fullscreen state needs to be switched
- RECT windowrc; // currently known window rect
- RECT prev_windowrc; // last non-fullscreen window rect
+ // Note: maximized state doesn't involve nor modify windowrc
+ RECT windowrc; // currently known normal/fullscreen window client rect
+ RECT prev_windowrc; // saved normal window client rect while in fullscreen
// video size
uint32_t o_dwidth;
@@ -129,7 +132,13 @@ struct vo_w32_state {
// UTF-16 decoding state for WM_CHAR and VK_PACKET
int high_surrogate;
- // Whether to fit the window on screen on next window state updating
+ // Fit the window to one monitor working area next time it's not fullscreen
+ // and not maximized. Used once after every new "untrusted" size comes from
+ // mpv, else we assume that the last known size is valid and don't fit.
+ // FIXME: on a multi-monitor setup one bit is not enough, because the first
+ // fit (autofit etc) should be to one monitor, but later size changes from
+ // mpv like window-scale (VOCTRL_SET_UNFS_WINDOW_SIZE) should allow the
+ // entire virtual desktop area - but we still limit to one monitor size.
bool fit_on_screen;
ITaskbarList2 *taskbar_list;
@@ -528,24 +537,26 @@ done:
static void update_dpi(struct vo_w32_state *w32)
{
UINT dpiX, dpiY;
+ HDC hdc = NULL;
+ int dpi = 0;
+
if (w32->api.pGetDpiForMonitor && w32->api.pGetDpiForMonitor(w32->monitor,
MDT_EFFECTIVE_DPI, &dpiX, &dpiY) == S_OK) {
- w32->dpi = (int)dpiX;
- w32->dpi_scale = w32->opts->hidpi_window_scale ? w32->dpi / 96.0 : 1.0;
- MP_VERBOSE(w32, "DPI detected from the new API: %d\n", w32->dpi);
- return;
- }
- HDC hdc = GetDC(NULL);
- if (hdc) {
- w32->dpi = GetDeviceCaps(hdc, LOGPIXELSX);
- w32->dpi_scale = w32->opts->hidpi_window_scale ? w32->dpi / 96.0 : 1.0;
+ dpi = (int)dpiX;
+ MP_VERBOSE(w32, "DPI detected from the new API: %d\n", dpi);
+ } else if ((hdc = GetDC(NULL))) {
+ dpi = GetDeviceCaps(hdc, LOGPIXELSX);
ReleaseDC(NULL, hdc);
- MP_VERBOSE(w32, "DPI detected from the old API: %d\n", w32->dpi);
- } else {
- w32->dpi = 96;
- w32->dpi_scale = 1.0;
- MP_VERBOSE(w32, "Couldn't determine DPI, falling back to %d\n", w32->dpi);
+ MP_VERBOSE(w32, "DPI detected from the old API: %d\n", dpi);
+ }
+
+ if (dpi <= 0) {
+ dpi = 96;
+ MP_VERBOSE(w32, "Couldn't determine DPI, falling back to %d\n", dpi);
}
+
+ w32->dpi = dpi;
+ w32->dpi_scale = w32->opts->hidpi_window_scale ? w32->dpi / 96.0 : 1.0;
}
static void update_display_info(struct vo_w32_state *w32)
@@ -795,13 +806,12 @@ static void update_window_style(struct vo_w32_state *w32)
w32->windowrc = wr;
}
-// Adjust rc size and position if its size is larger than rc2.
+// If rc is wider/taller than n_w/n_h, shrink rc size while keeping the center.
// returns true if the rectangle was modified.
-static bool fit_rect(RECT *rc, RECT *rc2)
+static bool fit_rect_size(RECT *rc, long n_w, long n_h)
{
- // Calculate old size and maximum new size
+ // nothing to do if we already fit.
int o_w = rect_w(*rc), o_h = rect_h(*rc);
- int n_w = rect_w(*rc2), n_h = rect_h(*rc2);
if (o_w <= n_w && o_h <= n_h)
return false;
@@ -821,17 +831,27 @@ static bool fit_rect(RECT *rc, RECT *rc2)
return true;
}
-// Adjust window size and position if its size is larger than the screen size.
+// If the window is bigger than the desktop, shrink to fit with same center.
+// Also, if the top edge is above the working area, move down to align.
static void fit_window_on_screen(struct vo_w32_state *w32)
{
- if (w32->parent || w32->current_fs || IsMaximized(w32->window))
- return;
-
RECT screen = get_working_area(w32);
- if (w32->opts->border && w32->opts->fit_border)
+ if (w32->opts->border)
subtract_window_borders(w32, w32->window, &screen);
- if (fit_rect(&w32->windowrc, &screen)) {
+ bool adjusted = fit_rect_size(&w32->windowrc, rect_w(screen), rect_h(screen));
+
+ if (w32->windowrc.top < screen.top) {
+ // if the top-edge of client area is above the target area (mainly
+ // because the client-area is centered but the title bar is taller
+ // than the bottom border), then move it down to align the edges.
+ // Windows itself applies the same constraint during manual move.
+ w32->windowrc.bottom += screen.top - w32->windowrc.top;
+ w32->windowrc.top = screen.top;
+ adjusted = true;
+ }
+
+ if (adjusted) {
MP_VERBOSE(w32, "adjusted window bounds: %d:%d:%d:%d\n",
(int)w32->windowrc.left, (int)w32->windowrc.top,
(int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
@@ -839,11 +859,10 @@ static void fit_window_on_screen(struct vo_w32_state *w32)
}
// Calculate new fullscreen state and change window size and position.
-// returns true if fullscreen state was changed.
-static bool update_fullscreen_state(struct vo_w32_state *w32)
+static void update_fullscreen_state(struct vo_w32_state *w32)
{
if (w32->parent)
- return false;
+ return;
bool new_fs = w32->opts->fullscreen;
if (w32->toggle_fs) {
@@ -875,7 +894,6 @@ static bool update_fullscreen_state(struct vo_w32_state *w32)
MP_VERBOSE(w32, "reset window bounds: %d:%d:%d:%d\n",
(int)w32->windowrc.left, (int)w32->windowrc.top,
(int)rect_w(w32->windowrc), (int)rect_h(w32->windowrc));
- return toggle_fs;
}
static void update_minimized_state(struct vo_w32_state *w32)
@@ -969,15 +987,13 @@ static void reinit_window_state(struct vo_w32_state *w32)
return;
// The order matters: fs state should be updated prior to changing styles
- bool toggle_fs = update_fullscreen_state(w32);
+ update_fullscreen_state(w32);
update_window_style(w32);
- // Assume that the window has already been fit on screen before switching fs
- if (!toggle_fs || w32->fit_on_screen) {
+ // fit_on_screen is applied at most once when/if applicable (normal win).
+ if (w32->fit_on_screen && !w32->current_fs && !IsMaximized(w32->window)) {
fit_window_on_screen(w32);
- // The fullscreen state might still be active, so set the flag
- // to fit on screen next time the window leaves the fullscreen.
- w32->fit_on_screen = w32->current_fs;
+ w32->fit_on_screen = false;
}
// Show and activate the window after all window state parameters were set
@@ -1198,7 +1214,13 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam,
break;
case WM_KILLFOCUS:
mp_input_put_key(w32->input_ctx, MP_INPUT_RELEASE_ALL);
- break;
+ w32->focused = false;
+ signal_events(w32, VO_EVENT_FOCUS);
+ return 0;
+ case WM_SETFOCUS:
+ w32->focused = true;
+ signal_events(w32, VO_EVENT_FOCUS);
+ return 0;
case WM_SETCURSOR:
// The cursor should only be hidden if the mouse is in the client area
// and if the window isn't in menu mode (HIWORD(lParam) is non-zero)
@@ -1397,10 +1419,18 @@ static void gui_thread_reconfig(void *ptr)
struct vo *vo = w32->vo;
RECT r = get_working_area(w32);
+ if (!w32->current_fs && !IsMaximized(w32->window) && w32->opts->border)
+ subtract_window_borders(w32, w32->window, &r);
struct mp_rect screen = { r.left, r.top, r.right, r.bottom };
struct vo_win_geometry geo;
- vo_calc_window_geometry2(vo, &screen, w32->dpi_scale, &geo);
+ RECT monrc = get_monitor_info(w32).rcMonitor;
+ struct mp_rect mon = { monrc.left, monrc.top, monrc.right, monrc.bottom };
+
+ if (w32->dpi_scale == 0)
+ force_update_display_info(w32);
+
+ vo_calc_window_geometry3(vo, &screen, &mon, w32->dpi_scale, &geo);
vo_apply_window_geometry(vo, &geo);
bool reset_size = w32->o_dwidth != vo->dwidth ||
@@ -1695,6 +1725,10 @@ static int gui_thread_control(struct vo_w32_state *w32, int request, void *arg)
return VO_TRUE;
}
+ case VOCTRL_GET_HIDPI_SCALE: {
+ *(double *)arg