From b2d89253a62e247d85e376e0abbe6ab9083b0f17 Mon Sep 17 00:00:00 2001
From: naglis <827324+naglis@users.noreply.github.com>
Date: Thu, 16 Nov 2023 22:58:07 +0200
Subject: [PATCH 01/16] doc/protocol.rst: mention song id lifetime

---
 doc/protocol.rst | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/doc/protocol.rst b/doc/protocol.rst
index f8eec58e8..6c105c3e2 100644
--- a/doc/protocol.rst
+++ b/doc/protocol.rst
@@ -691,7 +691,8 @@ Song ids on the other hand are stable: an id is assigned to a song
 when it is added, and will stay the same, no matter how much it is
 moved around.  Adding the same song twice will assign different ids to
 them, and a deleted-and-readded song will have a new id.  This way, a
-client can always be sure the correct song is being used.
+client can always be sure the correct song is being used.  Song ids are not
+preserved across :program:`MPD` restarts.
 
 Many commands come in two flavors, one for each address type.
 Whenever possible, ids should be used.

From 1bf7d3062367c4a01344d8ed7e03664ee3f5f0df Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sat, 25 Nov 2023 20:47:09 +0100
Subject: [PATCH 02/16] subprojects: update fmt to 10.1.1-1

---
 subprojects/fmt.wrap | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap
index 0ea7eb386..bc109cc3b 100644
--- a/subprojects/fmt.wrap
+++ b/subprojects/fmt.wrap
@@ -1,12 +1,13 @@
 [wrap-file]
-directory = fmt-9.1.0
-source_url = https://github.com/fmtlib/fmt/archive/9.1.0.tar.gz
-source_filename = fmt-9.1.0.tar.gz
-source_hash = 5dea48d1fcddc3ec571ce2058e13910a0d4a6bab4cc09a809d8b1dd1c88ae6f2
-patch_filename = fmt_9.1.0-1_patch.zip
-patch_url = https://wrapdb.mesonbuild.com/v2/fmt_9.1.0-1/get_patch
-patch_hash = 4557b9ba87b3eb63694ed9b21d1a2117d4a97ca56b91085b10288e9a5294adf8
-wrapdb_version = 9.1.0-1
+directory = fmt-10.1.1
+source_url = https://github.com/fmtlib/fmt/archive/10.1.1.tar.gz
+source_filename = fmt-10.1.1.tar.gz
+source_hash = 78b8c0a72b1c35e4443a7e308df52498252d1cefc2b08c9a97bc9ee6cfe61f8b
+patch_filename = fmt_10.1.1-1_patch.zip
+patch_url = https://wrapdb.mesonbuild.com/v2/fmt_10.1.1-1/get_patch
+patch_hash = adec33acaf87c0859c52b242a44bc71c3427751da3f1adaed511f4186794a42f
+source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_10.1.1-1/fmt-10.1.1.tar.gz
+wrapdb_version = 10.1.1-1
 
 [provide]
 fmt = fmt_dep

From 1802cf9fd1c902bfb931e575ba00606c57dacb9f Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sat, 25 Nov 2023 21:43:45 +0100
Subject: [PATCH 03/16] python/build/cmake.py: add CMAKE_FIND_ROOT_PATH on
 Windows

Works around CURL build failure because cmake insists on using
/usr/include/zlib.h.
---
 python/build/cmake.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/python/build/cmake.py b/python/build/cmake.py
index 74d5f286a..12887d3ba 100644
--- a/python/build/cmake.py
+++ b/python/build/cmake.py
@@ -53,6 +53,14 @@ set(CMAKE_CXX_FLAGS_INIT "{toolchain.cxxflags} {toolchain.cppflags}")
 set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix};{sysroot}")
 set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
 set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+""")
+    elif cmake_system_name == 'Windows':
+            # search libraries and headers only in the sysroot, not on
+            # the build host
+            f.write(f"""
+set(CMAKE_FIND_ROOT_PATH "{toolchain.install_prefix}")
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
 """)
 
 def configure(toolchain: AnyToolchain, src: str, build: str, args: list[str]=[], env: Optional[Mapping[str, str]]=None) -> None:

From 916ab9a7e620396459dcf1c07ff728485e9edae9 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sat, 25 Nov 2023 20:51:50 +0100
Subject: [PATCH 04/16] python/build/libs.py: update openmpt to 0.7.3

---
 python/build/libs.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/python/build/libs.py b/python/build/libs.py
index 15b7a0169..db532f51b 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -116,8 +116,8 @@ libmodplug = AutotoolsProject(
 )
 
 libopenmpt = AutotoolsProject(
-    'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.6+release.autotools.tar.gz',
-    '6ddb9e26a430620944891796fefb1bbb38bd9148f6cfc558810c0d3f269876c7',
+    'https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.7.3+release.autotools.tar.gz',
+    '2cf8369b7916b09264f3f14b9fb6cef35a6e9bee0328dec4f49d98211ccfd722',
     'lib/libopenmpt.a',
     [
         '--disable-shared', '--enable-static',

From 37ee821947ee867c870f7e0e1cbb2bfecdc0f5fa Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sat, 25 Nov 2023 20:52:31 +0100
Subject: [PATCH 05/16] python/build/libs.py: update FFmpeg to 6.1

---
 python/build/libs.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/python/build/libs.py b/python/build/libs.py
index db532f51b..8d0c16397 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -156,8 +156,8 @@ gme = CmakeProject(
 )
 
 ffmpeg = FfmpegProject(
-    'http://ffmpeg.org/releases/ffmpeg-6.0.tar.xz',
-    '57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082',
+    'http://ffmpeg.org/releases/ffmpeg-6.1.tar.xz',
+    '488c76e57dd9b3bee901f71d5c95eaf1db4a5a31fe46a28654e837144207c270',
     'lib/libavcodec.a',
     [
         '--disable-shared', '--enable-static',
@@ -464,6 +464,8 @@ ffmpeg = FfmpegProject(
         '--disable-decoder=pam',
         '--disable-decoder=pbm',
         '--disable-decoder=pcx',
+        '--disable-decoder=pdv',
+        '--disable-decoder=pfm',
         '--disable-decoder=pgm',
         '--disable-decoder=pgmyuv',
         '--disable-decoder=pgssub',

From 3db3e577f18aff085fcdab82582e05314bc5771d Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sat, 25 Nov 2023 20:53:39 +0100
Subject: [PATCH 06/16] python/build/libs.py: update OpenSSL to 3.1.4

---
 python/build/libs.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python/build/libs.py b/python/build/libs.py
index 8d0c16397..19af7f782 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -602,9 +602,9 @@ ffmpeg = FfmpegProject(
 )
 
 openssl = OpenSSLProject(
-    ('https://www.openssl.org/source/openssl-3.1.3.tar.gz',
-     'https://artfiles.org/openssl.org/source/openssl-3.1.3.tar.gz'),
-    'f0316a2ebd89e7f2352976445458689f80302093788c466692fb2a188b2eacf6',
+    ('https://www.openssl.org/source/openssl-3.1.4.tar.gz',
+     'https://artfiles.org/openssl.org/source/openssl-3.1.4.tar.gz'),
+    '840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3',
     'include/openssl/ossl_typ.h',
 )
 

From 0f82f186529e86a985f578343354b5b1d0185aa3 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sat, 25 Nov 2023 20:54:02 +0100
Subject: [PATCH 07/16] python/build/libs.py: update CURL to 8.4.0

---
 python/build/libs.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python/build/libs.py b/python/build/libs.py
index 19af7f782..b029c9146 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -609,9 +609,9 @@ openssl = OpenSSLProject(
 )
 
 curl = CmakeProject(
-    ('https://curl.se/download/curl-8.2.1.tar.xz',
-     'https://github.com/curl/curl/releases/download/curl-8_2_1/curl-8.2.1.tar.xz'),
-    'dd322f6bd0a20e6cebdfd388f69e98c3d183bed792cf4713c8a7ef498cba4894',
+    ('https://curl.se/download/curl-8.4.0.tar.xz',
+     'https://github.com/curl/curl/releases/download/curl-8_4_0/curl-8.4.0.tar.xz'),
+    '16c62a9c4af0f703d28bda6d7bbf37ba47055ad3414d70dec63e2e6336f2a82d',
     'lib/libcurl.a',
     [
         '-DBUILD_CURL_EXE=OFF',

From 17d944f6ce205f00d99682db9681111827cd2867 Mon Sep 17 00:00:00 2001
From: borine <32966433+borine@users.noreply.github.com>
Date: Sat, 6 May 2023 08:40:07 +0100
Subject: [PATCH 08/16] input/plugins/Alsa: limit ALSA buffer time to 2 seconds
 maximum

Some ALSA capture devices can have very large buffers, holding 10
seconds or more audio. Using the maximum buffer size with such
devices leads to unacceptably large, and unnecessary, latency.
Also, some ALSA drivers (e.g. HDA Intel PCH) report an invalid
maximum period size, and the period size that mpd calculates from
the maximum buffer size results in "Invalid argument" error when
applying the hw_params. Note that the "default" capture device on
many cards includes the "dsnoop" plugin which imposes a buffer
size of 16384 frames, so that "alsa://" works OK but
"alsa://plughw" or "alsa://hw" both fail.

Limit the maximum buffer time for ALSA input devices to a more useable
2 seconds, thereby avoiding both the above problems.
---
 NEWS                                  |  2 ++
 src/input/plugins/AlsaInputPlugin.cxx | 13 +++++++++----
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/NEWS b/NEWS
index 48fa2401a..65fdd1002 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,8 @@
 ver 0.23.15 (not yet released)
 * decoder
   - ffmpeg: fix build failure with FFmpeg 6.1
+* output
+  - alsa: limit buffer time to 2 seconds
 
 ver 0.23.14 (2023/10/08)
 * decoder
diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx
index ccf0b10d8..d76359833 100644
--- a/src/input/plugins/AlsaInputPlugin.cxx
+++ b/src/input/plugins/AlsaInputPlugin.cxx
@@ -369,9 +369,14 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
 		 period_size_min, period_size_max,
 		 period_time_min, period_time_max);
 
-	/* choose the maximum possible buffer_size ... */
-	snd_pcm_hw_params_set_buffer_size(capture_handle, hw_params,
-					  buffer_size_max);
+	/* choose the maximum buffer_time up to limit of 2 seconds ... */
+	unsigned buffer_time = buffer_time_max;
+	if (buffer_time > 2000000U)
+		buffer_time = 2000000U;
+	int direction = -1;
+	if ((err = snd_pcm_hw_params_set_buffer_time_near(capture_handle,
+				hw_params, &buffer_time, &direction)) < 0)
+		throw Alsa::MakeError(err, "Cannot set buffer time");
 
 	/* ... and calculate the period_size to have four periods in
 	   one buffer; this way, we get woken up often enough to avoid
@@ -379,7 +384,7 @@ AlsaInputStream::ConfigureCapture(AudioFormat audio_format)
 	snd_pcm_uframes_t buffer_size;
 	if (snd_pcm_hw_params_get_buffer_size(hw_params, &buffer_size) == 0) {
 		snd_pcm_uframes_t period_size = buffer_size / 4;
-		int direction = -1;
+		direction = -1;
 		if ((err = snd_pcm_hw_params_set_period_size_near(capture_handle,
 		                             hw_params, &period_size, &direction)) < 0)
 			throw Alsa::MakeError(err, "Cannot set period size");

From a9467513e1e8a621687918d1f066a0f940c2b413 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Sun, 10 Dec 2023 08:24:03 +0100
Subject: [PATCH 09/16] doc/developer.rst: add missing return type to code
 style sample

---
 doc/developer.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/doc/developer.rst b/doc/developer.rst
index 8cbc8c3b0..d3683a358 100644
--- a/doc/developer.rst
+++ b/doc/developer.rst
@@ -20,6 +20,7 @@ Some example code:
 
 .. code-block:: c
 
+    int
     Foo(const char *abc, int xyz)
     {
         if (abc == nullptr) {

From 94b5b9f370210674bbfeed6074fab7a3856f2a5b Mon Sep 17 00:00:00 2001
From: Colin Edwards <colin@recursivepenguin.com>
Date: Tue, 19 Dec 2023 22:05:03 -0600
Subject: [PATCH 10/16] android: Fix MPD shutdown from settings UI

---
 src/Main.cxx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Main.cxx b/src/Main.cxx
index 6b9bdf641..7992f7cb7 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -669,7 +669,7 @@ JNIEXPORT void JNICALL
 Java_org_musicpd_Bridge_shutdown(JNIEnv *, jclass)
 {
 	if (global_instance != nullptr)
-		global_instance->Break();
+		global_instance->event_loop.InjectBreak();
 }
 
 gcc_visibility_default

From 2fb34697c7dbc170718c04e133c912352cbcde87 Mon Sep 17 00:00:00 2001
From: borine <32966433+borine@users.noreply.github.com>
Date: Fri, 12 May 2023 10:17:45 +0100
Subject: [PATCH 11/16] input/plugins/Alsa: catch all exceptions

snd_pcm_poll_descriptors_revents() may return any error code; the
ALSA docs do not constrain the permitted values. A 'hw' device
will only ever return an error if the pfd array passed in is
invalid (-EINVAL), but other I/O plugins may return arbitary
errors. For example a network-based device may return -EPIPE etc.
The resulting exception thrown by
AlsaNonBlockPcm::DispatchSockets() must be caught to prevent the
mpd process from being aborted.
---
 src/input/plugins/AlsaInputPlugin.cxx | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/src/input/plugins/AlsaInputPlugin.cxx b/src/input/plugins/AlsaInputPlugin.cxx
index d76359833..ba940cce9 100644
--- a/src/input/plugins/AlsaInputPlugin.cxx
+++ b/src/input/plugins/AlsaInputPlugin.cxx
@@ -234,7 +234,7 @@ AlsaInputStream::PrepareSockets() noexcept
 
 void
 AlsaInputStream::DispatchSockets() noexcept
-{
+try {
 	non_block.DispatchSockets(*this, capture_handle);
 
 	const std::scoped_lock<Mutex> protect(mutex);
@@ -253,16 +253,17 @@ AlsaInputStream::DispatchSockets() noexcept
 		if (n_frames == -EAGAIN)
 			return;
 
-		if (Recover(n_frames) < 0) {
-			postponed_exception = std::make_exception_ptr(std::runtime_error("PCM error - stream aborted"));
-			InvokeOnAvailable();
-			return;
-		}
+		if (Recover(n_frames) < 0)
+			throw std::runtime_error("PCM error - stream aborted");
 	}
 
 	size_t nbytes = n_frames * frame_size;
 	CommitWriteBuffer(nbytes);
 }
+catch (...) {
+	postponed_exception = std::current_exception();
+	InvokeOnAvailable();
+}
 
 inline int
 AlsaInputStream::Recover(int err)

From c1c67286d36fae3a3dc8d719c5b5afaefce7122d Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Wed, 20 Dec 2023 13:28:52 +0100
Subject: [PATCH 12/16] python/build/libs.py: update CURL to 8.5.0

---
 python/build/libs.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/python/build/libs.py b/python/build/libs.py
index b029c9146..2bc9674d3 100644
--- a/python/build/libs.py
+++ b/python/build/libs.py
@@ -609,9 +609,9 @@ openssl = OpenSSLProject(
 )
 
 curl = CmakeProject(
-    ('https://curl.se/download/curl-8.4.0.tar.xz',
-     'https://github.com/curl/curl/releases/download/curl-8_4_0/curl-8.4.0.tar.xz'),
-    '16c62a9c4af0f703d28bda6d7bbf37ba47055ad3414d70dec63e2e6336f2a82d',
+    ('https://curl.se/download/curl-8.5.0.tar.xz',
+     'https://github.com/curl/curl/releases/download/curl-8_5_0/curl-8.5.0.tar.xz'),
+    '42ab8db9e20d8290a3b633e7fbb3cec15db34df65fd1015ef8ac1e4723750eeb',
     'lib/libcurl.a',
     [
         '-DBUILD_CURL_EXE=OFF',

From c5d6aa169f82fed2d3ebbe38badedc98fcecacce Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Wed, 20 Dec 2023 13:43:19 +0100
Subject: [PATCH 13/16] lib/curl/patches: refresh
 no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch for 7.85.0

---
 .../no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch  | 24 +++++++++----------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch b/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch
index dfa126374..8c3e6202a 100644
--- a/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch
+++ b/src/lib/curl/patches/no_CMAKE_C_IMPLICIT_LINK_LIBRARIES.patch
@@ -1,13 +1,13 @@
-Index: curl-7.84.0/CMakeLists.txt
+Index: curl-7.85.0/CMakeLists.txt
 ===================================================================
---- curl-7.84.0.orig/CMakeLists.txt
-+++ curl-7.84.0/CMakeLists.txt
-@@ -1536,7 +1536,7 @@ set(includedir              "\${prefix}/
- set(LDFLAGS                 "${CMAKE_SHARED_LINKER_FLAGS}")
- set(LIBCURL_LIBS            "")
- set(libdir                  "${CMAKE_INSTALL_PREFIX}/lib")
--foreach(_lib ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${CURL_LIBS})
-+foreach(_lib ${CURL_LIBS})
-   if(TARGET "${_lib}")
-     set(_libname "${_lib}")
-     get_target_property(_imported "${_libname}" IMPORTED)
+--- curl-7.85.0.orig/CMakeLists.txt
++++ curl-7.85.0/CMakeLists.txt
+@@ -1655,7 +1655,7 @@
+   set(LDFLAGS                 "${CMAKE_SHARED_LINKER_FLAGS}")
+   set(LIBCURL_LIBS            "")
+   set(libdir                  "${CMAKE_INSTALL_PREFIX}/lib")
+-  foreach(_lib ${CMAKE_C_IMPLICIT_LINK_LIBRARIES} ${CURL_LIBS})
++  foreach(_lib ${CURL_LIBS})
+     if(TARGET "${_lib}")
+       set(_libname "${_lib}")
+       get_target_property(_imported "${_libname}" IMPORTED)

From a654c5d643cf1f31263d54d730300d678b942bad Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Wed, 20 Dec 2023 16:15:08 +0100
Subject: [PATCH 14/16] Revert "android: Fix MPD shutdown from settings UI"

This reverts commit 94b5b9f370210674bbfeed6074fab7a3856f2a5b.  It was
not necessary for branch v0.23.x because there, Break() is
thread-safe; this was only changed later by commit
a3b32819b1578a261c1cc40cc384c8af3ed8fb0f
---
 src/Main.cxx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Main.cxx b/src/Main.cxx
index 7992f7cb7..6b9bdf641 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -669,7 +669,7 @@ JNIEXPORT void JNICALL
 Java_org_musicpd_Bridge_shutdown(JNIEnv *, jclass)
 {
 	if (global_instance != nullptr)
-		global_instance->event_loop.InjectBreak();
+		global_instance->Break();
 }
 
 gcc_visibility_default

From 6e6f72a5210085a8c00b1858114744e810215edf Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Wed, 20 Dec 2023 15:12:19 +0100
Subject: [PATCH 15/16] win32/HResult: convert assert() to runtime check to
 work around -Walloc-size-larger-than

---
 src/win32/HResult.cxx | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/win32/HResult.cxx b/src/win32/HResult.cxx
index a4e04caa6..c5e522464 100644
--- a/src/win32/HResult.cxx
+++ b/src/win32/HResult.cxx
@@ -100,9 +100,11 @@ FormatHResultError(HRESULT result, const char *fmt, ...) noexcept
 	va_start(args1, fmt);
 	va_copy(args2, args1);
 
-	const int size = vsnprintf(nullptr, 0, fmt, args1);
+	int size = vsnprintf(nullptr, 0, fmt, args1);
 	va_end(args1);
-	assert(size >= 0);
+
+	if (size < 0)
+		size = 0;
 
 	auto buffer = std::make_unique<char[]>(size + 1);
 	vsprintf(buffer.get(), fmt, args2);

From b8bfc986181a6824b7ee9c4f2c475369ca8ac931 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
Date: Wed, 20 Dec 2023 16:21:57 +0100
Subject: [PATCH 16/16] release v0.23.15

---
 NEWS | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/NEWS b/NEWS
index 65fdd1002..e3585b8ca 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,4 @@
-ver 0.23.15 (not yet released)
+ver 0.23.15 (2023/12/20)
 * decoder
   - ffmpeg: fix build failure with FFmpeg 6.1
 * output