Date: Tue, 5 Nov 2024 07:17:57 -0500
Subject: [PATCH 03/23] README update about HEVC/4K decoding on R Pi
---
README.html | 38 +++++++++++++++++++++++---------------
README.md | 25 ++++++++++++++++---------
README.txt | 36 ++++++++++++++++++++++--------------
3 files changed, 61 insertions(+), 38 deletions(-)
diff --git a/README.html b/README.html
index 35a70091..53ec5ca0 100644
--- a/README.html
+++ b/README.html
@@ -218,13 +218,18 @@ Detailed description of
distributions (Ubuntu, Manjaro) available with Raspberry Pi Imager.
(For GStreamer < 1.22, see the UxPlay
-Wiki).
-
(New): Support for h265 (HEVC) hardware decoding on
-Raspberry Pi (Pi 4 model B and Pi 5)
-Support is present, but so far satisfactory results have not been
-obtained. Pi model 5 only provides hardware-accelerated (GPU) decoding
-for h265 video, but not H264, as its CPU is powerful enough for
-satisfactory software H264 decoding
+Wiki). Pi model 5 has no support for hardware H264 decoding, as
+its CPU is powerful enough for satisfactory software H264
+decoding
+Support for h265 (HEVC) hardware decoding on Raspberry Pi
+(Pi 4 model B and Pi 5)
+These Raspberry Pi models have a dedicated HEVC decoding block (not
+the GPU), with a driver “rpivid” which is not yet in the mainline Linux
+kernel (but is planned to be there in future). Unfortunately it produces
+decoded video in a non-standard pixel format (NC30 or “SAND”) which will
+not be supported by GStreamer until the driver is in the mainline
+kernel; without this support, UxPlay support for HEVC hardware decoding
+on Raspberry Pi will not work.
Note to packagers:
UxPlay’s GPLv3 license does not have an added “GPL exception”
@@ -636,14 +641,17 @@
Starting and running UxPlay
“-vd omxh264dec
”), but this is broken by Pi 4 Model B
firmware. OMX support was removed from Raspberry Pi OS (Bullseye), but
is present in Buster.
-H265 (4K) video is supported with hardware
-decoding by the Broadcom GPU on Raspberry Pi 5 models, as well as on
-Raspberry Pi 4 model B. While GStreamer seem to make use of this
-hardware decoding, satisfactory rendering speed of 4K video by UxPlay on
-these Raspberry Pi models has not yet been acheived. The option
-“-h265” is required for activating h265 support. A wired ethernet
-connection is preferred in this mode (and may be required by the
-client).
+H265 (4K) video is potentially supported by
+hardware decoding on Raspberry Pi 5 models, as well as on Raspberry Pi 4
+model B, using a dedicated HEVC decoding block, but the “rpivid” kernel
+driver for this it not yet supported by GStreamer (this driver decodes
+video into a non-standard format that cannot be supported by GStreamer
+until the driver is in the mainline Linux kernel). Raspberry Pi provides
+a version of ffmpeg that can use that format, but at present UxPlay
+cannot use this. The best solution would be for the driver to be
+“upstreamed” to the kernel, allowing GStreamer support. (Software HEVC
+decoding works, but does not seem to give satisfactory results on the
+Pi).
Even with GPU video decoding, some frames may be dropped by the
lower-power models to keep audio and video synchronized using
diff --git a/README.md b/README.md
index 7f99f2a9..9f92a376 100644
--- a/README.md
+++ b/README.md
@@ -168,13 +168,17 @@ if not, software decoding is used.
so far only included in Raspberry Pi OS, and two other distributions (Ubuntu, Manjaro) available
with Raspberry Pi Imager. _(For GStreamer < 1.22, see
the [UxPlay Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches))_.
-
-* **(New): Support for h265 (HEVC) hardware decoding on Raspberry Pi (Pi 4 model B and Pi 5)**
-
- Support is present, but so far satisfactory results have not been obtained.
- Pi model 5 only provides hardware-accelerated (GPU) decoding for h265 video, but not H264,
+ Pi model 5 has no support for hardware H264 decoding,
as its CPU is powerful enough for satisfactory software H264 decoding
+* **Support for h265 (HEVC) hardware decoding on Raspberry Pi (Pi 4 model B and Pi 5)**
+
+ These Raspberry Pi models have a dedicated HEVC decoding block (not the GPU), with a driver
+ "rpivid" which is not yet in the mainline Linux kernel (but is planned to be there in future). Unfortunately
+ it produces decoded video in a non-standard pixel format (NC30 or "SAND") which will not be supported
+ by GStreamer until the driver is in the mainline kernel; without this support, UxPlay support for HEVC
+ hardware decoding on Raspberry Pi will not work.
+
### Note to packagers:
UxPlay's GPLv3 license does not have an added
@@ -506,10 +510,13 @@ See [Usage](#usage) for more run-time options.
(use option "`-vd omxh264dec`"), but this is broken by Pi 4 Model B firmware. OMX support was removed from
Raspberry Pi OS (Bullseye), but is present in Buster.
-* **H265 (4K)** video is supported with hardware decoding by the Broadcom GPU on Raspberry Pi 5 models, as well as
- on Raspberry Pi 4 model B. **While GStreamer seem to make use of this hardware decoding, satisfactory rendering speed of
- 4K video by UxPlay on these Raspberry Pi models has not yet been acheived.** The option "-h265" is required for activating h265 support.
- A wired ethernet connection is preferred in this mode (and may be required by the client).
+* **H265 (4K)** video is potentially supported by hardware decoding on Raspberry Pi 5 models, as well as
+ on Raspberry Pi 4 model B, using a dedicated HEVC decoding block, but the "rpivid" kernel driver for this
+ it not yet supported by GStreamer (this driver decodes video into a non-standard format that cannot be supported
+ by GStreamer until the driver is in the mainline Linux kernel). Raspberry Pi provides a version of ffmpeg that
+ can use that format, but at present UxPlay cannot use this. The best solution would be for the driver to be
+ "upstreamed" to the kernel, allowing GStreamer support. (Software HEVC decoding works, but does not seem to
+ give satisfactory results on the Pi).
Even with GPU video decoding, some frames may be dropped by the lower-power models to keep audio and video synchronized
using timestamps. In Legacy Raspberry Pi OS (Bullseye), raspi-config "Performance Options" allows specifying how much memory
diff --git a/README.txt b/README.txt
index 1a90a453..7ed5cdf2 100644
--- a/README.txt
+++ b/README.txt
@@ -212,14 +212,19 @@ used.
available with Raspberry Pi Imager. *(For GStreamer \< 1.22, see the
[UxPlay
Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches))*.
+ Pi model 5 has no support for hardware H264 decoding, as its CPU is
+ powerful enough for satisfactory software H264 decoding
-- **(New): Support for h265 (HEVC) hardware decoding on Raspberry Pi
- (Pi 4 model B and Pi 5)**
+- **Support for h265 (HEVC) hardware decoding on Raspberry Pi (Pi 4
+ model B and Pi 5)**
- Support is present, but so far satisfactory results have not been
- obtained. Pi model 5 only provides hardware-accelerated (GPU)
- decoding for h265 video, but not H264, as its CPU is powerful enough
- for satisfactory software H264 decoding
+ These Raspberry Pi models have a dedicated HEVC decoding block (not
+ the GPU), with a driver "rpivid" which is not yet in the mainline
+ Linux kernel (but is planned to be there in future). Unfortunately
+ it produces decoded video in a non-standard pixel format (NC30 or
+ "SAND") which will not be supported by GStreamer until the driver is
+ in the mainline kernel; without this support, UxPlay support for
+ HEVC hardware decoding on Raspberry Pi will not work.
### Note to packagers:
@@ -632,14 +637,17 @@ See [Usage](#usage) for more run-time options.
this is broken by Pi 4 Model B firmware. OMX support was removed
from Raspberry Pi OS (Bullseye), but is present in Buster.
-- **H265 (4K)** video is supported with hardware decoding by the
- Broadcom GPU on Raspberry Pi 5 models, as well as on Raspberry Pi 4
- model B. **While GStreamer seem to make use of this hardware
- decoding, satisfactory rendering speed of 4K video by UxPlay on
- these Raspberry Pi models has not yet been acheived.** The option
- "-h265" is required for activating h265 support. A wired ethernet
- connection is preferred in this mode (and may be required by the
- client).
+- **H265 (4K)** video is potentially supported by hardware decoding on
+ Raspberry Pi 5 models, as well as on Raspberry Pi 4 model B, using a
+ dedicated HEVC decoding block, but the "rpivid" kernel driver for
+ this it not yet supported by GStreamer (this driver decodes video
+ into a non-standard format that cannot be supported by GStreamer
+ until the driver is in the mainline Linux kernel). Raspberry Pi
+ provides a version of ffmpeg that can use that format, but at
+ present UxPlay cannot use this. The best solution would be for the
+ driver to be "upstreamed" to the kernel, allowing GStreamer support.
+ (Software HEVC decoding works, but does not seem to give
+ satisfactory results on the Pi).
Even with GPU video decoding, some frames may be dropped by the
lower-power models to keep audio and video synchronized using
From 47d598cfa2878db47501e4df822c894c8a32545f Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 5 Nov 2024 08:11:34 -0500
Subject: [PATCH 04/23] whitespace issue
---
renderers/video_renderer.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 194e56d6..21655ff9 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -249,7 +249,8 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, video
} else {
free(renderer_type[0]->gst_window);
renderer_type[0]->gst_window = NULL;
- } } else if (renderer_type[0]->use_x11) {
+ }
+ } else if (renderer_type[0]->use_x11) {
renderer_type[i]->gst_window = (X11_Window_t *) calloc(1, sizeof(X11_Window_t));
g_assert(renderer_type[i]->gst_window);
memcpy(renderer_type[i]->gst_window, renderer_type[0]->gst_window, sizeof(X11_Window_t));
From 8018cdc61dac386c5fd05ed129ec5e8641a699e5 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sun, 10 Nov 2024 16:02:35 -0500
Subject: [PATCH 05/23] remove dead code
---
renderers/video_renderer.c | 1 -
renderers/video_renderer.h | 5 -----
2 files changed, 6 deletions(-)
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 21655ff9..8ec20752 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -42,7 +42,6 @@ static bool sync = false;
static bool auto_videosink = true;
static bool logger_debug = false;
static bool video_terminate = false;
-static user_data_t user_data;
#define NCODECS 2 /* renderers for h264 and h265 */
diff --git a/renderers/video_renderer.h b/renderers/video_renderer.h
index fa2cb2ee..23c89388 100644
--- a/renderers/video_renderer.h
+++ b/renderers/video_renderer.h
@@ -46,11 +46,6 @@ typedef enum videoflip_e {
} videoflip_t;
typedef struct video_renderer_s video_renderer_t;
-
-typedef struct user_data_s {
- int type;
- GMainLoop *loop;
-} user_data_t;
void video_renderer_init(logger_t *render_logger, const char *server_name, videoflip_t videoflip[2], const char *parser,
const char *decoder, const char *converter, const char *videosink, const char *videosin_options,
From b51ac52656932e5bd813723d55e1466281e4d98c Mon Sep 17 00:00:00 2001
From: fduncanh <72711181+fduncanh@users.noreply.github.com>
Date: Wed, 13 Nov 2024 17:14:10 -0500
Subject: [PATCH 06/23] Update README.md
---
README.html | 8 ++++----
README.md | 2 +-
README.txt | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/README.html b/README.html
index 53ec5ca0..73c93fe8 100644
--- a/README.html
+++ b/README.html
@@ -942,10 +942,10 @@ Usage
correct version will be used in each pipeline. A wired Client-Server
ethernet connection is preferred over Wifi for 4K video, and might be
required by the client. Only recent Apple devices (M1/M2 Macs or iPads,
-and some iPhones) can send h265 video if a resolut “-s wxh” with h >
-1080 is requested. The “-h265” option changes the default resolution
-(“-s” option) from 1920x1080 to 3840x2160, and leaves default maximum
-framerate (“-fps” option) at 30fps.
+and some iPhones) can send h265 video if a resolution “-s wxh” with h
+> 1080 is requested. The “-h265” option changes the default
+resolution (“-s” option) from 1920x1080 to 3840x2160, and leaves default
+maximum framerate (“-fps” option) at 30fps.
-pin [nnnn]: (since v1.67) use Apple-style
(one-time) “pin” authentication when a new client connects for the first
time: a four-digit pin code is displayed on the terminal, and the client
diff --git a/README.md b/README.md
index 9f92a376..d552eaff 100644
--- a/README.md
+++ b/README.md
@@ -746,7 +746,7 @@ with "`#`" are treated as comments, and ignored. Command line options supersede
video (1080p) in screen-mirror mode. When this option is used, two "video pipelines" (one for h264, one for h265) are created.
If any GStreamer plugins in the pipeline are specific for h264 or h265, the correct version will be used in each pipeline.
A wired Client-Server ethernet connection is preferred over Wifi for 4K video, and might be required by the client. Only recent Apple devices
- (M1/M2 Macs or iPads, and some iPhones) can send h265 video if a resolut "-s wxh" with h > 1080 is requested.
+ (M1/M2 Macs or iPads, and some iPhones) can send h265 video if a resolution "-s wxh" with h > 1080 is requested.
The "-h265" option changes the default resolution ("-s" option) from 1920x1080 to 3840x2160, and leaves default maximum
framerate ("-fps" option) at 30fps.
diff --git a/README.txt b/README.txt
index 7ed5cdf2..9bfa07c0 100644
--- a/README.txt
+++ b/README.txt
@@ -945,8 +945,8 @@ the pipeline are specific for h264 or h265, the correct version will be
used in each pipeline. A wired Client-Server ethernet connection is
preferred over Wifi for 4K video, and might be required by the client.
Only recent Apple devices (M1/M2 Macs or iPads, and some iPhones) can
-send h265 video if a resolut "-s wxh" with h \> 1080 is requested. The
-"-h265" option changes the default resolution ("-s" option) from
+send h265 video if a resolution "-s wxh" with h \> 1080 is requested.
+The "-h265" option changes the default resolution ("-s" option) from
1920x1080 to 3840x2160, and leaves default maximum framerate ("-fps"
option) at 30fps.
From bc715a411eddd929eecf4b91287125987999c319 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sun, 17 Nov 2024 19:34:37 -0500
Subject: [PATCH 07/23] update README (new MacPorts GStreamer info).
---
README.html | 32 ++++++++++++++++++++------------
README.md | 22 ++++++++++------------
README.txt | 30 ++++++++++++++++++------------
3 files changed, 48 insertions(+), 36 deletions(-)
diff --git a/README.html b/README.html
index 73c93fe8..03155459 100644
--- a/README.html
+++ b/README.html
@@ -746,23 +746,31 @@ Starting and running UxPlay
UxPlay). New: the UxPlay build script will now also detect
Homebrew installations in non-standard locations indicated by the
environment variable $HOMEBREW_PREFIX
.
-Using GStreamer installed from MacPorts: this is
-not recommended, as currently the MacPorts GStreamer is
-old (v1.16.2), unmaintained, and built to use X11:
+Using GStreamer installed from MacPorts : (MacPorts
+is now again supplying current or recent Gstreamer). Before building
+UxPlay, install the MacPorts GStreamer with
+“sudo port install gstreamer1 gstreamer1-gst-plugins-base
”.
+Plugins are installed by
+“sudo port install gstreamer1-gst-plugins-*
” where
+“*
” is “good”, “bad”, (and optionally “ugly”). For the
+libav plugin,
+“sudo port install ffmpeg6 [+nonfree] gstreamer1-gst-libav
”
+(where “+nonfree” is optional, and makes linked GPL binaries
+non-distributable). Unfortunately, the current MacPorts GStreamer build
+(bug or feature?) does not provide the opengl plugin, so the only
+working videosink it provides is osxvideosink. (Hopefully this will be
+corrected). It is also possible to install an X11-based GStreamer
+with MacPorts, (add ” +x11” after “base”, “good” “bad” and “ugly” in the
+plugin names): for X11 support on macOS, compile UxPlay using a special
+cmake option -DUSE_X11=ON
, and run it from an XQuartz
+terminal with -vs ximagesink; older non-retina macs require a lower
+resolution when using X11: uxplay -s 800x600
.
- Instead build
gstreamer yourself if you use MacPorts and do not want to use the
-“Official” Gstreamer binaries.
+“Official” Gstreamer binaries or Macports packages.
-(If you really wish to use the MacPorts GStreamer-1.16.2, install
-pkgconf (“sudo port install pkgconf”), then “sudo port install
-gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good
-gstreamer1-gst-plugins-bad gstreamer1-gst-libav”. For X11 support on
-macOS, compile UxPlay using a special cmake option
--DUSE_X11=ON
, and run it from an XQuartz terminal with -vs
-ximagesink; older non-retina macs require a lower resolution when using
-X11: uxplay -s 800x600
.)
After installing GStreamer, build and install uxplay: open a terminal
and change into the UxPlay source directory (“UxPlay-master” for zipfile
downloads, “UxPlay” for “git clone” downloads) and build/install with
diff --git a/README.md b/README.md
index d552eaff..40563bd6 100644
--- a/README.md
+++ b/README.md
@@ -591,20 +591,18 @@ their location (Homebrew does not supply a complete GStreamer, but seems to have
the environment variable `$HOMEBREW_PREFIX`.**
-**Using GStreamer installed from MacPorts**: this is **not** recommended, as currently the MacPorts GStreamer
-is old (v1.16.2), unmaintained, and built to use X11:
+**Using GStreamer installed from MacPorts** : (MacPorts is now again supplying current or recent Gstreamer).
+Before building UxPlay, install the MacPorts GStreamer with "`sudo port install gstreamer1 gstreamer1-gst-plugins-base`". Plugins are
+installed by "`sudo port install gstreamer1-gst-plugins-*`" where "`*`" is "good", "bad", (and optionally "ugly"). For the libav plugin,
+"`sudo port install ffmpeg6 [+nonfree] gstreamer1-gst-libav`" (where "+nonfree" is optional, and makes linked GPL binaries non-distributable).
+Unfortunately, the current MacPorts GStreamer build (bug or feature?) does not provide the opengl plugin, so the only working videosink it provides is
+osxvideosink. (Hopefully this will be corrected). _It is also possible to install an X11-based GStreamer with MacPorts, (add " +x11" after "base", "good"
+"bad" and "ugly" in the plugin names):
+for X11 support on macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and run it from an XQuartz terminal with -vs ximagesink;
+older non-retina macs require a lower resolution when using X11: `uxplay -s 800x600`._
* Instead [build gstreamer yourself](https://github.com/FDH2/UxPlay/wiki/Building-GStreamer-from-Source-on-macOS-with-MacPorts)
-if you use MacPorts and do not want to use the "Official" Gstreamer binaries.
-
-_(If you really wish to use the MacPorts GStreamer-1.16.2,
-install pkgconf ("sudo port install pkgconf"), then
-"sudo port install gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-libav".
-For X11 support on macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and run
-it from an XQuartz terminal with -vs ximagesink; older non-retina macs require a lower resolution
-when using X11: `uxplay -s 800x600`.)_
-
-
+if you use MacPorts and do not want to use the "Official" Gstreamer binaries or Macports packages.
After installing GStreamer, build and install uxplay: open a terminal and change into the UxPlay source directory
("UxPlay-master" for zipfile downloads, "UxPlay" for "git clone" downloads) and build/install with
diff --git a/README.txt b/README.txt
index 9bfa07c0..2c3b56e6 100644
--- a/README.txt
+++ b/README.txt
@@ -744,22 +744,28 @@ complete GStreamer, but seems to have everything needed for UxPlay).
installations in non-standard locations indicated by the environment
variable `$HOMEBREW_PREFIX`.**
-**Using GStreamer installed from MacPorts**: this is **not**
-recommended, as currently the MacPorts GStreamer is old (v1.16.2),
-unmaintained, and built to use X11:
+**Using GStreamer installed from MacPorts** : (MacPorts is now again
+supplying current or recent Gstreamer). Before building UxPlay, install
+the MacPorts GStreamer with
+"`sudo port install gstreamer1 gstreamer1-gst-plugins-base`". Plugins
+are installed by "`sudo port install gstreamer1-gst-plugins-*`" where
+"`*`" is "good", "bad", (and optionally "ugly"). For the libav plugin,
+"`sudo port install ffmpeg6 [+nonfree] gstreamer1-gst-libav`" (where
+"+nonfree" is optional, and makes linked GPL binaries
+non-distributable). Unfortunately, the current MacPorts GStreamer build
+(bug or feature?) does not provide the opengl plugin, so the only
+working videosink it provides is osxvideosink. (Hopefully this will be
+corrected). *It is also possible to install an X11-based GStreamer with
+MacPorts, (add " +x11" after "base", "good" "bad" and "ugly" in the
+plugin names): for X11 support on macOS, compile UxPlay using a special
+cmake option `-DUSE_X11=ON`, and run it from an XQuartz terminal with
+-vs ximagesink; older non-retina macs require a lower resolution when
+using X11: `uxplay -s 800x600`.*
- Instead [build gstreamer
yourself](https://github.com/FDH2/UxPlay/wiki/Building-GStreamer-from-Source-on-macOS-with-MacPorts)
if you use MacPorts and do not want to use the "Official" Gstreamer
- binaries.
-
-*(If you really wish to use the MacPorts GStreamer-1.16.2, install
-pkgconf ("sudo port install pkgconf"), then "sudo port install
-gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good
-gstreamer1-gst-plugins-bad gstreamer1-gst-libav". For X11 support on
-macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and
-run it from an XQuartz terminal with -vs ximagesink; older non-retina
-macs require a lower resolution when using X11: `uxplay -s 800x600`.)*
+ binaries or Macports packages.
After installing GStreamer, build and install uxplay: open a terminal
and change into the UxPlay source directory ("UxPlay-master" for zipfile
From 078d95bb7986d10607b9e0e53b9558f8e690dcad Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Fri, 22 Nov 2024 01:39:43 -0500
Subject: [PATCH 08/23] fix broken support for Ubuntu-20.04LTS
---
lib/CMakeLists.txt | 14 ++++++++++----
renderers/video_renderer.c | 18 +++++++++++++-----
2 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index f02a5e74..520c5bbc 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -82,22 +82,28 @@ else()
endif()
# libplist
-pkg_search_module(PLIST REQUIRED libplist-2.0)
-if ( PLIST_FOUND )
- message( STATUS "found libplist-${PLIST_VERSION}" )
-endif()
+
if( APPLE )
# use static linking
+ pkg_search_module(PLIST REQUIRED libplist-2.0)
find_library( LIBPLIST libplist-2.0.a REQUIRED )
message( STATUS "(Static linking) LIBPLIST " ${LIBPLIST} )
target_link_libraries ( airplay ${LIBPLIST} )
elseif( WIN32)
+pkg_search_module(PLIST REQUIRED libplist-2.0)
find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} )
target_link_libraries ( airplay ${LIBPLIST} )
else ()
+ pkg_search_module(PLIST libplist>=2.0)
+ if(NOT PLIST_FOUND)
+ pkg_search_module(PLIST REQUIRED libplist-2.0)
+ endif()
find_library( LIBPLIST ${PLIST_LIBRARIES} PATH ${PLIST_LIBDIR} )
target_link_libraries ( airplay PUBLIC ${LIBPLIST} )
endif()
+if ( PLIST_FOUND )
+ message( STATUS "found libplist-${PLIST_VERSION}" )
+endif()
target_include_directories( airplay PRIVATE ${PLIST_INCLUDE_DIRS} )
#libcrypto
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 8ec20752..3846a9b5 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -206,11 +206,19 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, video
sync = false;
}
- if (!strcmp(renderer_type[i]->codec, h265)) {
- g_string_replace (launch, (const gchar *) h264, (const gchar *) h265, 0);
- } else {
- g_string_replace (launch, (const gchar *) h265, (const gchar *) h264, 0);
- }
+ if (!strcmp(renderer_type[i]->codec, h264)) {
+ char *pos = launch->str;
+ while ((pos = strstr(pos,h265))){
+ pos +=3;
+ *pos = '4';
+ }
+ } else if (!strcmp(renderer_type[i]->codec, h265)) {
+ char *pos = launch->str;
+ while ((pos = strstr(pos,h264))){
+ pos +=3;
+ *pos = '5';
+ }
+ }
logger_log(logger, LOGGER_DEBUG, "GStreamer video pipeline %d:\n\"%s\"", i + 1, launch->str);
renderer_type[i]->pipeline = gst_parse_launch(launch->str, &error);
From d9a81f7ace18100dfd60b4347f75d41ef8cfa980 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sun, 8 Dec 2024 00:26:58 -0500
Subject: [PATCH 09/23] raop_ntp: fix recv timeout + socket error handling in
Windows
---
lib/compat.c | 36 ++++++++++++++++++++++++++++++++++++
lib/raop_ntp.c | 12 ++++++++----
lib/raop_rtp_mirror.c | 32 ++++++++++++++++++++------------
lib/sockets.h | 6 +++++-
4 files changed, 69 insertions(+), 17 deletions(-)
create mode 100644 lib/compat.c
diff --git a/lib/compat.c b/lib/compat.c
new file mode 100644
index 00000000..cba80fcc
--- /dev/null
+++ b/lib/compat.c
@@ -0,0 +1,36 @@
+
+/*
+ * Copyright (c) 2024 F. Duncanh, All Rights Reserved.
+ *
+ * This library 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.
+ *
+ * This library 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.
+ *
+ *==================================================================
+ */
+
+#ifdef _WIN32
+#include
+#include
+#include "compat.h"
+
+#define MAX_SOCKET_ERROR_MESSAGE_LENGTH 256
+
+/* Windows (winsock2) socket error message text */
+char *wsa_strerror(int errnum) {
+ static char message[MAX_SOCKET_ERROR_MESSAGE_LENGTH] = { 0 };
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
+ 0, errnum, 0, message, sizeof(message), 0);
+ char *nl = strchr(message, '\n');
+ if (nl) {
+ *nl = 0; /* remove any trailing newline, or truncate to one line */
+ }
+ return message;
+}
+#endif
diff --git a/lib/raop_ntp.c b/lib/raop_ntp.c
index 3f180273..1a932e41 100644
--- a/lib/raop_ntp.c
+++ b/lib/raop_ntp.c
@@ -214,10 +214,14 @@ raop_ntp_init_socket(raop_ntp_t *raop_ntp, int use_ipv6)
}
// We're calling recvfrom without knowing whether there is any data, so we need a timeout
-
+ uint32_t recv_timeout_msec = 300;
+#ifdef _WIN32
+ DWORD tv = recv_timeout_msec;
+#else
struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 300000;
+ tv.tv_sec = recv_timeout_msec / (uint32_t) 1000;
+ tv.tv_usec = ((uint32_t) 1000) * (recv_timeout_msec % (uint32_t) 1000);
+#endif
if (setsockopt(tsock, SOL_SOCKET, SO_RCVTIMEO, CAST &tv, sizeof(tv)) < 0) {
goto sockets_cleanup;
}
@@ -299,7 +303,7 @@ raop_ntp_thread(void *arg)
if (send_len < 0) {
int sock_err = SOCKET_GET_ERROR();
logger_log(raop_ntp->logger, LOGGER_ERR, "raop_ntp error sending request. Error %d:%s",
- sock_err, strerror(sock_err));
+ sock_err, SOCKET_ERROR_STRING(sock_err));
} else {
// Read response
response_len = recvfrom(raop_ntp->tsock, (char *)response, sizeof(response), 0, NULL, NULL);
diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c
index a39cbeed..e97f66f0 100644
--- a/lib/raop_rtp_mirror.c
+++ b/lib/raop_rtp_mirror.c
@@ -245,8 +245,9 @@ raop_rtp_mirror_thread(void *arg)
saddrlen = sizeof(saddr);
stream_fd = accept(raop_rtp_mirror->mirror_data_sock, (struct sockaddr *)&saddr, &saddrlen);
if (stream_fd == -1) {
+ int sock_err = SOCKET_GET_ERROR();
logger_log(raop_rtp_mirror->logger, LOGGER_ERR,
- "raop_rtp_mirror error in accept %d %s", errno, strerror(errno));
+ "raop_rtp_mirror error in accept %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
break;
}
@@ -255,31 +256,36 @@ raop_rtp_mirror_thread(void *arg)
tv.tv_sec = 0;
tv.tv_usec = 5000;
if (setsockopt(stream_fd, SOL_SOCKET, SO_RCVTIMEO, CAST &tv, sizeof(tv)) < 0) {
+ int sock_err = SOCKET_GET_ERROR();
logger_log(raop_rtp_mirror->logger, LOGGER_ERR,
- "raop_rtp_mirror could not set stream socket timeout %d %s", errno, strerror(errno));
+ "raop_rtp_mirror could not set stream socket timeout %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
break;
}
int option;
option = 1;
if (setsockopt(stream_fd, SOL_SOCKET, SO_KEEPALIVE, CAST &option, sizeof(option)) < 0) {
+ int sock_err = SOCKET_GET_ERROR();
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING,
- "raop_rtp_mirror could not set stream socket keepalive %d %s", errno, strerror(errno));
+ "raop_rtp_mirror could not set stream socket keepalive %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
}
option = 60;
if (setsockopt(stream_fd, SOL_TCP, TCP_KEEPIDLE, CAST &option, sizeof(option)) < 0) {
+ int sock_err = SOCKET_GET_ERROR();
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING,
- "raop_rtp_mirror could not set stream socket keepalive time %d %s", errno, strerror(errno));
+ "raop_rtp_mirror could not set stream socket keepalive time %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
}
option = 10;
if (setsockopt(stream_fd, SOL_TCP, TCP_KEEPINTVL, CAST &option, sizeof(option)) < 0) {
+ int sock_err = SOCKET_GET_ERROR();
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING,
- "raop_rtp_mirror could not set stream socket keepalive interval %d %s", errno, strerror(errno));
+ "raop_rtp_mirror could not set stream socket keepalive interval %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
}
option = 6;
if (setsockopt(stream_fd, SOL_TCP, TCP_KEEPCNT, CAST &option, sizeof(option)) < 0) {
+ int sock_err = SOCKET_GET_ERROR();
logger_log(raop_rtp_mirror->logger, LOGGER_WARNING,
- "raop_rtp_mirror could not set stream socket keepalive probes %d %s", errno, strerror(errno));
+ "raop_rtp_mirror could not set stream socket keepalive probes %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
}
readstart = 0;
}
@@ -301,10 +307,11 @@ raop_rtp_mirror_thread(void *arg)
stream_fd = -1;
continue;
} else if (payload == NULL && ret == -1) {
- if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // Timeouts can happen even if the connection is fine
+ int sock_err = SOCKET_GET_ERROR();
+ if (sock_err == SOCKET_ERRORNAME(EAGAIN) || sock_err == SOCKET_ERRORNAME(EWOULDBLOCK)) continue; // Timeouts can happen even if the connection is fine
logger_log(raop_rtp_mirror->logger, LOGGER_ERR,
- "raop_rtp_mirror error in header recv: %d %s", errno, strerror(errno));
- if (errno == ECONNRESET) conn_reset = true;;
+ "raop_rtp_mirror error in header recv: %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
+ if (sock_err == SOCKET_ERRORNAME(ECONNRESET)) conn_reset = true;;
break;
}
@@ -364,9 +371,10 @@ raop_rtp_mirror_thread(void *arg)
logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror tcp socket was closed by client (recv returned 0)");
break;
} else if (ret == -1) {
- if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // Timeouts can happen even if the connection is fine
- logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in recv: %d %s", errno, strerror(errno));
- if (errno == ECONNRESET) conn_reset = true;
+ int sock_err = SOCKET_GET_ERROR();
+ if (sock_err == SOCKET_ERRORNAME(EAGAIN) || sock_err == SOCKET_ERRORNAME(EWOULDBLOCK)) continue; // Timeouts can happen even if the connection is fine
+ logger_log(raop_rtp_mirror->logger, LOGGER_ERR, "raop_rtp_mirror error in recv: %d %s", sock_err, SOCKET_ERROR_STRING(sock_err));
+ if (errno == SOCKET_ERRORNAME(ECONNRESET)) conn_reset = true;
break;
}
diff --git a/lib/sockets.h b/lib/sockets.h
index ece6d683..7fb2585a 100644
--- a/lib/sockets.h
+++ b/lib/sockets.h
@@ -16,6 +16,9 @@
#define SOCKETS_H
#if defined(WIN32)
+
+char *wsa_strerror(int errnum);
+
typedef int socklen_t;
#ifndef SHUT_RD
@@ -31,6 +34,7 @@ typedef int socklen_t;
#define SOCKET_GET_ERROR() WSAGetLastError()
#define SOCKET_SET_ERROR(value) WSASetLastError(value)
#define SOCKET_ERRORNAME(name) WSA##name
+#define SOCKET_ERROR_STRING(errnum) wsa_strerror(errnum)
#define WSAEAGAIN WSAEWOULDBLOCK
#define WSAENOMEM WSA_NOT_ENOUGH_MEMORY
@@ -43,7 +47,7 @@ typedef int socklen_t;
#define SOCKET_GET_ERROR() (errno)
#define SOCKET_SET_ERROR(value) (errno = (value))
#define SOCKET_ERRORNAME(name) name
-
+#define SOCKET_ERROR_STRING(errnum) strerror(errnum)
#endif
#endif
From 9aaead374868c1502ba85f792ec670d1fb87e0d0 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Mon, 9 Dec 2024 18:31:01 -0500
Subject: [PATCH 10/23] cosmetic changes to x_display_fix.h
---
renderers/x_display_fix.h | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/renderers/x_display_fix.h b/renderers/x_display_fix.h
index 8af3429a..90d5180c 100644
--- a/renderers/x_display_fix.h
+++ b/renderers/x_display_fix.h
@@ -23,6 +23,9 @@
/* based on code from David Ventura https://github.com/DavidVentura/UxPlay */
+/* This file should be only included from video_renderer.c as it defines static
+ * functions and depends on video_renderer internals */
+
#ifndef X_DISPLAY_FIX_H
#define X_DISPLAY_FIX_H
@@ -40,12 +43,12 @@ struct X11_Window_s {
Window window;
} typedef X11_Window_t;
-void get_X11_Display(X11_Window_t * X11) {
+static void get_X11_Display(X11_Window_t * X11) {
X11->display = XOpenDisplay(NULL);
X11->window = (Window) NULL;
}
-Window enum_windows(const char * str, Display * display, Window window, int depth) {
+static Window enum_windows(const char * str, Display * display, Window window, int depth) {
int i;
XTextProperty text;
XGetWMName(display, window, &text);
@@ -73,7 +76,7 @@ int X11_error_catcher( Display *disp, XErrorEvent *xe ) {
return 0;
}
-void get_x_window(X11_Window_t * X11, const char * name) {
+static void get_x_window(X11_Window_t * X11, const char * name) {
Window root = XDefaultRootWindow(X11->display);
XSetErrorHandler(X11_error_catcher);
X11->window = enum_windows(name, X11->display, root, 0);
@@ -89,7 +92,7 @@ void get_x_window(X11_Window_t * X11, const char * name) {
#endif
}
-void set_fullscreen(X11_Window_t * X11, bool * fullscreen) {
+static void set_fullscreen(X11_Window_t * X11, bool * fullscreen) {
XClientMessageEvent msg = {
.type = ClientMessage,
.display = X11->display,
From 08c6f0cdb06649df7fe8f8512f12dc5693d9caeb Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 10 Dec 2024 01:36:32 -0500
Subject: [PATCH 11/23] UxPlay 1.71: add support for HLS streaming video
---
README.html | 79 ++-
README.md | 54 +-
README.txt | 79 ++-
lib/airplay_video.c | 305 +++++++++++
lib/airplay_video.h | 74 +++
lib/fcup_request.h | 112 ++++
lib/http_handlers.h | 1001 ++++++++++++++++++++++++++++++++++++
lib/http_request.c | 37 +-
lib/http_request.h | 5 +-
lib/http_response.c | 15 +
lib/http_response.h | 2 +
lib/httpd.c | 167 +++++-
lib/httpd.h | 9 +-
lib/raop.c | 332 +++++++++---
lib/raop.h | 40 +-
lib/raop_handlers.h | 8 +-
lib/utils.c | 11 +
lib/utils.h | 1 +
renderers/video_renderer.c | 437 +++++++++++-----
renderers/video_renderer.h | 15 +-
uxplay.1 | 6 +-
uxplay.cpp | 133 ++++-
uxplay.spec | 4 +-
23 files changed, 2562 insertions(+), 364 deletions(-)
create mode 100644 lib/airplay_video.c
create mode 100644 lib/airplay_video.h
create mode 100644 lib/fcup_request.h
create mode 100644 lib/http_handlers.h
diff --git a/README.html b/README.html
index 03155459..20814da6 100644
--- a/README.html
+++ b/README.html
@@ -1,6 +1,6 @@
UxPlay
-1.70: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
+id="uxplay-1.71-airplay-mirror-and-airplay-audio-server-for-linux-macos-and-unix-now-also-runs-on-windows.">UxPlay
+1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix
(now also runs on Windows).
Now
@@ -9,23 +9,17 @@
(where ALL user issues should be posted, and latest versions can be
found).
-- NEW in v1.70: Support for 4k (h265) video with
-the new “-h265” option. (Recent Apple devices will send HEVC (h265)
-video in AirPlay mirror mode if larger resolutions (h >
-1080) are requested with UxPlay’s “-s wxh” option; wired ethernet
-connection is prefered to wireless in this mode, and may also be
-required by the client; the “-h265” option changes the default
-resolution from 1920x1080 to 3840x2160, but leaves default maximum
-framerate (“-fps” option) at 30fps.)
+- NEW in v1.71: Support for (YouTube) HLS (HTTP
+Live Streaming) video with the new “-hls” option. Click on the
+airplay icon in the YouTube app to stream video.
Highlights:
- GPLv3, open source.
- Originally supported only AirPlay Mirror protocol, now has added
support for AirPlay Audio-only (Apple Lossless ALAC) streaming from
-current iOS/iPadOS clients. There is no current support for
-Airplay HLS video-streaming (e.g., YouTube video) but this is in
-development.
+current iOS/iPadOS clients. Now with support for Airplay HLS
+video-streaming (currently only YouTube video).
- macOS computers (2011 or later, both Intel and “Apple Silicon” M1/M2
systems) can act either as AirPlay clients, or as the server running
UxPlay. Using AirPlay, UxPlay can emulate a second display for macOS
@@ -169,16 +163,15 @@
Detailed description of
Note that Apple video-DRM (as found in “Apple TV app”
content on the client) cannot be decrypted by UxPlay, and the Apple TV
app cannot be watched using UxPlay’s AirPlay Mirror mode (only the
-unprotected audio will be streamed, in AAC format), but both video and
-audio content from DRM-free apps like “YouTube app” will be streamed by
-UxPlay in Mirror mode.
-As UxPlay does not currently support non-Mirror AirPlay
-video streaming (where the client controls a web server on the AirPlay
-server that directly receives HLS content to avoid it being decoded and
-re-encoded by the client), using the icon for AirPlay video in apps such
-as the YouTube app will only send audio (in lossless ALAC format)
-without the accompanying video (there are plans to support HLS video in
-future releases of UxPlay)
+unprotected audio will be streamed, in AAC format).
+With the new “-hls” option, UxPlay now also supports
+non-Mirror AirPlay video streaming (where the client controls a web
+server on the AirPlay server that directly receives HLS content to avoid
+it being decoded and re-encoded by the client). This currently only
+supports streaming of YouTube videos. Without the -hls option, using the
+icon for AirPlay video in apps such as the YouTube app will only send
+audio (in lossless ALAC format) without the accompanying
+video.
Possibility
@@ -644,7 +637,7 @@ Starting and running UxPlay
H265 (4K) video is potentially supported by
hardware decoding on Raspberry Pi 5 models, as well as on Raspberry Pi 4
model B, using a dedicated HEVC decoding block, but the “rpivid” kernel
-driver for this it not yet supported by GStreamer (this driver decodes
+driver for this is not yet supported by GStreamer (this driver decodes
video into a non-standard format that cannot be supported by GStreamer
until the driver is in the mainline Linux kernel). Raspberry Pi provides
a version of ffmpeg that can use that format, but at present UxPlay
@@ -746,31 +739,23 @@
Starting and running UxPlay
UxPlay). New: the UxPlay build script will now also detect
Homebrew installations in non-standard locations indicated by the
environment variable $HOMEBREW_PREFIX
.
-Using GStreamer installed from MacPorts : (MacPorts
-is now again supplying current or recent Gstreamer). Before building
-UxPlay, install the MacPorts GStreamer with
-“sudo port install gstreamer1 gstreamer1-gst-plugins-base
”.
-Plugins are installed by
-“sudo port install gstreamer1-gst-plugins-*
” where
-“*
” is “good”, “bad”, (and optionally “ugly”). For the
-libav plugin,
-“sudo port install ffmpeg6 [+nonfree] gstreamer1-gst-libav
”
-(where “+nonfree” is optional, and makes linked GPL binaries
-non-distributable). Unfortunately, the current MacPorts GStreamer build
-(bug or feature?) does not provide the opengl plugin, so the only
-working videosink it provides is osxvideosink. (Hopefully this will be
-corrected). It is also possible to install an X11-based GStreamer
-with MacPorts, (add ” +x11” after “base”, “good” “bad” and “ugly” in the
-plugin names): for X11 support on macOS, compile UxPlay using a special
-cmake option -DUSE_X11=ON
, and run it from an XQuartz
-terminal with -vs ximagesink; older non-retina macs require a lower
-resolution when using X11: uxplay -s 800x600
.
+Using GStreamer installed from MacPorts: this is
+not recommended, as currently the MacPorts GStreamer is
+old (v1.16.2), unmaintained, and built to use X11:
- Instead build
gstreamer yourself if you use MacPorts and do not want to use the
-“Official” Gstreamer binaries or Macports packages.
+“Official” Gstreamer binaries.
+(If you really wish to use the MacPorts GStreamer-1.16.2, install
+pkgconf (“sudo port install pkgconf”), then “sudo port install
+gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good
+gstreamer1-gst-plugins-bad gstreamer1-gst-libav”. For X11 support on
+macOS, compile UxPlay using a special cmake option
+-DUSE_X11=ON
, and run it from an XQuartz terminal with -vs
+ximagesink; older non-retina macs require a lower resolution when using
+X11: uxplay -s 800x600
.)
After installing GStreamer, build and install uxplay: open a terminal
and change into the UxPlay source directory (“UxPlay-master” for zipfile
downloads, “UxPlay” for “git clone” downloads) and build/install with
@@ -954,6 +939,10 @@
Usage
> 1080 is requested. The “-h265” option changes the default
resolution (“-s” option) from 1920x1080 to 3840x2160, and leaves default
maximum framerate (“-fps” option) at 30fps.
+-hls Activate HTTP Live Streaming support. With this
+option YouTube videos can be streamed directly from YouTube servers to
+UxPlay (without passing through the client) by clicking on the AirPlay
+icon in the YouTube app.
-pin [nnnn]: (since v1.67) use Apple-style
(one-time) “pin” authentication when a new client connects for the first
time: a four-digit pin code is displayed on the terminal, and the client
@@ -1590,6 +1579,8 @@
5. Mirror screen
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.
Changelog
+
1.71 2024-12-10 Add support for HTTP Live Streaming (HLS), initially
+only for YouTube movies
1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
2160). Fix issue with GStreamer >= 1.24 when client sleeps, then
wakes.
diff --git a/README.md b/README.md
index 40563bd6..3bc3adcc 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,16 @@
-# UxPlay 1.70: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
+# UxPlay 1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### **Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).**
- * _**NEW in v1.70**: Support for 4k (h265) video with the new "-h265" option._ (Recent Apple devices will send HEVC (h265) video in AirPlay mirror mode
- if larger resolutions (_h_ > 1080) are requested with UxPlay's "-s wxh" option; wired ethernet connection is prefered to
- wireless in this mode, and may also be required by the client;
- the "-h265" option changes the default resolution from 1920x1080 to 3840x2160, but leaves default maximum framerate ("-fps" option) at 30fps.)
-
+ * _**NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming) video with the new "-hls" option._ Click on the airplay icon in the YouTube app to stream video.
+
## Highlights:
* GPLv3, open source.
* Originally supported only AirPlay Mirror protocol, now has added support
for AirPlay Audio-only (Apple Lossless ALAC) streaming
- from current iOS/iPadOS clients. **There is no current support for Airplay HLS
- video-streaming (e.g., YouTube video) but this is in development.**
+ from current iOS/iPadOS clients. **Now with support for Airplay HLS
+ video-streaming (currently only YouTube video).**
* macOS computers (2011 or later, both Intel and "Apple Silicon" M1/M2
systems) can act either as AirPlay clients, or
as the server running UxPlay. Using AirPlay, UxPlay can
@@ -124,15 +121,14 @@ switch back by initiating a_ **Mirror** _mode connection; cover-art display stop
* **Note that Apple video-DRM
(as found in "Apple TV app" content on the client) cannot be decrypted by UxPlay, and
-the Apple TV app cannot be watched using UxPlay's AirPlay Mirror mode (only the unprotected audio will be streamed, in AAC format),
-but both video and audio content from DRM-free apps like "YouTube app" will be streamed by UxPlay in Mirror mode.**
+the Apple TV app cannot be watched using UxPlay's AirPlay Mirror mode (only the unprotected audio will be streamed, in AAC format).**
-* **As UxPlay does not currently support non-Mirror AirPlay video streaming (where the
+* **With the new "-hls" option, UxPlay now also supports non-Mirror AirPlay video streaming (where the
client controls a web server on the AirPlay server that directly receives
-HLS content to avoid it being decoded and re-encoded by the client),
-using the icon for AirPlay video in apps such as the YouTube app
+HLS content to avoid it being decoded and re-encoded by the client). This currently only supports streaming of YouTube videos.
+Without the -hls option, using the icon for AirPlay video in apps such as the YouTube app
will only send audio (in lossless ALAC format) without the accompanying
-video (there are plans to support HLS video in future releases of UxPlay)**
+video.**
### Possibility for using hardware-accelerated h264/h265 video-decoding, if available.
@@ -512,7 +508,7 @@ See [Usage](#usage) for more run-time options.
* **H265 (4K)** video is potentially supported by hardware decoding on Raspberry Pi 5 models, as well as
on Raspberry Pi 4 model B, using a dedicated HEVC decoding block, but the "rpivid" kernel driver for this
- it not yet supported by GStreamer (this driver decodes video into a non-standard format that cannot be supported
+ is not yet supported by GStreamer (this driver decodes video into a non-standard format that cannot be supported
by GStreamer until the driver is in the mainline Linux kernel). Raspberry Pi provides a version of ffmpeg that
can use that format, but at present UxPlay cannot use this. The best solution would be for the driver to be
"upstreamed" to the kernel, allowing GStreamer support. (Software HEVC decoding works, but does not seem to
@@ -591,18 +587,20 @@ their location (Homebrew does not supply a complete GStreamer, but seems to have
the environment variable `$HOMEBREW_PREFIX`.**
-**Using GStreamer installed from MacPorts** : (MacPorts is now again supplying current or recent Gstreamer).
-Before building UxPlay, install the MacPorts GStreamer with "`sudo port install gstreamer1 gstreamer1-gst-plugins-base`". Plugins are
-installed by "`sudo port install gstreamer1-gst-plugins-*`" where "`*`" is "good", "bad", (and optionally "ugly"). For the libav plugin,
-"`sudo port install ffmpeg6 [+nonfree] gstreamer1-gst-libav`" (where "+nonfree" is optional, and makes linked GPL binaries non-distributable).
-Unfortunately, the current MacPorts GStreamer build (bug or feature?) does not provide the opengl plugin, so the only working videosink it provides is
-osxvideosink. (Hopefully this will be corrected). _It is also possible to install an X11-based GStreamer with MacPorts, (add " +x11" after "base", "good"
-"bad" and "ugly" in the plugin names):
-for X11 support on macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and run it from an XQuartz terminal with -vs ximagesink;
-older non-retina macs require a lower resolution when using X11: `uxplay -s 800x600`._
+**Using GStreamer installed from MacPorts**: this is **not** recommended, as currently the MacPorts GStreamer
+is old (v1.16.2), unmaintained, and built to use X11:
* Instead [build gstreamer yourself](https://github.com/FDH2/UxPlay/wiki/Building-GStreamer-from-Source-on-macOS-with-MacPorts)
-if you use MacPorts and do not want to use the "Official" Gstreamer binaries or Macports packages.
+if you use MacPorts and do not want to use the "Official" Gstreamer binaries.
+
+_(If you really wish to use the MacPorts GStreamer-1.16.2,
+install pkgconf ("sudo port install pkgconf"), then
+"sudo port install gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-libav".
+For X11 support on macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and run
+it from an XQuartz terminal with -vs ximagesink; older non-retina macs require a lower resolution
+when using X11: `uxplay -s 800x600`.)_
+
+
After installing GStreamer, build and install uxplay: open a terminal and change into the UxPlay source directory
("UxPlay-master" for zipfile downloads, "UxPlay" for "git clone" downloads) and build/install with
@@ -748,6 +746,10 @@ with "`#`" are treated as comments, and ignored. Command line options supersede
The "-h265" option changes the default resolution ("-s" option) from 1920x1080 to 3840x2160, and leaves default maximum
framerate ("-fps" option) at 30fps.
+**-hls** Activate HTTP Live Streaming support. With this option YouTube videos can be streamed directly from
+ YouTube servers to UxPlay (without passing through the client)
+ by clicking on the AirPlay icon in the YouTube app.
+
**-pin [nnnn]**: (since v1.67) use Apple-style (one-time) "pin" authentication when a new client connects for the first time: a four-digit pin code is
displayed on the terminal, and the client screen shows a login prompt for this to be entered. When "-pin" is used by itself, a new random
pin code is chosen for each authentication; if "-pin nnnn" (e.g., "-pin 3939") is used, this will set an unchanging fixed code. Authentication adds the server to the client's list of
@@ -1236,6 +1238,8 @@ tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
# Changelog
+1.71 2024-12-10 Add support for HTTP Live Streaming (HLS), initially only for YouTube movies
+
1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x 2160). Fix issue
with GStreamer >= 1.24 when client sleeps, then wakes.
diff --git a/README.txt b/README.txt
index 2c3b56e6..c6a563fa 100644
--- a/README.txt
+++ b/README.txt
@@ -1,24 +1,18 @@
-# UxPlay 1.70: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
+# UxPlay 1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
### **Now developed at the GitHub site (where ALL user issues should be posted, and latest versions can be found).**
-- ***NEW in v1.70**: Support for 4k (h265) video with the new "-h265"
- option.* (Recent Apple devices will send HEVC (h265) video in
- AirPlay mirror mode if larger resolutions (*h* \> 1080) are
- requested with UxPlay's "-s wxh" option; wired ethernet connection
- is prefered to wireless in this mode, and may also be required by
- the client; the "-h265" option changes the default resolution from
- 1920x1080 to 3840x2160, but leaves default maximum framerate ("-fps"
- option) at 30fps.)
+- ***NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming)
+ video with the new "-hls" option.* Click on the airplay icon in the
+ YouTube app to stream video.
## Highlights:
- GPLv3, open source.
- Originally supported only AirPlay Mirror protocol, now has added
support for AirPlay Audio-only (Apple Lossless ALAC) streaming from
- current iOS/iPadOS clients. **There is no current support for
- Airplay HLS video-streaming (e.g., YouTube video) but this is in
- development.**
+ current iOS/iPadOS clients. **Now with support for Airplay HLS
+ video-streaming (currently only YouTube video).**
- macOS computers (2011 or later, both Intel and "Apple Silicon" M1/M2
systems) can act either as AirPlay clients, or as the server running
UxPlay. Using AirPlay, UxPlay can emulate a second display for macOS
@@ -159,17 +153,16 @@ stops/restarts as you leave/re-enter* **Audio** *mode.*
- **Note that Apple video-DRM (as found in "Apple TV app" content on
the client) cannot be decrypted by UxPlay, and the Apple TV app
cannot be watched using UxPlay's AirPlay Mirror mode (only the
- unprotected audio will be streamed, in AAC format), but both video
- and audio content from DRM-free apps like "YouTube app" will be
- streamed by UxPlay in Mirror mode.**
-
-- **As UxPlay does not currently support non-Mirror AirPlay video
- streaming (where the client controls a web server on the AirPlay
- server that directly receives HLS content to avoid it being decoded
- and re-encoded by the client), using the icon for AirPlay video in
- apps such as the YouTube app will only send audio (in lossless ALAC
- format) without the accompanying video (there are plans to support
- HLS video in future releases of UxPlay)**
+ unprotected audio will be streamed, in AAC format).**
+
+- **With the new "-hls" option, UxPlay now also supports non-Mirror
+ AirPlay video streaming (where the client controls a web server on
+ the AirPlay server that directly receives HLS content to avoid it
+ being decoded and re-encoded by the client). This currently only
+ supports streaming of YouTube videos. Without the -hls option, using
+ the icon for AirPlay video in apps such as the YouTube app will only
+ send audio (in lossless ALAC format) without the accompanying
+ video.**
### Possibility for using hardware-accelerated h264/h265 video-decoding, if available.
@@ -640,7 +633,7 @@ See [Usage](#usage) for more run-time options.
- **H265 (4K)** video is potentially supported by hardware decoding on
Raspberry Pi 5 models, as well as on Raspberry Pi 4 model B, using a
dedicated HEVC decoding block, but the "rpivid" kernel driver for
- this it not yet supported by GStreamer (this driver decodes video
+ this is not yet supported by GStreamer (this driver decodes video
into a non-standard format that cannot be supported by GStreamer
until the driver is in the mainline Linux kernel). Raspberry Pi
provides a version of ffmpeg that can use that format, but at
@@ -744,28 +737,22 @@ complete GStreamer, but seems to have everything needed for UxPlay).
installations in non-standard locations indicated by the environment
variable `$HOMEBREW_PREFIX`.**
-**Using GStreamer installed from MacPorts** : (MacPorts is now again
-supplying current or recent Gstreamer). Before building UxPlay, install
-the MacPorts GStreamer with
-"`sudo port install gstreamer1 gstreamer1-gst-plugins-base`". Plugins
-are installed by "`sudo port install gstreamer1-gst-plugins-*`" where
-"`*`" is "good", "bad", (and optionally "ugly"). For the libav plugin,
-"`sudo port install ffmpeg6 [+nonfree] gstreamer1-gst-libav`" (where
-"+nonfree" is optional, and makes linked GPL binaries
-non-distributable). Unfortunately, the current MacPorts GStreamer build
-(bug or feature?) does not provide the opengl plugin, so the only
-working videosink it provides is osxvideosink. (Hopefully this will be
-corrected). *It is also possible to install an X11-based GStreamer with
-MacPorts, (add " +x11" after "base", "good" "bad" and "ugly" in the
-plugin names): for X11 support on macOS, compile UxPlay using a special
-cmake option `-DUSE_X11=ON`, and run it from an XQuartz terminal with
--vs ximagesink; older non-retina macs require a lower resolution when
-using X11: `uxplay -s 800x600`.*
+**Using GStreamer installed from MacPorts**: this is **not**
+recommended, as currently the MacPorts GStreamer is old (v1.16.2),
+unmaintained, and built to use X11:
- Instead [build gstreamer
yourself](https://github.com/FDH2/UxPlay/wiki/Building-GStreamer-from-Source-on-macOS-with-MacPorts)
if you use MacPorts and do not want to use the "Official" Gstreamer
- binaries or Macports packages.
+ binaries.
+
+*(If you really wish to use the MacPorts GStreamer-1.16.2, install
+pkgconf ("sudo port install pkgconf"), then "sudo port install
+gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good
+gstreamer1-gst-plugins-bad gstreamer1-gst-libav". For X11 support on
+macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and
+run it from an XQuartz terminal with -vs ximagesink; older non-retina
+macs require a lower resolution when using X11: `uxplay -s 800x600`.)*
After installing GStreamer, build and install uxplay: open a terminal
and change into the UxPlay source directory ("UxPlay-master" for zipfile
@@ -956,6 +943,11 @@ The "-h265" option changes the default resolution ("-s" option) from
1920x1080 to 3840x2160, and leaves default maximum framerate ("-fps"
option) at 30fps.
+**-hls** Activate HTTP Live Streaming support. With this option YouTube
+videos can be streamed directly from YouTube servers to UxPlay (without
+passing through the client) by clicking on the AirPlay icon in the
+YouTube app.
+
**-pin \[nnnn\]**: (since v1.67) use Apple-style (one-time) "pin"
authentication when a new client connects for the first time: a
four-digit pin code is displayed on the terminal, and the client screen
@@ -1628,6 +1620,9 @@ what version UxPlay claims to be.
# Changelog
+1.71 2024-12-10 Add support for HTTP Live Streaming (HLS), initially
+only for YouTube movies
+
1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
2160). Fix issue with GStreamer \>= 1.24 when client sleeps, then wakes.
diff --git a/lib/airplay_video.c b/lib/airplay_video.c
new file mode 100644
index 00000000..7a739bb9
--- /dev/null
+++ b/lib/airplay_video.c
@@ -0,0 +1,305 @@
+/**
+ * Copyright (c) 2024 fduncanh
+ * All Rights Reserved.
+ *
+ * This library 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.
+ *
+ * This library 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.
+ *
+ */
+
+// it should only start and stop the media_data_store that handles all HLS transactions, without
+// otherwise participating in them.
+
+#include
+#include
+#include
+#include
+#include
+
+#include "raop.h"
+#include "airplay_video.h"
+
+struct media_item_s {
+ char *uri;
+ char *playlist;
+ int access;
+};
+
+struct airplay_video_s {
+ raop_t *raop;
+ char apple_session_id[37];
+ char playback_uuid[37];
+ char *uri_prefix;
+ char local_uri_prefix[23];
+ int next_uri;
+ int FCUP_RequestID;
+ float start_position_seconds;
+ playback_info_t *playback_info;
+ // The local port of the airplay server on the AirPlay server
+ unsigned short airplay_port;
+ char *master_uri;
+ char *master_playlist;
+ media_item_t *media_data_store;
+ int num_uri;
+};
+
+// initialize airplay_video service.
+int airplay_video_service_init(raop_t *raop, unsigned short http_port,
+ const char *session_id) {
+ char uri[] = "http://localhost:xxxxx";
+ assert(raop);
+
+ airplay_video_t *airplay_video = deregister_airplay_video(raop);
+ if (airplay_video) {
+ airplay_video_service_destroy(airplay_video);
+ }
+
+ airplay_video = (airplay_video_t *) calloc(1, sizeof(airplay_video_t));
+ if (!airplay_video) {
+ return -1;
+ }
+
+ /* create local_uri_prefix string */
+ strncpy(airplay_video->local_uri_prefix, uri, sizeof(airplay_video->local_uri_prefix));
+ char *ptr = strstr(airplay_video->local_uri_prefix, "xxxxx");
+ snprintf(ptr, 6, "%-5u", http_port);
+ ptr = strstr(airplay_video->local_uri_prefix, " ");
+ if (ptr) {
+ *ptr = '\0';
+ }
+
+ if (!register_airplay_video(raop, airplay_video)) {
+ return -2;
+ }
+
+ printf(" %p %p\n", airplay_video, get_airplay_video(raop));
+
+ airplay_video->raop = raop;
+
+
+ airplay_video->FCUP_RequestID = 0;
+
+
+ size_t len = strlen(session_id);
+ assert(len == 36);
+ strncpy(airplay_video->apple_session_id, session_id, len);
+ (airplay_video->apple_session_id)[len] = '\0';
+
+ airplay_video->start_position_seconds = 0.0f;
+
+ airplay_video->master_uri = NULL;
+ airplay_video->media_data_store = NULL;
+ airplay_video->master_playlist = NULL;
+ airplay_video->num_uri = 0;
+ airplay_video->next_uri = 0;
+ return 0;
+}
+
+// destroy the airplay_video service
+void
+airplay_video_service_destroy(airplay_video_t *airplay_video)
+{
+
+ if (airplay_video->uri_prefix) {
+ free(airplay_video->uri_prefix);
+ }
+ if (airplay_video->master_uri) {
+ free (airplay_video->master_uri);
+ }
+ if (airplay_video->media_data_store) {
+ destroy_media_data_store(airplay_video);
+ }
+ if (airplay_video->master_playlist) {
+ free (airplay_video->master_playlist);
+ }
+
+
+ free (airplay_video);
+}
+
+const char *get_apple_session_id(airplay_video_t *airplay_video) {
+ return airplay_video->apple_session_id;
+}
+
+float get_start_position_seconds(airplay_video_t *airplay_video) {
+ return airplay_video->start_position_seconds;
+}
+
+void set_start_position_seconds(airplay_video_t *airplay_video, float start_position_seconds) {
+ airplay_video->start_position_seconds = start_position_seconds;
+}
+
+void set_playback_uuid(airplay_video_t *airplay_video, const char *playback_uuid) {
+ size_t len = strlen(playback_uuid);
+ assert(len == 36);
+ memcpy(airplay_video->playback_uuid, playback_uuid, len);
+ (airplay_video->playback_uuid)[len] = '\0';
+}
+
+void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix, int uri_prefix_len) {
+ if (airplay_video->uri_prefix) {
+ free (airplay_video->uri_prefix);
+ }
+ airplay_video->uri_prefix = (char *) calloc(uri_prefix_len + 1, sizeof(char));
+ memcpy(airplay_video->uri_prefix, uri_prefix, uri_prefix_len);
+}
+
+char *get_uri_prefix(airplay_video_t *airplay_video) {
+ return airplay_video->uri_prefix;
+}
+
+char *get_uri_local_prefix(airplay_video_t *airplay_video) {
+ return airplay_video->local_uri_prefix;
+}
+
+
+char *get_master_uri(airplay_video_t *airplay_video) {
+ return airplay_video->master_uri;
+}
+
+
+int get_next_FCUP_RequestID(airplay_video_t *airplay_video) {
+ return ++(airplay_video->FCUP_RequestID);
+}
+
+void set_next_media_uri_id(airplay_video_t *airplay_video, int num) {
+ airplay_video->next_uri = num;
+}
+
+int get_next_media_uri_id(airplay_video_t *airplay_video) {
+ return airplay_video->next_uri;
+}
+
+
+/* master playlist */
+
+void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist) {
+ if (airplay_video->master_playlist) {
+ free (airplay_video->master_playlist);
+ }
+ airplay_video->master_playlist = master_playlist;
+}
+
+char *get_master_playlist(airplay_video_t *airplay_video) {
+ return airplay_video->master_playlist;
+}
+
+/* media_data_store */
+
+int get_num_media_uri(airplay_video_t *airplay_video) {
+ return airplay_video->num_uri;
+}
+
+void destroy_media_data_store(airplay_video_t *airplay_video) {
+ media_item_t *media_data_store = airplay_video->media_data_store;
+ if (media_data_store) {
+ for (int i = 0; i < airplay_video->num_uri ; i ++ ) {
+ if (media_data_store[i].uri) {
+ free (media_data_store[i].uri);
+ }
+ if (media_data_store[i].playlist) {
+ free (media_data_store[i].playlist);
+ }
+ }
+ }
+ free (media_data_store);
+ airplay_video->num_uri = 0;
+}
+
+void create_media_data_store(airplay_video_t * airplay_video, char ** uri_list, int num_uri) {
+ destroy_media_data_store(airplay_video);
+ media_item_t *media_data_store = calloc(num_uri, sizeof(media_item_t));
+ for (int i = 0; i < num_uri; i++) {
+ media_data_store[i].uri = uri_list[i];
+ media_data_store[i].playlist = NULL;
+ media_data_store[i].access = 0;
+ }
+ airplay_video->media_data_store = media_data_store;
+ airplay_video->num_uri = num_uri;
+}
+
+int store_media_data_playlist_by_num(airplay_video_t *airplay_video, char * media_playlist, int num) {
+ media_item_t *media_data_store = airplay_video->media_data_store;
+ if ( num < 0 || num >= airplay_video->num_uri) {
+ return -1;
+ } else if (media_data_store[num].playlist) {
+ return -2;
+ }
+ media_data_store[num].playlist = media_playlist;
+ return 0;
+}
+
+char * get_media_playlist_by_num(airplay_video_t *airplay_video, int num) {
+ media_item_t *media_data_store = airplay_video->media_data_store;
+ if (media_data_store == NULL) {
+ return NULL;
+ }
+ if (num >= 0 && num num_uri) {
+ return media_data_store[num].playlist;
+ }
+ return NULL;
+}
+
+char * get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri) {
+ /* Problem: there can be more than one StreamInf playlist with the same uri:
+ * they differ by choice of partner Media (audio, subtitles) playlists
+ * If the same uri is requested again, one of the other ones will be returned
+ * (the least-previously-requested one will be served up)
+ */
+ media_item_t *media_data_store = airplay_video->media_data_store;
+ if (media_data_store == NULL) {
+ return NULL;
+ }
+ int found = 0;;
+ int num = -1;
+ int access = -1;
+ for (int i = 0; i < airplay_video->num_uri; i++) {
+ if (strstr(media_data_store[i].uri, uri)) {
+ if (!found) {
+ found = 1;
+ num = i;
+ access = media_data_store[i].access;
+ } else {
+ /* change > below to >= to reverse the order of choice */
+ if (access > media_data_store[i].access) {
+ access = media_data_store[i].access;
+ num = i;
+ }
+ }
+ }
+ }
+ if (found) {
+ printf("found %s\n", media_data_store[num].uri);
+ ++media_data_store[num].access;
+ return media_data_store[num].playlist;
+ }
+ return NULL;
+}
+
+char * get_media_uri_by_num(airplay_video_t *airplay_video, int num) {
+ media_item_t * media_data_store = airplay_video->media_data_store;
+ if (media_data_store == NULL) {
+ return NULL;
+ }
+ if (num >= 0 && num < airplay_video->num_uri) {
+ return media_data_store[num].uri;
+ }
+ return NULL;
+}
+
+int get_media_uri_num(airplay_video_t *airplay_video, char * uri) {
+ media_item_t *media_data_store = airplay_video->media_data_store;
+ for (int i = 0; i < airplay_video->num_uri ; i++) {
+ if (strstr(media_data_store[i].uri, uri)) {
+ return i;
+ }
+ }
+ return -1;
+}
diff --git a/lib/airplay_video.h b/lib/airplay_video.h
new file mode 100644
index 00000000..3a62ba08
--- /dev/null
+++ b/lib/airplay_video.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2024 fduncanh, All Rights Reserved.
+ *
+ * This library 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.
+ *
+ * This library 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.
+ *
+ *=================================================================
+ */
+
+#ifndef AIRPLAY_VIDEO_H
+#define AIRPLAY_VIDEO_H
+
+
+#include
+#include
+#include "raop.h"
+#include "logger.h"
+
+typedef struct airplay_video_s airplay_video_t;
+typedef struct media_item_s media_item_t;
+
+const char *get_apple_session_id(airplay_video_t *airplay_video);
+void set_start_position_seconds(airplay_video_t *airplay_video, float start_position_seconds);
+float get_start_position_seconds(airplay_video_t *airplay_video);
+void set_playback_uuid(airplay_video_t *airplay_video, const char *playback_uuid);
+void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix, int uri_prefix_len);
+char *get_uri_prefix(airplay_video_t *airplay_video);
+char *get_uri_local_prefix(airplay_video_t *airplay_video);
+int get_next_FCUP_RequestID(airplay_video_t *airplay_video);
+void set_next_media_uri_id(airplay_video_t *airplay_video, int id);
+int get_next_media_uri_id(airplay_video_t *airplay_video);
+char * get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri);
+void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist);
+char *get_master_playlist(airplay_video_t *airplay_video);
+int get_num_media_uri(airplay_video_t *airplay_video);
+void destroy_media_data_store(airplay_video_t *airplay_video);
+void create_media_data_store(airplay_video_t * airplay_video, char ** media_data_store, int num_uri);
+int store_media_data_playlist_by_num(airplay_video_t *airplay_video, char * media_playlist, int num);
+char *get_media_playlist_by_num(airplay_video_t *airplay_video, int num);
+char *get_media_uri_by_num(airplay_video_t *airplay_video, int num);
+int get_media_uri_num(airplay_video_t *airplay_video, char * uri);
+
+
+void airplay_video_service_destroy(airplay_video_t *airplay_video);
+
+// C wrappers for c++ class MediaDataStore
+//create the media_data_store, return a pointer to it.
+void* media_data_store_create(void *conn_opaque, uint16_t port);
+
+//delete the media_data_store
+void media_data_store_destroy(void *media_data_store);
+
+// called by the POST /action handler:
+char *process_media_data(void *media_data_store, const char *url, const char *data, int datalen);
+
+//called by the POST /play handler
+bool request_media_data(void *media_data_store, const char *primary_url, const char * session_id);
+
+//called by airplay_video_media_http_connection::get_handler: &path = req.uri)
+char *query_media_data(void *media_data_store, const char *url, int *len);
+
+//called by the post_stop_handler:
+void media_data_store_reset(void *media_data_store);
+
+const char *adjust_primary_uri(void *media_data_store, const char *url);
+
+#endif //AIRPLAY_VIDEO_H
diff --git a/lib/fcup_request.h b/lib/fcup_request.h
new file mode 100644
index 00000000..166ffa95
--- /dev/null
+++ b/lib/fcup_request.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2022 fduncanh
+ * All Rights Reserved.
+ *
+ * This library 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.
+ *
+ * This library 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.
+ *
+ */
+
+/* this file is part of raop.c via http_handlers.h and should not be included in any other file */
+
+
+//produces the fcup request plist in xml format as a null-terminated string
+char *create_fcup_request(const char *url, int request_id, const char *client_session_id, int *datalen) {
+ char *plist_xml = NULL;
+ /* values taken from apsdk-public; */
+ /* these seem to be arbitrary choices */
+ const int sessionID = 1;
+ const int FCUP_Response_ClientInfo = 1;
+ const int FCUP_Response_ClientRef = 40030004;
+
+ /* taken from a working AppleTV? */
+ const char User_Agent[] = "AppleCoreMedia/1.0.0.11B554a (Apple TV; U; CPU OS 7_0_4 like Mac OS X; en_us";
+
+ plist_t req_root_node = plist_new_dict();
+
+ plist_t session_id_node = plist_new_uint((int64_t) sessionID);
+ plist_dict_set_item(req_root_node, "sessionID", session_id_node);
+ plist_t type_node = plist_new_string("unhandledURLRequest");
+ plist_dict_set_item(req_root_node, "type", type_node);
+
+ plist_t fcup_request_node = plist_new_dict();
+
+ plist_t client_info_node = plist_new_uint(FCUP_Response_ClientInfo);
+ plist_dict_set_item(fcup_request_node, "FCUP_Response_ClientInfo", client_info_node);
+ plist_t client_ref_node = plist_new_uint((int64_t) FCUP_Response_ClientRef);
+ plist_dict_set_item(fcup_request_node, "FCUP_Response_ClientRef", client_ref_node);
+ plist_t request_id_node = plist_new_uint((int64_t) request_id);
+ plist_dict_set_item(fcup_request_node, "FCUP_Response_RequestID", request_id_node);
+ plist_t url_node = plist_new_string(url);
+ plist_dict_set_item(fcup_request_node, "FCUP_Response_URL", url_node);
+ plist_t session_id1_node = plist_new_uint((int64_t) sessionID);
+ plist_dict_set_item(fcup_request_node, "sessionID", session_id1_node);
+
+ plist_t fcup_response_header_node = plist_new_dict();
+ plist_t playback_session_id_node = plist_new_string(client_session_id);
+ plist_dict_set_item(fcup_response_header_node, "X-Playback-Session-Id", playback_session_id_node);
+ plist_t user_agent_node = plist_new_string(User_Agent);
+ plist_dict_set_item(fcup_response_header_node, "User-Agent", user_agent_node);
+
+ plist_dict_set_item(fcup_request_node, "FCUP_Response_Headers", fcup_response_header_node);
+ plist_dict_set_item(req_root_node, "request", fcup_request_node);
+
+ uint32_t uint_val;
+
+ plist_to_xml(req_root_node, &plist_xml, &uint_val);
+ *datalen = (int) uint_val;
+ plist_free(req_root_node);
+ assert(plist_xml[*datalen] == '\0');
+ return plist_xml; //needs to be freed after use
+}
+
+int fcup_request(void *conn_opaque, const char *media_url, const char *client_session_id, int request_id) {
+
+ raop_conn_t *conn = (raop_conn_t *) conn_opaque;
+ int datalen = 0;
+ int requestlen;
+
+ int socket_fd = httpd_get_connection_socket_by_type(conn->raop->httpd, CONNECTION_TYPE_PTTH, 1);
+
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "fcup_request send socket = %d", socket_fd);
+
+ /* create xml plist request data */
+ char *plist_xml = create_fcup_request(media_url, request_id, client_session_id, &datalen);
+
+ /* use http_response tools for creating the reverse http request */
+ http_response_t *request = http_response_create();
+ http_response_reverse_request_init(request, "POST", "/event", "HTTP/1.1");
+ http_response_add_header(request, "X-Apple-Session-ID", client_session_id);
+ http_response_add_header(request, "Content-Type", "text/x-apple-plist+xml");
+ http_response_finish(request, plist_xml, datalen);
+
+ free(plist_xml);
+
+ const char *http_request = http_response_get_data(request, &requestlen);
+ int send_len = send(socket_fd, http_request, requestlen, 0);
+ if (send_len < 0) {
+ int sock_err = SOCKET_GET_ERROR();
+ logger_log(conn->raop->logger, LOGGER_ERR, "fcup_request: send error %d:%s\n",
+ sock_err, strerror(sock_err));
+ http_response_destroy(request);
+ /* shut down connection? */
+ return -1;
+ }
+
+ if (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG) {
+ char *request_str = utils_data_to_text(http_request, requestlen);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s", request_str);
+ free (request_str);
+ }
+ http_response_destroy(request);
+ logger_log(conn->raop->logger, LOGGER_DEBUG,"fcup_request: send sent Request of %d bytes from socket %d\n",
+ send_len, socket_fd);
+ return 0;
+}
diff --git a/lib/http_handlers.h b/lib/http_handlers.h
new file mode 100644
index 00000000..59fd78a5
--- /dev/null
+++ b/lib/http_handlers.h
@@ -0,0 +1,1001 @@
+/**
+ * Copyright (c) 2024 fduncanh
+ * All Rights Reserved.
+ *
+ * This library 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.
+ *
+ * This library 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.
+ *
+ */
+
+/* this file is part of raop.c and should not be included in any other file */
+
+#include "airplay_video.h"
+#include "fcup_request.h"
+
+static void
+http_handler_server_info(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+
+ assert(conn->raop->dnssd);
+ int hw_addr_raw_len = 0;
+ const char *hw_addr_raw = dnssd_get_hw_addr(conn->raop->dnssd, &hw_addr_raw_len);
+
+ char *hw_addr = calloc(1, 3 * hw_addr_raw_len);
+ //int hw_addr_len =
+ utils_hwaddr_airplay(hw_addr, 3 * hw_addr_raw_len, hw_addr_raw, hw_addr_raw_len);
+
+ plist_t r_node = plist_new_dict();
+
+ /* first 12 AirPlay features bits (R to L): 0x27F = 0010 0111 1111
+ * Only bits 0-6 and bit 9 are set:
+ * 0. video supported
+ * 1. photo supported
+ * 2. video protected wirh FairPlay DRM
+ * 3. volume control supported for video
+ * 4. HLS supported
+ * 5. slideshow supported
+ * 6. (unknown)
+ * 9. audio supported.
+ */
+ plist_t features_node = plist_new_uint(0x27F);
+ plist_dict_set_item(r_node, "features", features_node);
+
+ plist_t mac_address_node = plist_new_string(hw_addr);
+ plist_dict_set_item(r_node, "macAddress", mac_address_node);
+
+ plist_t model_node = plist_new_string(GLOBAL_MODEL);
+ plist_dict_set_item(r_node, "model", model_node);
+
+ plist_t os_build_node = plist_new_string("12B435");
+ plist_dict_set_item(r_node, "osBuildVersion", os_build_node);
+
+ plist_t protovers_node = plist_new_string("1.0");
+ plist_dict_set_item(r_node, "protovers", protovers_node);
+
+ plist_t source_version_node = plist_new_string(GLOBAL_VERSION);
+ plist_dict_set_item(r_node, "srcvers", source_version_node);
+
+ plist_t vv_node = plist_new_uint(strtol(AIRPLAY_VV, NULL, 10));
+ plist_dict_set_item(r_node, "vv", vv_node);
+
+ plist_t device_id_node = plist_new_string(hw_addr);
+ plist_dict_set_item(r_node, "deviceid", device_id_node);
+
+ plist_to_xml(r_node, response_data, (uint32_t *) response_datalen);
+
+ //assert(*response_datalen == strlen(*response_data));
+
+ /* last character (at *response_data[response_datalen - 1]) is 0x0a = '\n'
+ * (*response_data[response_datalen] is '\0').
+ * apsdk removes the last "\n" by overwriting it with '\0', and reducing response_datalen by 1.
+ * TODO: check if this is necessary */
+
+ plist_free(r_node);
+ http_response_add_header(response, "Content-Type", "text/x-apple-plist+xml");
+ free(hw_addr);
+
+ /* initialize the airplay video service */
+ const char *session_id = http_request_get_header(request, "X-Apple-Session-ID");
+
+ airplay_video_service_init(conn->raop, conn->raop->port, session_id);
+
+}
+
+static void
+http_handler_scrub(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+ const char *url = http_request_get_url(request);
+ const char *data = strstr(url, "?");
+ float scrub_position = 0.0f;
+ if (data) {
+ data++;
+ const char *position = strstr(data, "=") + 1;
+ char *end;
+ double value = strtod(position, &end);
+ if (end && end != position) {
+ scrub_position = (float) value;
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "http_handler_scrub: got position = %.6f",
+ scrub_position);
+ }
+ }
+ printf("**********************SCRUB %f ***********************\n",scrub_position);
+ conn->raop->callbacks.on_video_scrub(conn->raop->callbacks.cls, scrub_position);
+}
+
+static void
+http_handler_rate(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+
+ const char *url = http_request_get_url(request);
+ const char *data = strstr(url, "?");
+ float rate_value = 0.0f;
+ if (data) {
+ data++;
+ const char *rate = strstr(data, "=") + 1;
+ char *end;
+ float value = strtof(rate, &end);
+ if (end && end != rate) {
+ rate_value = value;
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "http_handler_rate: got rate = %.6f", rate_value);
+ }
+ }
+ conn->raop->callbacks.on_video_rate(conn->raop->callbacks.cls, rate_value);
+}
+
+static void
+http_handler_stop(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+ logger_log(conn->raop->logger, LOGGER_INFO, "client HTTP request POST stop");
+
+ conn->raop->callbacks.on_video_stop(conn->raop->callbacks.cls);
+}
+
+/* handles PUT /setProperty http requests from Client to Server */
+
+static void
+http_handler_set_property(raop_conn_t *conn,
+ http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+
+ const char *url = http_request_get_url(request);
+ const char *property = url + strlen("/setProperty?");
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "http_handler_set_property: %s", property);
+
+ /* actionAtItemEnd: values:
+ 0: advance (advance to next item, if there is one)
+ 1: pause (pause playing)
+ 2: none (do nothing)
+
+ reverseEndTime (only used when rate < 0) time at which reverse playback ends
+ forwardEndTime (only used when rate > 0) time at which reverse playback ends
+ */
+
+ if (!strcmp(property, "reverseEndTime") ||
+ !strcmp(property, "forwardEndTime") ||
+ !strcmp(property, "actionAtItemEnd")) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "property %s is known but unhandled", property);
+
+ plist_t errResponse = plist_new_dict();
+ plist_t errCode = plist_new_uint(0);
+ plist_dict_set_item(errResponse, "errorCode", errCode);
+ plist_to_xml(errResponse, response_data, (uint32_t *) response_datalen);
+ plist_free(errResponse);
+ http_response_add_header(response, "Content-Type", "text/x-apple-plist+xml");
+ } else {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "property %s is unknown, unhandled", property);
+ http_response_add_header(response, "Content-Length", "0");
+ }
+}
+
+/* handles GET /getProperty http requests from Client to Server. (not implemented) */
+
+static void
+http_handler_get_property(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+ const char *url = http_request_get_url(request);
+ const char *property = url + strlen("getProperty?");
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "http_handler_get_property: %s (unhandled)", property);
+}
+
+/* this request (for a variant FairPlay decryption) cannot be handled by UxPlay */
+static void
+http_handler_fpsetup2(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+ logger_log(conn->raop->logger, LOGGER_WARNING, "client HTTP request POST fp-setup2 is unhandled");
+ http_response_add_header(response, "Content-Type", "application/x-apple-binary-plist");
+ int req_datalen;
+ const unsigned char *req_data = (unsigned char *) http_request_get_data(request, &req_datalen);
+ logger_log(conn->raop->logger, LOGGER_ERR, "only FairPlay version 0x03 is implemented, version is 0x%2.2x",
+ req_data[4]);
+ http_response_init(response, "HTTP/1.1", 421, "Misdirected Request");
+}
+
+// called by http_handler_playback_info while preparing response to a GET /playback_info request from the client.
+
+typedef struct time_range_s {
+ double start;
+ double duration;
+} time_range_t;
+
+void time_range_to_plist(void *time_ranges, const int n_time_ranges,
+ plist_t time_ranges_node) {
+ time_range_t *tr = (time_range_t *) time_ranges;
+ for (int i = 0 ; i < n_time_ranges; i++) {
+ plist_t time_range_node = plist_new_dict();
+ plist_t duration_node = plist_new_real(tr[i].duration);
+ plist_dict_set_item(time_range_node, "duration", duration_node);
+ plist_t start_node = plist_new_real(tr[i].start);
+ plist_dict_set_item(time_range_node, "start", start_node);
+ plist_array_append_item(time_ranges_node, time_range_node);
+ }
+}
+
+// called by http_handler_playback_info while preparing response to a GET /playback_info request from the client.
+
+int create_playback_info_plist_xml(playback_info_t *playback_info, char **plist_xml) {
+
+ plist_t res_root_node = plist_new_dict();
+
+ plist_t duration_node = plist_new_real(playback_info->duration);
+ plist_dict_set_item(res_root_node, "duration", duration_node);
+
+ plist_t position_node = plist_new_real(playback_info->position);
+ plist_dict_set_item(res_root_node, "position", position_node);
+
+ plist_t rate_node = plist_new_real(playback_info->rate);
+ plist_dict_set_item(res_root_node, "rate", rate_node);
+
+ /* should these be int or bool? */
+ plist_t ready_to_play_node = plist_new_uint(playback_info->ready_to_play);
+ plist_dict_set_item(res_root_node, "readyToPlay", ready_to_play_node);
+
+ plist_t playback_buffer_empty_node = plist_new_uint(playback_info->playback_buffer_empty);
+ plist_dict_set_item(res_root_node, "playbackBufferEmpty", playback_buffer_empty_node);
+
+ plist_t playback_buffer_full_node = plist_new_uint(playback_info->playback_buffer_full);
+ plist_dict_set_item(res_root_node, "playbackBufferFull", playback_buffer_full_node);
+
+ plist_t playback_likely_to_keep_up_node = plist_new_uint(playback_info->playback_likely_to_keep_up);
+ plist_dict_set_item(res_root_node, "playbackLikelyToKeepUp", playback_likely_to_keep_up_node);
+
+ plist_t loaded_time_ranges_node = plist_new_array();
+ time_range_to_plist(playback_info->loadedTimeRanges, playback_info->num_loaded_time_ranges,
+ loaded_time_ranges_node);
+ plist_dict_set_item(res_root_node, "loadedTimeRanges", loaded_time_ranges_node);
+
+ plist_t seekable_time_ranges_node = plist_new_array();
+ time_range_to_plist(playback_info->seekableTimeRanges, playback_info->num_seekable_time_ranges,
+ seekable_time_ranges_node);
+ plist_dict_set_item(res_root_node, "seekableTimeRanges", seekable_time_ranges_node);
+
+ int len;
+ plist_to_xml(res_root_node, plist_xml, (uint32_t *) &len);
+ /* plist_xml is null-terminated, last character is '/n' */
+
+ plist_free(res_root_node);
+
+ return len;
+}
+
+
+/* this handles requests from the Client for "Playback information" while the Media is playing on the
+ Media Player. (The Server gets this information by monitoring the Media Player). The Client could use
+ the information to e.g. update the slider it shows with progress to the player (0%-100%).
+ It does not affect playing of the Media*/
+
+static void
+http_handler_playback_info(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen)
+{
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "http_handler_playback_info");
+ //const char *session_id = http_request_get_header(request, "X-Apple-Session-ID");
+ playback_info_t playback_info;
+
+ playback_info.stallcount = 0;
+ playback_info.ready_to_play = true; // ???;
+ playback_info.playback_buffer_empty = false; // maybe need to get this from playbin
+ playback_info.playback_buffer_full = true;
+ playback_info.playback_likely_to_keep_up = true;
+
+ conn->raop->callbacks.on_video_acquire_playback_info(conn->raop->callbacks.cls, &playback_info);
+ if (playback_info.duration == -1.0) {
+ /* video has finished, reset */
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "playback_info not available (finishing)");
+ //httpd_remove_known_connections(conn->raop->httpd);
+ http_response_set_disconnect(response,1);
+ conn->raop->callbacks.video_reset(conn->raop->callbacks.cls);
+ return;
+ } else if (playback_info.position == -1.0) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "playback_info not available");
+ return;
+ }
+
+ playback_info.num_loaded_time_ranges = 1;
+ time_range_t time_ranges_loaded[1];
+ time_ranges_loaded[0].start = playback_info.position;
+ time_ranges_loaded[0].duration = playback_info.duration - playback_info.position;
+ playback_info.loadedTimeRanges = (void *) &time_ranges_loaded;
+
+ playback_info.num_seekable_time_ranges = 1;
+ time_range_t time_ranges_seekable[1];
+ time_ranges_seekable[0].start = 0.0;
+ time_ranges_seekable[0].duration = playback_info.position;
+ playback_info.seekableTimeRanges = (void *) &time_ranges_seekable;
+
+ *response_datalen = create_playback_info_plist_xml(&playback_info, response_data);
+ http_response_add_header(response, "Content-Type", "text/x-apple-plist+xml");
+}
+
+/* this handles the POST /reverse request from Client to Server on a AirPlay http channel to "Upgrade"
+ to "PTTH/1.0" Reverse HTTP protocol proposed in 2009 Internet-Draft
+
+ https://datatracker.ietf.org/doc/id/draft-lentczner-rhttp-00.txt .
+
+ After the Upgrade the channel becomes a reverse http "AirPlay (reversed)" channel for
+ http requests from Server to Client.
+ */
+
+static void
+http_handler_reverse(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+
+ /* get http socket for send */
+ int socket_fd = httpd_get_connection_socket (conn->raop->httpd, (void *) conn);
+ if (socket_fd < 0) {
+ logger_log(conn->raop->logger, LOGGER_ERR, "fcup_request failed to retrieve socket_fd from httpd");
+ /* shut down connection? */
+ }
+
+ const char *purpose = http_request_get_header(request, "X-Apple-Purpose");
+ const char *connection = http_request_get_header(request, "Connection");
+ const char *upgrade = http_request_get_header(request, "Upgrade");
+ logger_log(conn->raop->logger, LOGGER_INFO, "client requested reverse connection: %s; purpose: %s \"%s\"",
+ connection, upgrade, purpose);
+
+ httpd_set_connection_type(conn->raop->httpd, (void *) conn, CONNECTION_TYPE_PTTH);
+ int type_PTTH = httpd_count_connection_type(conn->raop->httpd, CONNECTION_TYPE_PTTH);
+
+ if (type_PTTH == 1) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "will use socket %d for %s connections", socket_fd, purpose);
+ http_response_init(response, "HTTP/1.1", 101, "Switching Protocols");
+ http_response_add_header(response, "Connection", "Upgrade");
+ http_response_add_header(response, "Upgrade", "PTTH/1.0");
+
+ } else {
+ logger_log(conn->raop->logger, LOGGER_ERR, "multiple TPPH connections (%d) are forbidden", type_PTTH );
+ }
+}
+
+/* this copies a Media Playlist into a null-terminated string. If it has the "#YT-EXT-CONDENSED-URI"
+ header, it is also expanded into the full Media Playlist format */
+
+char *adjust_yt_condensed_playlist(const char *media_playlist) {
+ /* expands a YT-EXT_CONDENSED-URL media playlist into a full media playlist
+ * returns a pointer to the expanded playlist, WHICH MUST BE FREED AFTER USE */
+
+ const char *base_uri_begin;
+ const char *params_begin;
+ const char *prefix_begin;
+ size_t base_uri_len;
+ size_t params_len;
+ size_t prefix_len;
+ const char* ptr = strstr(media_playlist, "#EXTM3U\n");
+
+ ptr += strlen("#EXTM3U\n");
+ assert(ptr);
+ if (strncmp(ptr, "#YT-EXT-CONDENSED-URL", strlen("#YT-EXT-CONDENSED-URL"))) {
+ size_t len = strlen(media_playlist);
+ char * playlist_copy = (char *) malloc(len + 1);
+ memcpy(playlist_copy, media_playlist, len);
+ playlist_copy[len] = '\0';
+ return playlist_copy;
+ }
+ ptr = strstr(ptr, "BASE-URI=");
+ base_uri_begin = strchr(ptr, '"');
+ base_uri_begin++;
+ ptr = strchr(base_uri_begin, '"');
+ base_uri_len = ptr - base_uri_begin;
+ char *base_uri = (char *) calloc(base_uri_len + 1, sizeof(char));
+ assert(base_uri);
+ memcpy(base_uri, base_uri_begin, base_uri_len); //must free
+
+ ptr = strstr(ptr, "PARAMS=");
+ params_begin = strchr(ptr, '"');
+ params_begin++;
+ ptr = strchr(params_begin,'"');
+ params_len = ptr - params_begin;
+ char *params = (char *) calloc(params_len + 1, sizeof(char));
+ assert(params);
+ memcpy(params, params_begin, params_len); //must free
+
+ ptr = strstr(ptr, "PREFIX=");
+ prefix_begin = strchr(ptr, '"');
+ prefix_begin++;
+ ptr = strchr(prefix_begin,'"');
+ prefix_len = ptr - prefix_begin;
+ char *prefix = (char *) calloc(prefix_len + 1, sizeof(char));
+ assert(prefix);
+ memcpy(prefix, prefix_begin, prefix_len); //must free
+
+ /* expand params */
+ int nparams = 0;
+ int *params_size = NULL;
+ const char **params_start = NULL;
+ if (strlen(params)) {
+ nparams = 1;
+ char * comma = strchr(params, ',');
+ while (comma) {
+ nparams++;
+ comma++;
+ comma = strchr(comma, ',');
+ }
+ params_start = (const char **) calloc(nparams, sizeof(char *)); //must free
+ params_size = (int *) calloc(nparams, sizeof(int)); //must free
+ ptr = params;
+ for (int i = 0; i < nparams; i++) {
+ comma = strchr(ptr, ',');
+ params_start[i] = ptr;
+ if (comma) {
+ params_size[i] = (int) (comma - ptr);
+ ptr = comma;
+ ptr++;
+ } else {
+ params_size[i] = (int) (params + params_len - ptr);
+ break;
+ }
+ }
+ }
+
+ int count = 0;
+ ptr = strstr(media_playlist, "#EXTINF");
+ while (ptr) {
+ count++;
+ ptr = strstr(++ptr, "#EXTINF");
+ }
+
+ size_t old_size = strlen(media_playlist);
+ size_t new_size = old_size;
+ new_size += count * (base_uri_len + params_len);
+
+ char * new_playlist = (char *) calloc( new_size + 100, sizeof(char));
+ const char *old_pos = media_playlist;
+ char *new_pos = new_playlist;
+ ptr = old_pos;
+ ptr = strstr(old_pos, "#EXTINF:");
+ size_t len = ptr - old_pos;
+ /* copy header section before chunks */
+ memcpy(new_pos, old_pos, len);
+ old_pos += len;
+ new_pos += len;
+ int counter = 0;
+ while (ptr) {
+ counter++;
+ /* for each chunk */
+ const char *end = NULL;
+ char *start = strstr(ptr, prefix);
+ len = start - ptr;
+ /* copy first line of chunk entry */
+ memcpy(new_pos, old_pos, len);
+ old_pos += len;
+ new_pos += len;
+
+ /* copy base uri to replace prefix*/
+ memcpy(new_pos, base_uri, base_uri_len);
+ new_pos += base_uri_len;
+ old_pos += prefix_len;
+ ptr = strstr(old_pos, "#EXTINF:");
+
+ /* insert the PARAMS separators on the slices line */
+ end = old_pos;
+ int last = nparams - 1;
+ for (int i = 0; i < nparams; i++) {
+ if (i != last) {
+ end = strchr(end, '/');
+ } else {
+ end = strstr(end, "#EXT"); /* the next line starts with either #EXTINF (usually) or #EXT-X-ENDLIST (at last chunk)*/
+ }
+ *new_pos = '/';
+ new_pos++;
+ memcpy(new_pos, params_start[i], params_size[i]);
+ new_pos += params_size[i];
+ *new_pos = '/';
+ new_pos++;
+
+ len = end - old_pos;
+ end++;
+
+ memcpy (new_pos, old_pos, len);
+ new_pos += len;
+ old_pos += len;
+ if (i != last) {
+ old_pos++; /* last entry is not followed by "/" separator */
+ }
+ }
+ }
+ /* copy tail */
+
+ len = media_playlist + strlen(media_playlist) - old_pos;
+ memcpy(new_pos, old_pos, len);
+ new_pos += len;
+ old_pos += len;
+
+ new_playlist[new_size] = '\0';
+
+ free (prefix);
+ free (base_uri);
+ free (params);
+ if (params_size) {
+ free (params_size);
+ }
+ if (params_start) {
+ free (params_start);
+ }
+
+ return new_playlist;
+}
+
+/* this adjusts the uri prefixes in the Master Playlist, for sending to the Media Player running on the Server Host */
+
+char *adjust_master_playlist (char *fcup_response_data, int fcup_response_datalen, char *uri_prefix, char *uri_local_prefix) {
+
+ size_t uri_prefix_len = strlen(uri_prefix);
+ size_t uri_local_prefix_len = strlen(uri_local_prefix);
+ int counter = 0;
+ char *ptr = strstr(fcup_response_data, uri_prefix);
+ while (ptr != NULL) {
+ counter++;
+ ptr++;
+ ptr = strstr(ptr, uri_prefix);
+ }
+
+ size_t len = uri_local_prefix_len - uri_prefix_len;
+ len *= counter;
+ len += fcup_response_datalen;
+ char *new_master = (char *) malloc(len + 1);
+ *(new_master + len) = '\0';
+ char *first = fcup_response_data;
+ char *new = new_master;
+ char *last = strstr(first, uri_prefix);
+ counter = 0;
+ while (last != NULL) {
+ counter++;
+ len = last - first;
+ memcpy(new, first, len);
+ first = last + uri_prefix_len;
+ new += len;
+ memcpy(new, uri_local_prefix, uri_local_prefix_len);
+ new += uri_local_prefix_len;
+ last = strstr(last + uri_prefix_len, uri_prefix);
+ if (last == NULL) {
+ len = fcup_response_data + fcup_response_datalen - first;
+ memcpy(new, first, len);
+ break;
+ }
+ }
+ return new_master;
+}
+
+/* this parses the Master Playlist to make a table of the Media Playlist uri's that it lists */
+
+int create_media_uri_table(const char *url_prefix, const char *master_playlist_data, int datalen,
+ char ***media_uri_table, int *num_uri) {
+ char *ptr = strstr(master_playlist_data, url_prefix);
+ char ** table = NULL;
+ if (ptr == NULL) {
+ return -1;
+ }
+ int count = 0;
+ while (ptr != NULL) {
+ char *end = strstr(ptr, "m3u8");
+ if (end == NULL) {
+ return 1;
+ }
+ end += sizeof("m3u8");
+ count++;
+ ptr = strstr(end, url_prefix);
+ }
+ table = (char **) calloc(count, sizeof(char *));
+ if (!table) {
+ return -1;
+ }
+ for (int i = 0; i < count; i++) {
+ table[i] = NULL;
+ }
+ ptr = strstr(master_playlist_data, url_prefix);
+ count = 0;
+ while (ptr != NULL) {
+ char *end = strstr(ptr, "m3u8");
+ char *uri;
+ if (end == NULL) {
+ return 0;
+ }
+ end += sizeof("m3u8");
+ size_t len = end - ptr - 1;
+ uri = (char *) calloc(len + 1, sizeof(char));
+ memcpy(uri , ptr, len);
+ table[count] = uri;
+ uri = NULL;
+ count ++;
+ ptr = strstr(end, url_prefix);
+ }
+ *num_uri = count;
+
+ *media_uri_table = table;
+ return 0;
+}
+
+/* the POST /action request from Client to Server on the AirPlay http channel follows a POST /event "FCUP Request"
+ from Server to Client on the reverse http channel, for a HLS playlist (first the Master Playlist, then the Media Playlists
+ listed in the Master Playlist. The POST /action request contains the playlist requested by the Server in
+ the preceding "FCUP Request". The FCUP Request sequence continues until all Media Playlists have been obtained by the Server */
+
+static void
+http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+
+ bool data_is_plist = false;
+ plist_t req_root_node = NULL;
+ uint64_t uint_val;
+ int request_id = 0;
+ int fcup_response_statuscode = 0;
+ bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG);
+
+
+ const char* session_id = http_request_get_header(request, "X-Apple-Session-ID");
+ if (!session_id) {
+ logger_log(conn->raop->logger, LOGGER_ERR, "Play request had no X-Apple-Session-ID");
+ goto post_action_error;
+ }
+ const char *apple_session_id = get_apple_session_id(conn->raop->airplay_video);
+ if (strcmp(session_id, apple_session_id)){
+ logger_log(conn->raop->logger, LOGGER_ERR, "X-Apple-Session-ID has changed:\n was:\"%s\"\n now:\"%s\"",
+ apple_session_id, session_id);
+ goto post_action_error;
+ }
+
+ /* verify that this request contains a binary plist*/
+ char *header_str = NULL;
+ http_request_get_header_string(request, &header_str);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "request header: %s", header_str);
+ data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL);
+ free(header_str);
+ if (!data_is_plist) {
+ logger_log(conn->raop->logger, LOGGER_INFO, "POST /action: did not receive expected plist from client");
+ goto post_action_error;
+ }
+
+ /* extract the root_node plist */
+ int request_datalen = 0;
+ const char *request_data = http_request_get_data(request, &request_datalen);
+ if (request_datalen == 0) {
+ logger_log(conn->raop->logger, LOGGER_INFO, "POST /action: did not receive expected plist from client");
+ goto post_action_error;
+ }
+ plist_from_bin(request_data, request_datalen, &req_root_node);
+
+ /* determine type of data */
+ plist_t req_type_node = plist_dict_get_item(req_root_node, "type");
+ if (!PLIST_IS_STRING(req_type_node)) {
+ goto post_action_error;
+ }
+
+ /* three possible types are known */
+ char *type = NULL;
+ int action_type = 0;
+ plist_get_string_val(req_type_node, &type);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "action type is %s", type);
+ if (strstr(type, "unhandledURLResponse")) {
+ action_type = 1;
+ } else if (strstr(type, "playlistInsert")) {
+ action_type = 2;
+ } else if (strstr(type, "playlistRemove")) {
+ action_type = 3;
+ }
+ free (type);
+
+ plist_t req_params_node = NULL;
+ switch (action_type) {
+ case 1:
+ goto unhandledURLResponse;
+ case 2:
+ logger_log(conn->raop->logger, LOGGER_INFO, "unhandled action type playlistInsert (add new playback)");
+ goto finish;
+ case 3:
+ logger_log(conn->raop->logger, LOGGER_INFO, "unhandled action type playlistRemove (stop playback)");
+ goto finish;
+ default:
+ logger_log(conn->raop->logger, LOGGER_INFO, "unknown action type (unhandled)");
+ goto finish;
+ }
+
+ unhandledURLResponse:;
+
+ req_params_node = plist_dict_get_item(req_root_node, "params");
+ if (!PLIST_IS_DICT (req_params_node)) {
+ goto post_action_error;
+ }
+
+ /* handling type "unhandledURLResponse" (case 1)*/
+ uint_val = 0;
+ int fcup_response_datalen = 0;
+
+ if (logger_debug) {
+ plist_t plist_fcup_response_statuscode_node = plist_dict_get_item(req_params_node,
+ "FCUP_Response_StatusCode");
+ if (plist_fcup_response_statuscode_node) {
+ plist_get_uint_val(plist_fcup_response_statuscode_node, &uint_val);
+ fcup_response_statuscode = (int) uint_val;
+ uint_val = 0;
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "FCUP_Response_StatusCode = %d",
+ fcup_response_statuscode);
+ }
+
+ plist_t plist_fcup_response_requestid_node = plist_dict_get_item(req_params_node,
+ "FCUP_Response_RequestID");
+ if (plist_fcup_response_requestid_node) {
+ plist_get_uint_val(plist_fcup_response_requestid_node, &uint_val);
+ request_id = (int) uint_val;
+ uint_val = 0;
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "FCUP_Response_RequestID = %d", request_id);
+ }
+ }
+
+ plist_t plist_fcup_response_url_node = plist_dict_get_item(req_params_node, "FCUP_Response_URL");
+ if (!PLIST_IS_STRING(plist_fcup_response_url_node)) {
+ goto post_action_error;
+ }
+ char *fcup_response_url = NULL;
+ plist_get_string_val(plist_fcup_response_url_node, &fcup_response_url);
+ if (!fcup_response_url) {
+ goto post_action_error;
+ }
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "FCUP_Response_URL = %s", fcup_response_url);
+
+ plist_t plist_fcup_response_data_node = plist_dict_get_item(req_params_node, "FCUP_Response_Data");
+ if (!PLIST_IS_DATA(plist_fcup_response_data_node)){
+ goto post_action_error;
+ }
+
+ uint_val = 0;
+ char *fcup_response_data = NULL;
+ plist_get_data_val(plist_fcup_response_data_node, &fcup_response_data, &uint_val);
+ fcup_response_datalen = (int) uint_val;
+
+ if (!fcup_response_data) {
+ free (fcup_response_url);
+ goto post_action_error;
+ }
+
+ if (logger_debug) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "FCUP_Response datalen = %d", fcup_response_datalen);
+ char *ptr = fcup_response_data;
+ printf("begin FCUP Response data:\n");
+ for (int i = 0; i < fcup_response_datalen; i++) {
+ printf("%c", *ptr);
+ ptr++;
+ }
+ printf("end FCUP Response data\n");
+ }
+
+
+ char *ptr = strstr(fcup_response_url, "/master.m3u8");
+ if (ptr) {
+ /* this is a master playlist */
+ char *uri_prefix = get_uri_prefix(conn->raop->airplay_video);
+ char ** media_data_store = NULL;
+ int num_uri = 0;
+
+ char *uri_local_prefix = get_uri_local_prefix(conn->raop->airplay_video);
+ char *new_master = adjust_master_playlist (fcup_response_data, fcup_response_datalen, uri_prefix, uri_local_prefix);
+ store_master_playlist(conn->raop->airplay_video, new_master);
+ create_media_uri_table(uri_prefix, fcup_response_data, fcup_response_datalen, &media_data_store, &num_uri);
+ create_media_data_store(conn->raop->airplay_video, media_data_store, num_uri);
+ num_uri = get_num_media_uri(conn->raop->airplay_video);
+ set_next_media_uri_id(conn->raop->airplay_video, 0);
+ } else {
+ /* this is a media playlist */
+ assert(fcup_response_data);
+ char *playlist = (char *) calloc(fcup_response_datalen + 1, sizeof(char));
+ memcpy(playlist, fcup_response_data, fcup_response_datalen);
+ int uri_num = get_next_media_uri_id(conn->raop->airplay_video);
+ --uri_num; // (next num is current num + 1)
+ store_media_data_playlist_by_num(conn->raop->airplay_video, playlist, uri_num);
+ float duration = 0.0f, next;
+ int count = 0;
+ ptr = strstr(fcup_response_data, "#EXTINF:");
+ while (ptr != NULL) {
+ char *end;
+ ptr += strlen("#EXTINF:");
+ next = strtof(ptr, &end);
+ duration += next;
+ count++;
+ ptr = strstr(end, "#EXTINF:");
+ }
+ if (count) {
+ printf("\n%s:\nplaylist has %5d chunks, total duration %9.3f secs\n", fcup_response_url, count, duration);
+ }
+ }
+
+ if (fcup_response_data) {
+ free (fcup_response_data);
+ }
+ if (fcup_response_url) {
+ free (fcup_response_url);
+ }
+
+ int num_uri = get_num_media_uri(conn->raop->airplay_video);
+ int uri_num = get_next_media_uri_id(conn->raop->airplay_video);
+ if (uri_num < num_uri) {
+ fcup_request((void *) conn, get_media_uri_by_num(conn->raop->airplay_video, uri_num),
+ apple_session_id,
+ get_next_FCUP_RequestID(conn->raop->airplay_video));
+ set_next_media_uri_id(conn->raop->airplay_video, ++uri_num);
+ } else {
+ char * uri_local_prefix = get_uri_local_prefix(conn->raop->airplay_video);
+ conn->raop->callbacks.on_video_play(conn->raop->callbacks.cls,
+ strcat(uri_local_prefix, "/master.m3u8"),
+ get_start_position_seconds(conn->raop->airplay_video));
+ }
+
+ finish:
+ plist_free(req_root_node);
+ return;
+
+ post_action_error:;
+ http_response_init(response, "HTTP/1.1", 400, "Bad Request");
+
+ if (req_root_node) {
+ plist_free(req_root_node);
+ }
+
+}
+
+/* The POST /play request from the Client to Server on the AirPlay http channel contains (among other information)
+ the "Content Location" that specifies the HLS Playlists for the video to be streamed, as well as the video
+ "start position in seconds". Once this request is received by the Sever, the Server sends a POST /event
+ "FCUP Request" request to the Client on the reverse http channel, to request the HLS Master Playlist */
+
+static void
+http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+
+ char* playback_location = NULL;
+ plist_t req_root_node = NULL;
+ float start_position_seconds = 0.0f;
+ bool data_is_binary_plist = false;
+ bool data_is_text = false;
+ bool data_is_octet = false;
+
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "http_handler_play");
+
+ const char* session_id = http_request_get_header(request, "X-Apple-Session-ID");
+ if (!session_id) {
+ logger_log(conn->raop->logger, LOGGER_ERR, "Play request had no X-Apple-Session-ID");
+ goto play_error;
+ }
+ const char *apple_session_id = get_apple_session_id(conn->raop->airplay_video);
+ if (strcmp(session_id, apple_session_id)){
+ logger_log(conn->raop->logger, LOGGER_ERR, "X-Apple-Session-ID has changed:\n was:\"%s\"\n now:\"%s\"",
+ apple_session_id, session_id);
+ goto play_error;
+ }
+
+ int request_datalen = -1;
+ const char *request_data = http_request_get_data(request, &request_datalen);
+
+ if (request_datalen > 0) {
+ char *header_str = NULL;
+ http_request_get_header_string(request, &header_str);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "request header:\n%s", header_str);
+ data_is_binary_plist = (strstr(header_str, "x-apple-binary-plist") != NULL);
+ data_is_text = (strstr(header_str, "text/parameters") != NULL);
+ data_is_octet = (strstr(header_str, "octet-stream") != NULL);
+ free (header_str);
+ }
+ if (!data_is_text && !data_is_octet && !data_is_binary_plist) {
+ goto play_error;
+ }
+
+ if (data_is_text) {
+ logger_log(conn->raop->logger, LOGGER_ERR, "Play request Content is text (unsupported)");
+ goto play_error;
+ }
+
+ if (data_is_octet) {
+ logger_log(conn->raop->logger, LOGGER_ERR, "Play request Content is octet-stream (unsupported)");
+ goto play_error;
+ }
+
+ if (data_is_binary_plist) {
+ plist_from_bin(request_data, request_datalen, &req_root_node);
+
+ plist_t req_uuid_node = plist_dict_get_item(req_root_node, "uuid");
+ if (!req_uuid_node) {
+ goto play_error;
+ } else {
+ char* playback_uuid = NULL;
+ plist_get_string_val(req_uuid_node, &playback_uuid);
+ set_playback_uuid(conn->raop->airplay_video, playback_uuid);
+ free (playback_uuid);
+ }
+
+ plist_t req_content_location_node = plist_dict_get_item(req_root_node, "Content-Location");
+ if (!req_content_location_node) {
+ goto play_error;
+ } else {
+ plist_get_string_val(req_content_location_node, &playback_location);
+ }
+
+ plist_t req_start_position_seconds_node = plist_dict_get_item(req_root_node, "Start-Position-Seconds");
+ if (!req_start_position_seconds_node) {
+ logger_log(conn->raop->logger, LOGGER_INFO, "No Start-Position-Seconds in Play request");
+ } else {
+ double start_position = 0.0;
+ plist_get_real_val(req_start_position_seconds_node, &start_position);
+ start_position_seconds = (float) start_position;
+ }
+ set_start_position_seconds(conn->raop->airplay_video, (float) start_position_seconds);
+ }
+
+ char *ptr = strstr(playback_location, "/master.m3u8");
+ int prefix_len = (int) (ptr - playback_location);
+ set_uri_prefix(conn->raop->airplay_video, playback_location, prefix_len);
+ set_next_media_uri_id(conn->raop->airplay_video, 0);
+ fcup_request((void *) conn, playback_location, apple_session_id, get_next_FCUP_RequestID(conn->raop->airplay_video));
+
+ if (playback_location) {
+ free (playback_location);
+ }
+
+ if (req_root_node) {
+ plist_free(req_root_node);
+ }
+ return;
+
+ play_error:;
+ if (req_root_node) {
+ plist_free(req_root_node);
+ }
+ logger_log(conn->raop->logger, LOGGER_ERR, "Could not find valid Plist Data for /play, Unhandled");
+ http_response_init(response, "HTTP/1.1", 400, "Bad Request");
+}
+
+/* the HLS handler handles http requests GET /[uri] on the HLS channel from the media player to the Server, asking for
+ (adjusted) copies of Playlists: first the Master Playlist (adjusted to change the uri prefix to
+ "http://localhost:[port]/.......m3u8"), then the Media Playlists that the media player wishes to use.
+ If the client supplied Media playlists with the "YT-EXT-CONDENSED-URI" header, these must be adjusted into
+ the standard uncondensed form before sending with the response. The uri in the request is the uri for the
+ Media Playlist, taken from the Master Playlist, with the uri prefix removed.
+*/
+
+static void
+http_handler_hls(raop_conn_t *conn, http_request_t *request, http_response_t *response,
+ char **response_data, int *response_datalen) {
+ const char *method = http_request_get_method(request);
+ assert (!strcmp(method, "GET"));
+ const char *url = http_request_get_url(request);
+ const char* upgrade = http_request_get_header(request, "Upgrade");
+ if (upgrade) {
+ //don't accept Upgrade: h2c request ?
+ return;
+ }
+
+ if (!strcmp(url, "/master.m3u8")){
+ char * master_playlist = get_master_playlist(conn->raop->airplay_video);
+ size_t len = strlen(master_playlist);
+ char * data = (char *) malloc(len + 1);
+ memcpy(data, master_playlist, len);
+ data[len] = '\0';
+ *response_data = data;
+ *response_datalen = (int ) len;
+ } else {
+ char * media_playlist = NULL;
+ media_playlist = get_media_playlist_by_uri(conn->raop->airplay_video, url);
+ if (media_playlist) {
+ char *data = adjust_yt_condensed_playlist(media_playlist);
+ *response_data = data;
+ *response_datalen = strlen(data);
+ } else {
+ printf("%s not found\n", url);
+ assert(0);
+ }
+ }
+
+ http_response_add_header(response, "Access-Control-Allow-Headers", "Content-type");
+ http_response_add_header(response, "Access-Control-Allow-Origin", "*");
+ const char *date;
+ date = gmt_time_string();
+ http_response_add_header(response, "Date", date);
+ if (*response_datalen > 0) {
+ http_response_add_header(response, "Content-Type", "application/x-mpegURL; charset=utf-8");
+ } else if (*response_datalen == 0) {
+ http_response_init(response, "HTTP/1.1", 404, "Not Found");
+ }
+}
diff --git a/lib/http_request.c b/lib/http_request.c
index 5a6343ad..76bece6b 100644
--- a/lib/http_request.c
+++ b/lib/http_request.c
@@ -27,6 +27,7 @@ struct http_request_s {
llhttp_t parser;
llhttp_settings_t parser_settings;
+ bool is_reverse; // if true, this is a reverse-response from client
const char *method;
char *url;
char protocol[9];
@@ -160,7 +161,7 @@ http_request_init(void)
llhttp_init(&request->parser, HTTP_REQUEST, &request->parser_settings);
request->parser.data = request;
-
+ request->is_reverse = false;
return request;
}
@@ -206,6 +207,9 @@ int
http_request_has_error(http_request_t *request)
{
assert(request);
+ if (request->is_reverse) {
+ return 0;
+ }
return (llhttp_get_errno(&request->parser) != HPE_OK);
}
@@ -213,6 +217,9 @@ const char *
http_request_get_error_name(http_request_t *request)
{
assert(request);
+ if (request->is_reverse) {
+ return NULL;
+ }
return llhttp_errno_name(llhttp_get_errno(&request->parser));
}
@@ -220,6 +227,9 @@ const char *
http_request_get_error_description(http_request_t *request)
{
assert(request);
+ if (request->is_reverse) {
+ return NULL;
+ }
return llhttp_get_error_reason(&request->parser);
}
@@ -227,6 +237,9 @@ const char *
http_request_get_method(http_request_t *request)
{
assert(request);
+ if (request->is_reverse) {
+ return NULL;
+ }
return request->method;
}
@@ -234,6 +247,9 @@ const char *
http_request_get_url(http_request_t *request)
{
assert(request);
+ if (request->is_reverse) {
+ return NULL;
+ }
return request->url;
}
@@ -241,6 +257,9 @@ const char *
http_request_get_protocol(http_request_t *request)
{
assert(request);
+ if (request->is_reverse) {
+ return NULL;
+ }
return request->protocol;
}
@@ -250,6 +269,9 @@ http_request_get_header(http_request_t *request, const char *name)
int i;
assert(request);
+ if (request->is_reverse) {
+ return NULL;
+ }
for (i=0; iheaders_size; i+=2) {
if (!strcmp(request->headers[i], name)) {
@@ -263,7 +285,6 @@ const char *
http_request_get_data(http_request_t *request, int *datalen)
{
assert(request);
-
if (datalen) {
*datalen = request->datalen;
}
@@ -277,6 +298,10 @@ http_request_get_header_string(http_request_t *request, char **header_str)
*header_str = NULL;
return 0;
}
+ if (request->is_reverse) {
+ *header_str = NULL;
+ return 0;
+ }
int len = 0;
for (int i = 0; i < request->headers_size; i++) {
len += strlen(request->headers[i]);
@@ -309,3 +334,11 @@ http_request_get_header_string(http_request_t *request, char **header_str)
assert(p == &(str[len]));
return len;
}
+
+bool http_request_is_reverse(http_request_t *request) {
+ return request->is_reverse;
+}
+
+void http_request_set_reverse(http_request_t *request) {
+ request->is_reverse = true;
+}
diff --git a/lib/http_request.h b/lib/http_request.h
index d72a6f11..dd136802 100644
--- a/lib/http_request.h
+++ b/lib/http_request.h
@@ -15,8 +15,9 @@
#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H
-typedef struct http_request_s http_request_t;
+#include
+typedef struct http_request_s http_request_t;
http_request_t *http_request_init(void);
@@ -32,6 +33,8 @@ const char *http_request_get_protocol(http_request_t *request);
const char *http_request_get_header(http_request_t *request, const char *name);
const char *http_request_get_data(http_request_t *request, int *datalen);
int http_request_get_header_string(http_request_t *request, char **header_str);
+bool http_request_is_reverse(http_request_t *request);
+void http_request_set_reverse(http_request_t *request);
void http_request_destroy(http_request_t *request);
diff --git a/lib/http_response.c b/lib/http_response.c
index 50f1d891..a1943387 100644
--- a/lib/http_response.c
+++ b/lib/http_response.c
@@ -91,6 +91,21 @@ http_response_init(http_response_t *response, const char *protocol, int code, co
http_response_add_data(response, "\r\n", 2);
}
+void
+http_response_reverse_request_init(http_response_t *request, const char *method, const char *url, const char *protocol)
+{
+ assert(request);
+ request->data_length = 0; /* reinitialize a previously-initialized response as a reverse-HTTP (PTTH/1.0) request */
+
+ /* Add first line of response to the data array */
+ http_response_add_data(request, method, strlen(method));
+ http_response_add_data(request, " ", 1);
+ http_response_add_data(request, url, strlen(url));
+ http_response_add_data(request, " ", 1);
+ http_response_add_data(request, protocol, strlen(protocol));
+ http_response_add_data(request, "\r\n", 2);
+}
+
void
http_response_destroy(http_response_t *response)
{
diff --git a/lib/http_response.h b/lib/http_response.h
index fcc78997..e34ba0c8 100644
--- a/lib/http_response.h
+++ b/lib/http_response.h
@@ -22,6 +22,8 @@ typedef struct http_response_s http_response_t;
http_response_t *http_response_create();
void http_response_init(http_response_t *response, const char *protocol, int code, const char *message);
+void http_response_reverse_request_init(http_response_t *request, const char *method, const char *url,
+ const char *protocol);
void http_response_add_header(http_response_t *response, const char *name, const char *value);
void http_response_finish(http_response_t *response, const char *data, int datalen);
diff --git a/lib/httpd.c b/lib/httpd.c
index 7140e2e9..1799e346 100644
--- a/lib/httpd.c
+++ b/lib/httpd.c
@@ -20,12 +20,24 @@
#include
#include
#include
+#include
#include "httpd.h"
#include "netutils.h"
#include "http_request.h"
#include "compat.h"
#include "logger.h"
+#include "utils.h"
+
+
+static const char *typename[] = {
+ [CONNECTION_TYPE_UNKNOWN] = "Unknown",
+ [CONNECTION_TYPE_RAOP] = "RAOP",
+ [CONNECTION_TYPE_AIRPLAY] = "AirPlay",
+ [CONNECTION_TYPE_PTTH] = "AirPlay (reversed)",
+ [CONNECTION_TYPE_HLS] = "HLS"
+};
+
struct http_connection_s {
int connected;
@@ -57,6 +69,20 @@ struct httpd_s {
int server_fd6;
};
+int
+httpd_get_connection_socket (httpd_t *httpd, void *user_data) {
+ for (int i = 0; i < httpd->max_connections; i++) {
+ http_connection_t *connection = &httpd->connections[i];
+ if (!connection->connected) {
+ continue;
+ }
+ if (connection->user_data == user_data) {
+ return connection->socket_fd;
+ }
+ }
+ return -1;
+}
+
int
httpd_set_connection_type (httpd_t *httpd, void *user_data, connection_type_t type) {
for (int i = 0; i < httpd->max_connections; i++) {
@@ -87,6 +113,42 @@ httpd_count_connection_type (httpd_t *httpd, connection_type_t type) {
return count;
}
+int
+httpd_get_connection_socket_by_type (httpd_t *httpd, connection_type_t type, int instance){
+ int count = 0;
+ for (int i = 0; i < httpd->max_connections; i++) {
+ http_connection_t *connection = &httpd->connections[i];
+ if (!connection->connected) {
+ continue;
+ }
+ if (connection->type == type) {
+ count++;
+ if (count == instance) {
+ return connection->socket_fd;
+ }
+ }
+ }
+ return 0;
+}
+
+void *
+httpd_get_connection_by_type (httpd_t *httpd, connection_type_t type, int instance){
+ int count = 0;
+ for (int i = 0; i < httpd->max_connections; i++) {
+ http_connection_t *connection = &httpd->connections[i];
+ if (!connection->connected) {
+ continue;
+ }
+ if (connection->type == type) {
+ count++;
+ if (count == instance) {
+ return connection->user_data;
+ }
+ }
+ }
+ return NULL;
+}
+
#define MAX_CONNECTIONS 12 /* value used in AppleTV 3*/
httpd_t *
httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold)
@@ -101,7 +163,6 @@ httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold)
return NULL;
}
-
httpd->nohold = (nohold ? 1 : 0);
httpd->max_connections = MAX_CONNECTIONS;
httpd->connections = calloc(httpd->max_connections, sizeof(http_connection_t));
@@ -213,7 +274,7 @@ httpd_accept_connection(httpd_t *httpd, int server_fd, int is_ipv6)
local = netutils_get_address(&local_saddr, &local_len, &local_zone_id);
remote = netutils_get_address(&remote_saddr, &remote_len, &remote_zone_id);
assert (local_zone_id == remote_zone_id);
-
+
ret = httpd_add_connection(httpd, fd, local, local_len, remote, remote_len, local_zone_id);
if (ret == -1) {
shutdown(fd, SHUT_RDWR);
@@ -235,7 +296,7 @@ httpd_remove_known_connections(httpd_t *httpd) {
if (!connection->connected || connection->type == CONNECTION_TYPE_UNKNOWN) {
continue;
}
- httpd_remove_connection(httpd, connection);
+ httpd_remove_connection(httpd, connection);
}
}
@@ -243,10 +304,11 @@ static THREAD_RETVAL
httpd_thread(void *arg)
{
httpd_t *httpd = arg;
+ char http[] = "HTTP/1.1";
char buffer[1024];
int i;
+
bool logger_debug = (logger_get_level(httpd->logger) >= LOGGER_DEBUG);
-
assert(httpd);
while (1) {
@@ -254,6 +316,7 @@ httpd_thread(void *arg)
struct timeval tv;
int nfds=0;
int ret;
+ int new_request;
MUTEX_LOCK(httpd->run_mutex);
if (!httpd->running) {
@@ -299,7 +362,7 @@ httpd_thread(void *arg)
/* Timeout happened */
continue;
} else if (ret == -1) {
- logger_log(httpd->logger, LOGGER_ERR, "httpd error in select");
+ logger_log(httpd->logger, LOGGER_ERR, "httpd error in select: %d %s", errno, strerror(errno));
break;
}
@@ -337,20 +400,93 @@ httpd_thread(void *arg)
if (!connection->request) {
connection->request = http_request_init();
assert(connection->request);
+ new_request = 1;
+ if (connection->type == CONNECTION_TYPE_PTTH) {
+ http_request_is_reverse(connection->request);
+ }
+ logger_log(httpd->logger, LOGGER_DEBUG, "new request, connection %d, socket %d type %s",
+ i, connection->socket_fd, typename [connection->type]);
+ } else {
+ new_request = 0;
+ }
+
+ logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d, connection %d",
+ connection->socket_fd, i);
+ if (logger_debug) {
+ printf("\nhttpd: current connections:\n");
+ for (int i = 0; i < httpd->max_connections; i++) {
+ http_connection_t *connection = &httpd->connections[i];
+ if(!connection->connected) {
+ continue;
+ }
+ if (!FD_ISSET(connection->socket_fd, &rfds)) {
+ printf("connection %d type %d socket %d conn %p %s\n", i,
+ connection->type, connection->socket_fd,
+ connection->user_data, typename [connection->type]);
+ } else {
+ printf("connection %d type %d socket %d conn %p %s ACTIVE CONNECTION\n", i, connection->type,
+ connection->socket_fd, connection->user_data, typename [connection->type]);
+ }
+ }
+ printf("\n");
+ }
+ /* reverse-http responses from the client must not be sent to the llhttp parser:
+ * such messages start with "HTTP/1.1" */
+ if (new_request) {
+ int readstart = 0;
+ new_request = 0;
+ while (readstart < 8) {
+ ret = recv(connection->socket_fd, buffer + readstart, sizeof(buffer) - 1 - readstart, 0);
+ if (ret == 0) {
+ logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d",
+ connection->socket_fd);
+ break;
+ } else if (ret == -1) {
+ if (errno == EAGAIN) {
+ continue;
+ } else {
+ int sock_err = SOCKET_GET_ERROR();
+ logger_log(httpd->logger, LOGGER_ERR, "httpd: recv socket error %d:%s",
+ sock_err, strerror(sock_err));
+ break;
+ }
+ } else {
+ readstart += ret;
+ ret = readstart;
+ }
+ }
+ if (!memcmp(buffer, http, 8)) {
+ http_request_set_reverse(connection->request);
+ }
+ } else {
+ ret = recv(connection->socket_fd, buffer, sizeof(buffer) - 1, 0);
+ if (ret == 0) {
+ logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d",
+ connection->socket_fd);
+ httpd_remove_connection(httpd, connection);
+ continue;
+ }
}
-
- logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d, connection %d", connection->socket_fd, i);
- ret = recv(connection->socket_fd, buffer, sizeof(buffer), 0);
- if (ret == 0) {
- logger_log(httpd->logger, LOGGER_INFO, "Connection closed for socket %d", connection->socket_fd);
- httpd_remove_connection(httpd, connection);
+ if (http_request_is_reverse(connection->request)) {
+ /* this is a response from the client to a
+ * GET /event reverse HTTP request from the server */
+ if (ret && logger_debug) {
+ buffer[ret] = '\0';
+ logger_log(httpd->logger, LOGGER_INFO, "<<<< received response from client"
+ " (reversed HTTP = \"PTTH/1.0\") connection"
+ " on socket %d:\n%s\n", connection->socket_fd, buffer);
+ }
+ if (ret == 0) {
+ httpd_remove_connection(httpd, connection);
+ }
continue;
}
/* Parse HTTP request from data read from connection */
http_request_add_data(connection->request, buffer, ret);
if (http_request_has_error(connection->request)) {
- logger_log(httpd->logger, LOGGER_ERR, "httpd error in parsing: %s", http_request_get_error_name(connection->request));
+ logger_log(httpd->logger, LOGGER_ERR, "httpd error in parsing: %s",
+ http_request_get_error_name(connection->request));
httpd_remove_connection(httpd, connection);
continue;
}
@@ -359,12 +495,13 @@ httpd_thread(void *arg)
if (http_request_is_complete(connection->request)) {
http_response_t *response = NULL;
// Callback the received data to raop
- if (logger_debug) {
+ if (logger_debug) {
const char *method = http_request_get_method(connection->request);
const char *url = http_request_get_url(connection->request);
const char *protocol = http_request_get_protocol(connection->request);
- logger_log(httpd->logger, LOGGER_INFO, "httpd request received on socket %d, connection %d, "
- "method = %s, url = %s, protocol = %s", connection->socket_fd, i, method, url, protocol);
+ logger_log(httpd->logger, LOGGER_INFO, "httpd request received on socket %d, "
+ "connection %d, method = %s, url = %s, protocol = %s",
+ connection->socket_fd, i, method, url, protocol);
}
httpd->callbacks.conn_request(connection->user_data, connection->request, &response);
http_request_destroy(connection->request);
diff --git a/lib/httpd.h b/lib/httpd.h
index f64d166b..e00a950c 100644
--- a/lib/httpd.h
+++ b/lib/httpd.h
@@ -23,7 +23,10 @@ typedef struct httpd_s httpd_t;
typedef enum connectype_type_e {
CONNECTION_TYPE_UNKNOWN,
- CONNECTION_TYPE_RAOP
+ CONNECTION_TYPE_RAOP,
+ CONNECTION_TYPE_AIRPLAY,
+ CONNECTION_TYPE_PTTH,
+ CONNECTION_TYPE_HLS
} connection_type_t;
struct httpd_callbacks_s {
@@ -39,7 +42,9 @@ void httpd_remove_known_connections(httpd_t *httpd);
int httpd_set_connection_type (httpd_t *http, void *user_data, connection_type_t type);
int httpd_count_connection_type (httpd_t *http, connection_type_t type);
-
+int httpd_get_connection_socket (httpd_t *httpd, void *user_data);
+int httpd_get_connection_socket_by_type (httpd_t *httpd, connection_type_t type, int instance);
+void *httpd_get_connection_by_type (httpd_t *httpd, connection_type_t type, int instance);
httpd_t *httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold);
int httpd_is_running(httpd_t *httpd);
diff --git a/lib/raop.c b/lib/raop.c
index e9d551ba..6bd3e0e9 100644
--- a/lib/raop.c
+++ b/lib/raop.c
@@ -72,6 +72,12 @@ struct raop_s {
/* public key as string */
char pk_str[2*ED25519_KEY_SIZE + 1];
+
+ /* place to store media_data_store */
+ airplay_video_t *airplay_video;
+
+ /* activate support for HLS live streaming */
+ bool hls_support;
};
struct raop_conn_s {
@@ -81,7 +87,8 @@ struct raop_conn_s {
raop_rtp_mirror_t *raop_rtp_mirror;
fairplay_t *fairplay;
pairing_session_t *session;
-
+ airplay_video_t *airplay_video;
+
unsigned char *local;
int locallen;
@@ -92,11 +99,14 @@ struct raop_conn_s {
connection_type_t connection_type;
+ char *client_session_id;
+
bool have_active_remote;
};
typedef struct raop_conn_s raop_conn_t;
#include "raop_handlers.h"
+#include "http_handlers.h"
static void *
conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remote, int remotelen, unsigned int zone_id) {
@@ -147,6 +157,9 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
conn->remotelen = remotelen;
conn->connection_type = CONNECTION_TYPE_UNKNOWN;
+ conn->client_session_id = NULL;
+ conn->airplay_video = NULL;
+
conn->have_active_remote = false;
@@ -162,35 +175,110 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
char *response_data = NULL;
int response_datalen = 0;
raop_conn_t *conn = ptr;
-
+ bool hls_request = false;
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "conn_request");
bool logger_debug = (logger_get_level(conn->raop->logger) >= LOGGER_DEBUG);
+ /*
+ All requests arriving here have been parsed by llhttp to obtain
+ method | url | protocol (RTSP/1.0 or HTTP/1.1)
+
+ There are three types of connections supplying these requests:
+ Connections from the AirPlay client:
+ (1) type RAOP connections with CSeq seqence header, and no X-Apple-Session-ID header
+ (2) type AIRPLAY connection with an X-Apple-Sequence-ID header and no Cseq header
+ Connections from localhost:
+ (3) type HLS internal connections from the local HLS server (gstreamer) at localhost with neither
+ of these headers, but a Host: localhost:[port] header. method = GET.
+ */
+
const char *method = http_request_get_method(request);
- const char *url = http_request_get_url(request);
- const char *protocol = http_request_get_protocol(request);
+
+ if (!method) {
+ return;
+ }
+
+/* this rejects messages from _airplay._tcp for video streaming protocol unless bool raop->hls_support is true*/
const char *cseq = http_request_get_header(request, "CSeq");
+ const char *protocol = http_request_get_protocol(request);
+ if (!cseq && !conn->raop->hls_support) {
+ logger_log(conn->raop->logger, LOGGER_INFO, "ignoring AirPlay video streaming request (use option -hls to activate HLS support)");
+ return;
+ }
+
+ const char *url = http_request_get_url(request);
+ const char *client_session_id = http_request_get_header(request, "X-Apple-Session-ID");
+ const char *host = http_request_get_header(request, "Host");
+ hls_request = (host && !cseq && !client_session_id);
if (conn->connection_type == CONNECTION_TYPE_UNKNOWN) {
- if (httpd_count_connection_type(conn->raop->httpd, CONNECTION_TYPE_RAOP)) {
- char ipaddr[40];
- utils_ipaddress_to_string(conn->remotelen, conn->remote, conn->zone_id, ipaddr, (int) (sizeof(ipaddr)));
- if (httpd_nohold(conn->raop->httpd)) {
- logger_log(conn->raop->logger, LOGGER_INFO, "\"nohold\" feature: switch to new connection request from %s", ipaddr);
- if (conn->raop->callbacks.video_reset) {
- printf("**************************video_reset*************************\n");
- conn->raop->callbacks.video_reset(conn->raop->callbacks.cls);
- }
- httpd_remove_known_connections(conn->raop->httpd);
+ if (cseq) {
+ if (httpd_count_connection_type(conn->raop->httpd, CONNECTION_TYPE_RAOP)) {
+ char ipaddr[40];
+ utils_ipaddress_to_string(conn->remotelen, conn->remote, conn->zone_id, ipaddr, (int) (sizeof(ipaddr)));
+ if (httpd_nohold(conn->raop->httpd)) {
+ logger_log(conn->raop->logger, LOGGER_INFO, "\"nohold\" feature: switch to new connection request from %s", ipaddr);
+ if (conn->raop->callbacks.video_reset) {
+ conn->raop->callbacks.video_reset(conn->raop->callbacks.cls);
+ }
+ httpd_remove_known_connections(conn->raop->httpd);
+ } else {
+ logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr);
+ *response = http_response_create();
+ http_response_init(*response, protocol, 409, "Conflict: Server is connected to another client");
+ goto finish;
+ }
+ }
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "New connection %p identified as Connection type RAOP", ptr);
+ httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_RAOP);
+ conn->connection_type = CONNECTION_TYPE_RAOP;
+ } else if (client_session_id) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "New connection %p identified as Connection type AirPlay", ptr);
+ httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_AIRPLAY);
+ conn->connection_type = CONNECTION_TYPE_AIRPLAY;
+ size_t len = strlen(client_session_id) + 1;
+ conn->client_session_id = (char *) malloc(len);
+ strncpy(conn->client_session_id, client_session_id, len);
+ /* airplay video has been requested: shut down any running RAOP udp services */
+ raop_conn_t *raop_conn = (raop_conn_t *) httpd_get_connection_by_type(conn->raop->httpd, CONNECTION_TYPE_RAOP, 1);
+ if (raop_conn) {
+ raop_rtp_mirror_t *raop_rtp_mirror = raop_conn->raop_rtp_mirror;
+ if (raop_rtp_mirror) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "New AirPlay connection: stopping RAOP mirror"
+ " service on RAOP connection %p", raop_conn);
+ raop_rtp_mirror_stop(raop_rtp_mirror);
+ }
- } else {
- logger_log(conn->raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr);
- *response = http_response_create();
- http_response_init(*response, protocol, 409, "Conflict: Server is connected to another client");
- goto finish;
- }
- }
- httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_RAOP);
- conn->connection_type = CONNECTION_TYPE_RAOP;
+ raop_rtp_t *raop_rtp = raop_conn->raop_rtp;
+ if (raop_rtp) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "New AirPlay connection: stopping RAOP audio"
+ " service on RAOP connection %p", raop_conn);
+ raop_rtp_stop(raop_rtp);
+ }
+
+ raop_ntp_t *raop_ntp = raop_conn->raop_ntp;
+ if (raop_rtp) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "New AirPlay connection: stopping NTP time"
+ " service on RAOP connection %p", raop_conn);
+ raop_ntp_stop(raop_ntp);
+ }
+ }
+ } else if (host) {
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "New connection %p identified as Connection type HLS", ptr);
+ httpd_set_connection_type(conn->raop->httpd, ptr, CONNECTION_TYPE_HLS);
+ conn->connection_type = CONNECTION_TYPE_HLS;
+ } else {
+ logger_log(conn->raop->logger, LOGGER_WARNING, "connection from unknown connection type");
+ }
+ }
+
+ /* this response code and message will be modified by the handler if necessary */
+ *response = http_response_create();
+ http_response_init(*response, protocol, 200, "OK");
+
+ /* is this really necessary? or is it obsolete? (added for all RTSP requests EXCEPT "RECORD") */
+ if (cseq && strcmp(method, "RECORD")) {
+ http_response_add_header(*response, "Audio-Jack-Status", "connected; type=digital");
}
if (!conn->have_active_remote) {
@@ -204,15 +292,6 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
}
}
- if (!method) {
- return;
- }
-
- /* this rejects unsupported messages from _airplay._tcp for video streaming protocol*/
- if (!cseq) {
- return;
- }
-
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s %s %s", method, url, protocol);
char *header_str= NULL;
http_request_get_header_string(request, &header_str);
@@ -225,74 +304,125 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
const char *request_data = http_request_get_data(request, &request_datalen);
if (request_data && logger_debug) {
if (request_datalen > 0) {
+ /* logger has a buffer limit of 4096 */
if (data_is_plist) {
- plist_t req_root_node = NULL;
+ plist_t req_root_node = NULL;
plist_from_bin(request_data, request_datalen, &req_root_node);
char * plist_xml;
uint32_t plist_len;
plist_to_xml(req_root_node, &plist_xml, &plist_len);
- logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
+ printf("%s\n",plist_xml);
+ //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
plist_free(req_root_node);
} else if (data_is_text) {
char *data_str = utils_data_to_text((char *) request_data, request_datalen);
- logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ printf("%s\n", data_str);
+ //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
} else {
char *data_str = utils_data_to_string((unsigned char *) request_data, request_datalen, 16);
- logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ printf("%s\n", data_str);
+ //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
}
}
}
}
- *response = http_response_create();
- http_response_init(*response, protocol, 200, "OK");
-
- //http_response_add_header(*response, "Apple-Jack-Status", "connected; type=analog");
-
+ if (client_session_id) {
+ assert(!strcmp(client_session_id, conn->client_session_id));
+ }
logger_log(conn->raop->logger, LOGGER_DEBUG, "Handling request %s with URL %s", method, url);
raop_handler_t handler = NULL;
- if (!strcmp(method, "GET") && !strcmp(url, "/info")) {
- handler = &raop_handler_info;
- } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-pin-start")) {
- handler = &raop_handler_pairpinstart;
- } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup-pin")) {
- handler = &raop_handler_pairsetup_pin;
- } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-setup")) {
- handler = &raop_handler_pairsetup;
- } else if (!strcmp(method, "POST") && !strcmp(url, "/pair-verify")) {
- handler = &raop_handler_pairverify;
- } else if (!strcmp(method, "POST") && !strcmp(url, "/fp-setup")) {
- handler = &raop_handler_fpsetup;
- } else if (!strcmp(method, "OPTIONS")) {
- handler = &raop_handler_options;
- } else if (!strcmp(method, "SETUP")) {
- handler = &raop_handler_setup;
- } else if (!strcmp(method, "GET_PARAMETER")) {
- handler = &raop_handler_get_parameter;
- } else if (!strcmp(method, "SET_PARAMETER")) {
- handler = &raop_handler_set_parameter;
- } else if (!strcmp(method, "POST") && !strcmp(url, "/feedback")) {
- handler = &raop_handler_feedback;
- } else if (!strcmp(method, "RECORD")) {
- handler = &raop_handler_record;
- } else if (!strcmp(method, "FLUSH")) {
- handler = &raop_handler_flush;
- } else if (!strcmp(method, "TEARDOWN")) {
- handler = &raop_handler_teardown;
- } else {
- logger_log(conn->raop->logger, LOGGER_INFO, "Unhandled Client Request: %s %s", method, url);
+ if (!hls_request && !strcmp(protocol, "RTSP/1.0")) {
+ if (!strcmp(method, "POST")) {
+ if (!strcmp(url, "/feedback")) {
+ handler = &raop_handler_feedback;
+ } else if (!strcmp(url, "/pair-pin-start")) {
+ handler = &raop_handler_pairpinstart;
+ } else if (!strcmp(url, "/pair-setup-pin")) {
+ handler = &raop_handler_pairsetup_pin;
+ } else if (!strcmp(url, "/pair-setup")) {
+ handler = &raop_handler_pairsetup;
+ } else if (!strcmp(url, "/pair-verify")) {
+ handler = &raop_handler_pairverify;
+ } else if (!strcmp(url, "/fp-setup")) {
+ handler = &raop_handler_fpsetup;
+ } else if (!strcmp(url, "/getProperty")) {
+ handler = &http_handler_get_property;
+ } else if (!strcmp(url, "/audioMode")) {
+ //handler = &http_handler_audioMode;
+ }
+ } else if (!strcmp(method, "GET")) {
+ if (!strcmp(url, "/info")) {
+ handler = &raop_handler_info;
+ }
+ } else if (!strcmp(method, "OPTIONS")) {
+ handler = &raop_handler_options;
+ } else if (!strcmp(method, "SETUP")) {
+ handler = &raop_handler_setup;
+ } else if (!strcmp(method, "GET_PARAMETER")) {
+ handler = &raop_handler_get_parameter;
+ } else if (!strcmp(method, "SET_PARAMETER")) {
+ handler = &raop_handler_set_parameter;
+ } else if (!strcmp(method, "RECORD")) {
+ handler = &raop_handler_record;
+ } else if (!strcmp(method, "FLUSH")) {
+ handler = &raop_handler_flush;
+ } else if (!strcmp(method, "TEARDOWN")) {
+ handler = &raop_handler_teardown;
+ }
+ } else if (!hls_request && !strcmp(protocol, "HTTP/1.1")) {
+ if (!strcmp(method, "POST")) {
+ if (!strcmp(url, "/reverse")) {
+ handler = &http_handler_reverse;
+ } else if (!strcmp(url, "/play")) {
+ handler = &http_handler_play;
+ } else if (!strncmp (url, "/getProperty?", strlen("/getProperty?"))) {
+ handler = &http_handler_get_property;
+ } else if (!strncmp(url, "/scrub?", strlen("/scrub?"))) {
+ handler = &http_handler_scrub;
+ } else if (!strncmp(url, "/rate?", strlen("/rate?"))) {
+ handler = &http_handler_rate;
+ } else if (!strcmp(url, "/stop")) {
+ handler = &http_handler_stop;
+ } else if (!strcmp(url, "/action")) {
+ handler = &http_handler_action;
+ } else if (!strcmp(url, "/fp-setup2")) {
+ handler = &http_handler_fpsetup2;
+ }
+ } else if (!strcmp(method, "GET")) {
+ if (!strcmp(url, "/server-info")) {
+ handler = &http_handler_server_info;
+ } else if (!strcmp(url, "/playback-info")) {
+ handler = &http_handler_playback_info;
+ }
+ } else if (!strcmp(method, "PUT")) {
+ if (!strncmp (url, "/setProperty?", strlen("/setProperty?"))) {
+ handler = &http_handler_set_property;
+ } else {
+ }
+ }
+ } else if (hls_request) {
+ handler = &http_handler_hls;
}
if (handler != NULL) {
handler(conn, request, *response, &response_data, &response_datalen);
+ } else {
+ logger_log(conn->raop->logger, LOGGER_INFO,
+ "Unhandled Client Request: %s %s %s", method, url, protocol);
}
+
finish:;
- http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION);
- http_response_add_header(*response, "CSeq", cseq);
+ if (!hls_request) {
+ http_response_add_header(*response, "Server", "AirTunes/"GLOBAL_VERSION);
+ if (cseq) {
+ http_response_add_header(*response, "CSeq", cseq);
+ }
+ }
http_response_finish(*response, response_data, response_datalen);
int len;
@@ -304,11 +434,14 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
}
header_str = utils_data_to_text(data, len);
logger_log(conn->raop->logger, LOGGER_DEBUG, "\n%s", header_str);
+
bool data_is_plist = (strstr(header_str,"apple-binary-plist") != NULL);
- bool data_is_text = (strstr(header_str,"text/parameters") != NULL);
+ bool data_is_text = (strstr(header_str,"text/") != NULL ||
+ strstr(header_str, "x-mpegURL") != NULL);
free(header_str);
if (response_data) {
if (response_datalen > 0 && logger_debug) {
+ /* logger has a buffer limit of 4096 */
if (data_is_plist) {
plist_t res_root_node = NULL;
plist_from_bin(response_data, response_datalen, &res_root_node);
@@ -316,21 +449,24 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
uint32_t plist_len;
plist_to_xml(res_root_node, &plist_xml, &plist_len);
plist_free(res_root_node);
- logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
+ printf("%s\n", plist_xml);
+ //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
} else if (data_is_text) {
char *data_str = utils_data_to_text((char*) response_data, response_datalen);
- logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ printf("%s\n", data_str);
+ //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
} else {
char *data_str = utils_data_to_string((unsigned char *) response_data, response_datalen, 16);
- logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ printf("%s\n", data_str);
+ //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
}
}
- free(response_data);
- response_data = NULL;
- response_datalen = 0;
+ if (response_data) {
+ free(response_data);
+ }
}
}
@@ -364,6 +500,13 @@ conn_destroy(void *ptr) {
free(conn->remote);
pairing_session_destroy(conn->session);
fairplay_destroy(conn->fairplay);
+ if (conn->client_session_id) {
+ free(conn->client_session_id);
+ }
+ if (conn->airplay_video) {
+ airplay_video_service_destroy(conn->airplay_video);
+ }
+
free(conn);
}
@@ -420,6 +563,8 @@ raop_init(raop_callbacks_t *callbacks) {
raop->max_ntp_timeouts = 0;
raop->audio_delay_micros = 250000;
+ raop->hls_support = false;
+
return raop;
}
@@ -474,6 +619,7 @@ raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile)
void
raop_destroy(raop_t *raop) {
if (raop) {
+ raop_destroy_airplay_video(raop);
raop_stop(raop);
pairing_destroy(raop->pairing);
httpd_destroy(raop->httpd);
@@ -533,6 +679,8 @@ int raop_set_plist(raop_t *raop, const char *plist_item, const int value) {
} else if (strcmp(plist_item, "pin") == 0) {
raop->pin = value;
raop->use_pin = true;
+ } else if (strcmp(plist_item, "hls") == 0) {
+ raop->hls_support = (value > 0 ? true : false);
} else {
retval = -1;
}
@@ -604,3 +752,27 @@ void raop_remove_known_connections(raop_t * raop) {
httpd_remove_known_connections(raop->httpd);
}
+airplay_video_t *deregister_airplay_video(raop_t *raop) {
+ airplay_video_t *airplay_video = raop->airplay_video;
+ raop->airplay_video = NULL;
+ return airplay_video;
+}
+
+bool register_airplay_video(raop_t *raop, airplay_video_t *airplay_video) {
+ if (raop->airplay_video) {
+ return false;
+ }
+ raop->airplay_video = airplay_video;
+ return true;
+}
+
+airplay_video_t * get_airplay_video(raop_t *raop) {
+ return raop->airplay_video;
+}
+
+void raop_destroy_airplay_video(raop_t *raop) {
+ if (raop->airplay_video) {
+ airplay_video_service_destroy(raop->airplay_video);
+ raop->airplay_video = NULL;
+ }
+}
diff --git a/lib/raop.h b/lib/raop.h
index 11571389..e0a20620 100644
--- a/lib/raop.h
+++ b/lib/raop.h
@@ -21,6 +21,7 @@
#include "dnssd.h"
#include "stream.h"
#include "raop_ntp.h"
+#include "airplay_video.h"
#if defined (WIN32) && defined(DLL_EXPORT)
# define RAOP_API __declspec(dllexport)
@@ -36,12 +37,29 @@ typedef struct raop_s raop_t;
typedef void (*raop_log_callback_t)(void *cls, int level, const char *msg);
+
+typedef struct playback_info_s {
+ //char * uuid;
+ uint32_t stallcount;
+ double duration;
+ double position;
+ float rate;
+ bool ready_to_play;
+ bool playback_buffer_empty;
+ bool playback_buffer_full;
+ bool playback_likely_to_keep_up;
+ int num_loaded_time_ranges;
+ int num_seekable_time_ranges;
+ void *loadedTimeRanges;
+ void *seekableTimeRanges;
+} playback_info_t;
+
typedef enum video_codec_e {
VIDEO_CODEC_UNKNOWN,
VIDEO_CODEC_H264,
VIDEO_CODEC_H265
} video_codec_t;
-
+
struct raop_callbacks_s {
void* cls;
@@ -49,8 +67,7 @@ struct raop_callbacks_s {
void (*video_process)(void *cls, raop_ntp_t *ntp, video_decode_struct *data);
void (*video_pause)(void *cls);
void (*video_resume)(void *cls);
- void (*video_codec) (void *cls, video_codec_t video_codec);
-
+
/* Optional but recommended callback functions */
void (*conn_init)(void *cls);
void (*conn_destroy)(void *cls);
@@ -72,11 +89,25 @@ struct raop_callbacks_s {
void (*export_dacp) (void *cls, const char *active_remote, const char *dacp_id);
void (*video_reset) (void *cls);
void (*video_set_codec)(void *cls, video_codec_t codec);
+ /* for HLS video player controls */
+ void (*on_video_play) (void *cls, const char *location, const float start_position);
+ void (*on_video_scrub) (void *cls, const float position);
+ void (*on_video_rate) (void *cls, const float rate);
+ void (*on_video_stop) (void *cls);
+ void (*on_video_acquire_playback_info) (void *cls, playback_info_t *playback_video);
+
};
typedef struct raop_callbacks_s raop_callbacks_t;
raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const char *remote,
- int remote_addr_len, unsigned short timing_rport, timing_protocol_t *time_protocol);
+ int remote_addr_len, unsigned short timing_rport,
+ timing_protocol_t *time_protocol);
+int airplay_video_service_init(raop_t *raop, unsigned short port, const char *session_id);
+
+bool register_airplay_video(raop_t *raop, airplay_video_t *airplay_video);
+airplay_video_t *get_airplay_video(raop_t *raop);
+airplay_video_t *deregister_airplay_video(raop_t *raop);
+
RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks);
RAOP_API int raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile);
RAOP_API void raop_set_log_level(raop_t *raop, int level);
@@ -93,6 +124,7 @@ RAOP_API void raop_stop(raop_t *raop);
RAOP_API void raop_set_dnssd(raop_t *raop, dnssd_t *dnssd);
RAOP_API void raop_destroy(raop_t *raop);
RAOP_API void raop_remove_known_connections(raop_t * raop);
+RAOP_API void raop_destroy_airplay_video(raop_t *raop);
#ifdef __cplusplus
}
diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h
index 98f62ddb..3b2c8388 100644
--- a/lib/raop_handlers.h
+++ b/lib/raop_handlers.h
@@ -197,7 +197,6 @@ raop_handler_pairpinstart(raop_conn_t *conn,
logger_log(conn->raop->logger, LOGGER_INFO, "*** CLIENT MUST NOW ENTER PIN = \"%s\" AS AIRPLAY PASSWORD", pin);
*response_data = NULL;
response_datalen = 0;
- return;
}
static void
@@ -749,13 +748,14 @@ raop_handler_setup(raop_conn_t *conn,
conn->raop_rtp_mirror = raop_rtp_mirror_init(conn->raop->logger, &conn->raop->callbacks,
conn->raop_ntp, remote, conn->remotelen, aeskey);
- // plist_t res_event_port_node = plist_new_uint(conn->raop->port);
- plist_t res_event_port_node = plist_new_uint(0);
+ /* the event port is not used in mirror mode or audio mode */
+ unsigned short event_port = 0;
+ plist_t res_event_port_node = plist_new_uint(event_port);
plist_t res_timing_port_node = plist_new_uint(timing_lport);
plist_dict_set_item(res_root_node, "timingPort", res_timing_port_node);
plist_dict_set_item(res_root_node, "eventPort", res_event_port_node);
- logger_log(conn->raop->logger, LOGGER_DEBUG, "eport = %d, tport = %d", 0, timing_lport);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "eport = %d, tport = %d", event_port, timing_lport);
}
// Process stream setup requests
diff --git a/lib/utils.c b/lib/utils.c
index f6c89f06..8f821868 100644
--- a/lib/utils.c
+++ b/lib/utils.c
@@ -282,3 +282,14 @@ int utils_ipaddress_to_string(int addresslen, const unsigned char *address, unsi
}
return ret;
}
+
+const char *gmt_time_string() {
+ static char date_buf[64];
+ memset(date_buf, 0, 64);
+
+ time_t now = time(0);
+ if (strftime(date_buf, 63, "%c GMT", gmtime(&now)))
+ return date_buf;
+ else
+ return "";
+}
diff --git a/lib/utils.h b/lib/utils.h
index be5db307..82df1f5a 100644
--- a/lib/utils.h
+++ b/lib/utils.h
@@ -30,6 +30,7 @@ char *utils_data_to_string(const unsigned char *data, int datalen, int chars_per
char *utils_data_to_text(const char *data, int datalen);
void ntp_timestamp_to_time(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);
void ntp_timestamp_to_seconds(uint64_t ntp_timestamp, char *timestamp, size_t maxsize);
+const char *gmt_time_string();
int utils_ipaddress_to_string(int addresslen, const unsigned char *address,
unsigned int zone_id, char *string, int len);
#endif
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 3846a9b5..53784a8f 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -3,7 +3,7 @@
* Copyright (C) 2019 Florian Draschbacher
* Modified for:
* UxPlay - An open-source AirPlay mirroring server
- * Copyright (C) 2021-24 F. Duncanh
+ * Copyright (C) 2021-23 F. Duncanh
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,10 +20,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-
+#include "video_renderer.h"
#include
#include
-#include "video_renderer.h"
#define SECOND_IN_NSECS 1000000000UL
#ifdef X_DISPLAY_FIX
@@ -31,7 +30,7 @@
#include "x_display_fix.h"
static bool fullscreen = false;
static bool alt_keypress = false;
-static unsigned char X11_search_attempts;
+static unsigned char X11_search_attempts;
#endif
static GstClockTime gst_video_pipeline_base_time = GST_CLOCK_TIME_NONE;
@@ -40,6 +39,10 @@ static unsigned short width, height, width_source, height_source; /* not curren
static bool first_packet = false;
static bool sync = false;
static bool auto_videosink = true;
+static bool hls_video = false;
+#ifdef X_DISPLAY_FIX
+static bool use_x11 = false;
+#endif
static bool logger_debug = false;
static bool video_terminate = false;
@@ -51,6 +54,9 @@ struct video_renderer_s {
const char *codec;
bool autovideo, state_pending;
int id;
+ gboolean terminate;
+ gint64 duration;
+ gint buffering_level;
#ifdef X_DISPLAY_FIX
bool use_x11;
const char * server_name;
@@ -63,6 +69,7 @@ static video_renderer_t *renderer_type[NCODECS] = {0};
static int n_renderers = NCODECS;
static char h264[] = "h264";
static char h265[] = "h265";
+static char hls[] = "hls";
static void append_videoflip (GString *launch, const videoflip_t *flip, const videoflip_t *rot) {
/* videoflip image transform */
@@ -85,7 +92,7 @@ static void append_videoflip (GString *launch, const videoflip_t *flip, const vi
case LEFT:
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UL_LR ! ");
break;
- case RIGHT:
+ case RIGHT:
g_string_append(launch, "videoflip video-direction=GST_VIDEO_ORIENTATION_UR_LL ! ");
break;
default:
@@ -142,26 +149,78 @@ void video_renderer_size(float *f_width_source, float *f_height_source, float *f
logger_log(logger, LOGGER_DEBUG, "begin video stream wxh = %dx%d; source %dx%d", width, height, width_source, height_source);
}
-void video_renderer_init(logger_t *render_logger, const char *server_name, videoflip_t videoflip[2], const char *parser,
+GstElement *make_video_sink(const char *videosink, const char *videosink_options) {
+ /* used to build a videosink for playbin, using the user-specified string "videosink" */
+ GstElement *video_sink = gst_element_factory_make(videosink, "videosink");
+ if (!video_sink) {
+ return NULL;
+ }
+
+ /* process the video_sink_optons */
+ size_t len = strlen(videosink_options);
+ if (!len) {
+ return video_sink;
+ }
+
+ char *options = (char *) malloc(len + 1);
+ strncpy(options, videosink_options, len + 1);
+
+ /* remove any extension begining with "!" */
+ char *end = strchr(options, '!');
+ if (end) {
+ *end = '\0';
+ }
+
+ /* add any fullscreen options "property=pval" included in string videosink_options*/
+ /* OK to use strtok_r in Windows with MSYS2 (POSIX); use strtok_s for MSVC */
+ char *token;
+ char *text = options;
+
+ while((token = strtok_r(text, " ", &text))) {
+ char *pval = strchr(token, '=');
+ if (pval) {
+ *pval = '\0';
+ pval++;
+ const gchar *property_name = (const gchar *) token;
+ const gchar *value = (const gchar *) pval;
+ g_print("playbin_videosink property: \"%s\" \"%s\"\n", property_name, value);
+ gst_util_set_object_arg(G_OBJECT (video_sink), property_name, value);
+ }
+ }
+ free(options);
+ return video_sink;
+}
+
+void video_renderer_init(logger_t *render_logger, const char *server_name, videoflip_t videoflip[2], const char *parser,
const char *decoder, const char *converter, const char *videosink, const char *videosink_options,
- bool initial_fullscreen, bool video_sync, bool h265_support) {
+ bool initial_fullscreen, bool video_sync, bool h265_support, const char *uri) {
GError *error = NULL;
GstCaps *caps = NULL;
-
+ hls_video = (uri != NULL);
/* videosink choices that are auto */
auto_videosink = (strstr(videosink, "autovideosink") || strstr(videosink, "fpsdisplaysink"));
-
+
logger = render_logger;
logger_debug = (logger_get_level(logger) >= LOGGER_DEBUG);
video_terminate = false;
-
+
/* this call to g_set_application_name makes server_name appear in the X11 display window title bar, */
/* (instead of the program name uxplay taken from (argv[0]). It is only set one time. */
+
const gchar *appname = g_get_application_name();
if (!appname || strcmp(appname,server_name)) g_set_application_name(server_name);
appname = NULL;
-
- n_renderers = h265_support ? 2 : 1;
+
+ /* the renderer for hls video will only be built if a HLS uri is provided in
+ * the call to video_renderer_init, in which case the h264 and 265 mirror-mode
+ * renderers will not be built. This is because it appears that we cannot
+ * put playbin into GST_STATE_READY before knowing the uri (?), so cannot use a
+ * unified renderer structure with h264, h265 and hls */
+ if (hls_video) {
+ n_renderers = 1;
+ } else {
+ n_renderers = h265_support ? 2 : 1;
+ }
g_assert (n_renderers <= NCODECS);
for (int i = 0; i < n_renderers; i++) {
g_assert (i < 2);
@@ -170,77 +229,97 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, video
renderer_type[i]->autovideo = auto_videosink;
renderer_type[i]->id = i;
renderer_type[i]->bus = NULL;
- switch (i) {
- case 0:
- renderer_type[i]->codec = h264;
- caps = gst_caps_from_string(h264_caps);
- break;
- case 1:
- renderer_type[i]->codec = h265;
- caps = gst_caps_from_string(h265_caps);
- break;
- default:
- g_assert(0);
- }
- GString *launch = g_string_new("appsrc name=video_source ! ");
- g_string_append(launch, "queue ! ");
- g_string_append(launch, parser);
- g_string_append(launch, " ! ");
- g_string_append(launch, decoder);
- g_string_append(launch, " ! ");
- append_videoflip(launch, &videoflip[0], &videoflip[1]);
- g_string_append(launch, converter);
- g_string_append(launch, " ! ");
- g_string_append(launch, "videoscale ! ");
- g_string_append(launch, videosink);
- g_string_append(launch, " name=");
- g_string_append(launch, videosink);
- g_string_append(launch, "_");
- g_string_append(launch, renderer_type[i]->codec);
- g_string_append(launch, videosink_options);
- if (video_sync) {
- g_string_append(launch, " sync=true");
- sync = true;
- } else {
- g_string_append(launch, " sync=false");
- sync = false;
- }
+ if (hls_video) {
+ /* use playbin3 to play HLS video: replace "playbin3" by "playbin" to use playbin2 */
+ renderer_type[i]->pipeline = gst_element_factory_make("playbin3", "hls-playbin3");
+ g_assert(renderer_type[i]->pipeline);
+ renderer_type[i]->appsrc = NULL;
+ renderer_type[i]->codec = hls;
+ /* if we are not using autovideosink, build a videossink based on the stricng "videosink" */
+ if(strcmp(videosink, "autovideosink")) {
+ GstElement *playbin_videosink = make_video_sink(videosink, videosink_options);
+
+ if (!playbin_videosink) {
+ logger_log(logger, LOGGER_ERR, "video_renderer_init: failed to create playbin_videosink");
+ } else {
+ logger_log(logger, LOGGER_DEBUG, "video_renderer_init: create playbin_videosink at %p", playbin_videosink);
+ g_object_set(G_OBJECT (renderer_type[i]->pipeline), "video-sink", playbin_videosink, NULL);
+ }
+ }
- if (!strcmp(renderer_type[i]->codec, h264)) {
- char *pos = launch->str;
- while ((pos = strstr(pos,h265))){
- pos +=3;
- *pos = '4';
+ g_object_set (G_OBJECT (renderer_type[i]->pipeline), "uri", uri, NULL);
+ } else {
+ switch (i) {
+ case 0:
+ renderer_type[i]->codec = h264;
+ caps = gst_caps_from_string(h264_caps);
+ break;
+ case 1:
+ renderer_type[i]->codec = h265;
+ caps = gst_caps_from_string(h265_caps);
+ break;
+ default:
+ g_assert(0);
}
- } else if (!strcmp(renderer_type[i]->codec, h265)) {
- char *pos = launch->str;
- while ((pos = strstr(pos,h264))){
- pos +=3;
- *pos = '5';
+ GString *launch = g_string_new("appsrc name=video_source ! ");
+ g_string_append(launch, "queue ! ");
+ g_string_append(launch, parser);
+ g_string_append(launch, " ! ");
+ g_string_append(launch, decoder);
+ g_string_append(launch, " ! ");
+ append_videoflip(launch, &videoflip[0], &videoflip[1]);
+ g_string_append(launch, converter);
+ g_string_append(launch, " ! ");
+ g_string_append(launch, "videoscale ! ");
+ g_string_append(launch, videosink);
+ g_string_append(launch, " name=");
+ g_string_append(launch, videosink);
+ g_string_append(launch, "_");
+ g_string_append(launch, renderer_type[i]->codec);
+ g_string_append(launch, videosink_options);
+ if (video_sync) {
+ g_string_append(launch, " sync=true");
+ sync = true;
+ } else {
+ g_string_append(launch, " sync=false");
+ sync = false;
}
- }
- logger_log(logger, LOGGER_DEBUG, "GStreamer video pipeline %d:\n\"%s\"", i + 1, launch->str);
- renderer_type[i]->pipeline = gst_parse_launch(launch->str, &error);
- if (error) {
- g_error ("get_parse_launch error (video) :\n %s\n",error->message);
- g_clear_error (&error);
- }
- g_assert (renderer_type[i]->pipeline);
-
- GstClock *clock = gst_system_clock_obtain();
- g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL);
-
- gst_pipeline_use_clock(GST_PIPELINE_CAST(renderer_type[i]->pipeline), clock);
- renderer_type[i]->appsrc = gst_bin_get_by_name (GST_BIN (renderer_type[i]->pipeline), "video_source");
- g_assert(renderer_type[i]->appsrc);
-
- g_object_set(renderer_type[i]->appsrc, "caps", caps, "stream-type", 0, "is-live", TRUE, "format", GST_FORMAT_TIME, NULL);
- g_string_free(launch, TRUE);
- gst_caps_unref(caps);
- gst_object_unref(clock);
+ if (!strcmp(renderer_type[i]->codec, h264)) {
+ char *pos = launch->str;
+ while ((pos = strstr(pos,h265))){
+ pos +=3;
+ *pos = '4';
+ }
+ } else if (!strcmp(renderer_type[i]->codec, h265)) {
+ char *pos = launch->str;
+ while ((pos = strstr(pos,h264))){
+ pos +=3;
+ *pos = '5';
+ }
+ }
+
+ logger_log(logger, LOGGER_DEBUG, "GStreamer video pipeline %d:\n\"%s\"", i + 1, launch->str);
+ renderer_type[i]->pipeline = gst_parse_launch(launch->str, &error);
+ if (error) {
+ g_error ("get_parse_launch error (video) :\n %s\n",error->message);
+ g_clear_error (&error);
+ }
+ g_assert (renderer_type[i]->pipeline);
+
+ GstClock *clock = gst_system_clock_obtain();
+ g_object_set(clock, "clock-type", GST_CLOCK_TYPE_REALTIME, NULL);
+ gst_pipeline_use_clock(GST_PIPELINE_CAST(renderer_type[i]->pipeline), clock);
+ renderer_type[i]->appsrc = gst_bin_get_by_name (GST_BIN (renderer_type[i]->pipeline), "video_source");
+ g_assert(renderer_type[i]->appsrc);
+
+ g_object_set(renderer_type[i]->appsrc, "caps", caps, "stream-type", 0, "is-live", TRUE, "format", GST_FORMAT_TIME, NULL);
+ g_string_free(launch, TRUE);
+ gst_caps_unref(caps);
+ gst_object_unref(clock);
+ }
#ifdef X_DISPLAY_FIX
- bool use_x11 = (strstr(videosink, "xvimagesink") || strstr(videosink, "ximagesink") || auto_videosink);
+ use_x11 = (strstr(videosink, "xvimagesink") || strstr(videosink, "ximagesink") || auto_videosink);
fullscreen = initial_fullscreen;
renderer_type[i]->server_name = server_name;
renderer_type[i]->gst_window = NULL;
@@ -269,36 +348,54 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, video
GstState state;
if (gst_element_get_state (renderer_type[i]->pipeline, &state, NULL, 0)) {
if (state == GST_STATE_READY) {
- logger_log(logger, LOGGER_DEBUG, "Initialized GStreamer video renderer %d", i + 1);
+ logger_log(logger, LOGGER_DEBUG, "Initialized GStreamer video renderer %d", i + 1);
+ if (hls_video && i == 0) {
+ renderer = renderer_type[i];
+ }
} else {
- logger_log(logger, LOGGER_ERR, "Failed to initialize GStreamer video renderer %d", i + 1);
+ logger_log(logger, LOGGER_ERR, "Failed to initialize GStreamer video renderer %d", i + 1);
}
} else {
- logger_log(logger, LOGGER_ERR, "Failed to initialize GStreamer video renderer %d", i + 1);
- }
+ logger_log(logger, LOGGER_ERR, "Failed to initialize GStreamer video renderer %d", i + 1);
+ }
}
}
void video_renderer_pause() {
+ if (!renderer) {
+ return;
+ }
logger_log(logger, LOGGER_DEBUG, "video renderer paused");
gst_element_set_state(renderer->pipeline, GST_STATE_PAUSED);
}
void video_renderer_resume() {
+ if (!renderer) {
+ return;
+ }
gst_element_set_state (renderer->pipeline, GST_STATE_PLAYING);
GstState state;
/* wait with timeout 100 msec for pipeline to change state from PAUSED to PLAYING */
gst_element_get_state(renderer->pipeline, &state, NULL, 100 * GST_MSECOND);
const gchar *state_name = gst_element_state_get_name(state);
logger_log(logger, LOGGER_DEBUG, "video renderer resumed: state %s", state_name);
- gst_video_pipeline_base_time = gst_element_get_base_time(renderer->appsrc);
+ if (renderer->appsrc) {
+ gst_video_pipeline_base_time = gst_element_get_base_time(renderer->appsrc);
+ }
}
void video_renderer_start() {
- /* start both h264 and h265 pipelines; will shut down the "wrong" one when we know the codec */
+ if (hls_video) {
+ renderer->bus = gst_element_get_bus(renderer->pipeline);
+ gst_element_set_state (renderer->pipeline, GST_STATE_PLAYING);
+ return;
+ }
+ /* when not hls, start both h264 and h265 pipelines; will shut down the "wrong" one when we know the codec */
for (int i = 0; i < n_renderers; i++) {
gst_element_set_state (renderer_type[i]->pipeline, GST_STATE_PLAYING);
- gst_video_pipeline_base_time = gst_element_get_base_time(renderer_type[i]->appsrc);
+ if (renderer_type[i]->appsrc) {
+ gst_video_pipeline_base_time = gst_element_get_base_time(renderer_type[i]->appsrc);
+ }
renderer_type[i]->bus = gst_element_get_bus(renderer_type[i]->pipeline);
}
renderer = NULL;
@@ -308,6 +405,23 @@ void video_renderer_start() {
#endif
}
+/* used to find any X11 Window used by the playbin (HLS) pipeline after it starts playing.
+* if use_x11 is true, called every 100 ms after playbin state is READY until the x11 window is found*/
+bool waiting_for_x11_window() {
+ if (!hls_video) {
+ return false;
+ }
+#ifdef X_DISPLAY_FIX
+ if (use_x11 && renderer->gst_window) {
+ get_x_window(renderer->gst_window, renderer->server_name);
+ if (!renderer->gst_window->window) {
+ return true; /* window still not found */
+ }
+ }
+#endif
+ return false;
+}
+
void video_renderer_render_buffer(unsigned char* data, int *data_len, int *nal_count, uint64_t *ntp_time) {
GstBuffer *buffer;
GstClockTime pts = (GstClockTime) *ntp_time; /*now in nsecs */
@@ -362,21 +476,28 @@ void video_renderer_flush() {
void video_renderer_stop() {
if (renderer) {
- gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
+ if (renderer->appsrc) {
+ gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
+ }
gst_element_set_state (renderer->pipeline, GST_STATE_NULL);
- }
+ //gst_element_set_state (renderer->playbin, GST_STATE_NULL);
+ }
}
-static void video_renderer_destroy_h26x(video_renderer_t *renderer) {
+void video_renderer_destroy() {
if (renderer) {
GstState state;
gst_element_get_state(renderer->pipeline, &state, NULL, 0);
if (state != GST_STATE_NULL) {
- gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
+ if (!hls_video) {
+ gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
+ }
gst_element_set_state (renderer->pipeline, GST_STATE_NULL);
}
gst_object_unref(renderer->bus);
- gst_object_unref (renderer->appsrc);
+ if (renderer->appsrc) {
+ gst_object_unref (renderer->appsrc);
+ }
gst_object_unref (renderer->pipeline);
#ifdef X_DISPLAY_FIX
if (renderer->gst_window) {
@@ -389,20 +510,7 @@ static void video_renderer_destroy_h26x(video_renderer_t *renderer) {
}
}
-
-void video_renderer_destroy() {
- for (int i = 0; i < n_renderers; i++) {
- if (renderer_type[i]) {
- video_renderer_destroy_h26x(renderer_type[i]);
- }
- }
-}
-
-/* not implemented for gstreamer */
-void video_renderer_update_background(int type) {
-}
-
-gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void * loop) {
+gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void *loop) {
/* identify which pipeline sent the message */
int type = -1;
@@ -413,18 +521,49 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void
}
}
g_assert(type != -1);
-
+
if (logger_debug) {
g_print("GStreamer %s bus message: %s %s\n", renderer_type[type]->codec, GST_MESSAGE_SRC_NAME(message), GST_MESSAGE_TYPE_NAME(message));
}
+
+ if (logger_debug && hls_video) {
+ gint64 pos;
+ gst_element_query_position (renderer_type[type]->pipeline, GST_FORMAT_TIME, &pos);
+ if (GST_CLOCK_TIME_IS_VALID(pos)) {
+ g_print("GStreamer bus message %s %s; position: %" GST_TIME_FORMAT "\n", GST_MESSAGE_SRC_NAME(message),
+ GST_MESSAGE_TYPE_NAME(message), GST_TIME_ARGS(pos));
+ } else {
+ g_print("GStreamer bus message %s %s; position: none\n", GST_MESSAGE_SRC_NAME(message),
+ GST_MESSAGE_TYPE_NAME(message));
+ }
+ }
+
switch (GST_MESSAGE_TYPE (message)) {
+ case GST_MESSAGE_DURATION:
+ renderer_type[type]->duration = GST_CLOCK_TIME_NONE;
+ break;
+ case GST_MESSAGE_BUFFERING:
+ if (hls_video) {
+ gint percent = -1;
+ gst_message_parse_buffering(message, &percent);
+ if (percent >= 0) {
+ renderer_type[type]->buffering_level = percent;
+ logger_log(logger, LOGGER_DEBUG, "Buffering :%u percent done", percent);
+ if (percent < 100) {
+ gst_element_set_state (renderer_type[type]->pipeline, GST_STATE_PAUSED);
+ } else {
+ gst_element_set_state (renderer_type[type]->pipeline, GST_STATE_PLAYING);
+ }
+ }
+ }
+ break;
case GST_MESSAGE_ERROR: {
GError *err;
gchar *debug;
gboolean flushing;
gst_message_parse_error (message, &err, &debug);
- logger_log(logger, LOGGER_INFO, "GStreamer error: %s", err->message);
- if (strstr(err->message,"Internal data stream error")) {
+ logger_log(logger, LOGGER_INFO, "GStreamer error: %s %s", GST_MESSAGE_SRC_NAME(message),err->message);
+ if (!hls_video && strstr(err->message,"Internal data stream error")) {
logger_log(logger, LOGGER_INFO,
"*** This is a generic GStreamer error that usually means that GStreamer\n"
"*** was unable to construct a working video pipeline.\n\n"
@@ -436,19 +575,27 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void
}
g_error_free (err);
g_free (debug);
- gst_app_src_end_of_stream (GST_APP_SRC(renderer_type[type]->appsrc));
- flushing = TRUE;
- gst_bus_set_flushing(bus, flushing);
- gst_element_set_state (renderer_type[type]->pipeline, GST_STATE_NULL);
- g_main_loop_quit( (GMainLoop *) loop);
+ if (renderer_type[type]->appsrc) {
+ gst_app_src_end_of_stream (GST_APP_SRC(renderer_type[type]->appsrc));
+ }
+ gst_bus_set_flushing(bus, TRUE);
+ gst_element_set_state (renderer_type[type]->pipeline, GST_STATE_READY);
+ renderer_type[type]->terminate = TRUE;
+ g_main_loop_quit( (GMainLoop *) loop);
break;
}
case GST_MESSAGE_EOS:
/* end-of-stream */
- logger_log(logger, LOGGER_INFO, "GStreamer: End-Of-Stream");
- // g_main_loop_quit( (GMainLoop *) loop);
+ logger_log(logger, LOGGER_INFO, "GStreamer: End-Of-Stream");
+ if (hls_video) {
+ gst_bus_set_flushing(bus, TRUE);
+ gst_element_set_state (renderer_type[type]->pipeline, GST_STATE_READY);
+ renderer_type[type]->terminate = TRUE;
+ g_main_loop_quit( (GMainLoop *) loop);
+ }
break;
case GST_MESSAGE_STATE_CHANGED:
+ESSAGE_STATE_CHANGED:
if (renderer_type[type]->state_pending && strstr(GST_MESSAGE_SRC_NAME(message), "pipeline")) {
GstState state;
gst_element_get_state(renderer_type[type]->pipeline, &state, NULL,0);
@@ -519,6 +666,7 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void
}
void video_renderer_choose_codec (bool video_is_h265) {
+ g_assert(!hls_video);
/* set renderer to h264 or h265, depending on pps/sps received by raop_rtp_mirror */
video_renderer_t *renderer_new = video_is_h265 ? renderer_type[1] : renderer_type[0];
if (renderer == renderer_new) {
@@ -543,7 +691,9 @@ void video_renderer_choose_codec (bool video_is_h265) {
unsigned int video_reset_callback(void * loop) {
if (video_terminate) {
video_terminate = false;
- gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
+ if (renderer->appsrc) {
+ gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
+ }
gboolean flushing = TRUE;
gst_bus_set_flushing(renderer->bus, flushing);
gst_element_set_state (renderer->pipeline, GST_STATE_NULL);
@@ -552,6 +702,63 @@ unsigned int video_reset_callback(void * loop) {
return (unsigned int) TRUE;
}
+bool video_get_playback_info(double *duration, double *position, float *rate) {
+ gint64 pos = 0;
+ GstState state;
+ *duration = 0.0;
+ *position = -1.0;
+ *rate = 0.0f;
+ if (!renderer) {
+
+ return true;
+ }
+ gst_element_get_state(renderer->pipeline, &state, NULL, 0);
+ *rate = 0.0f;
+ switch (state) {
+ case GST_STATE_PLAYING:
+ *rate = 1.0f;
+ default:
+ break;
+ }
+
+ if (!GST_CLOCK_TIME_IS_VALID(renderer->duration)) {
+ if (!gst_element_query_duration (renderer->pipeline, GST_FORMAT_TIME, &renderer->duration)) {
+ return true;
+ }
+ }
+ *duration = ((double) renderer->duration) / GST_SECOND;
+ if (*duration) {
+ if (gst_element_query_position (renderer->pipeline, GST_FORMAT_TIME, &pos) &&
+ GST_CLOCK_TIME_IS_VALID(pos)) {
+ *position = ((double) pos) / GST_SECOND;
+ }
+ }
+
+ logger_log(logger, LOGGER_DEBUG, "********* video_get_playback_info: position %" GST_TIME_FORMAT " duration %" GST_TIME_FORMAT " %s *********",
+ GST_TIME_ARGS (pos), GST_TIME_ARGS (renderer->duration), gst_element_state_get_name(state));
+
+ return true;
+}
+
+void video_renderer_seek(float position) {
+ double pos = (double) position;
+ pos *= GST_SECOND;
+ gint64 seek_position = (gint64) pos;
+ seek_position = seek_position < 1000 ? 1000 : seek_position;
+ seek_position = seek_position > renderer->duration - 1000 ? renderer->duration - 1000: seek_position;
+ g_print("SCRUB: seek to %f secs = %" GST_TIME_FORMAT ", duration = %" GST_TIME_FORMAT "\n", position,
+ GST_TIME_ARGS(seek_position), GST_TIME_ARGS(renderer->duration));
+ gboolean result = gst_element_seek_simple(renderer->pipeline, GST_FORMAT_TIME,
+ (GstSeekFlags)(GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT),
+ seek_position);
+ if (result) {
+ g_print("seek succeeded\n");
+ gst_element_set_state (renderer->pipeline, GST_STATE_PLAYING);
+ } else {
+ g_print("seek failed\n");
+ }
+}
+
unsigned int video_renderer_listen(void *loop, int id) {
g_assert(id >= 0 && id < n_renderers);
return (unsigned int) gst_bus_add_watch(renderer_type[id]->bus,(GstBusFunc)
diff --git a/renderers/video_renderer.h b/renderers/video_renderer.h
index 23c89388..4db432a5 100644
--- a/renderers/video_renderer.h
+++ b/renderers/video_renderer.h
@@ -46,23 +46,26 @@ typedef enum videoflip_e {
} videoflip_t;
typedef struct video_renderer_s video_renderer_t;
-
-void video_renderer_init(logger_t *render_logger, const char *server_name, videoflip_t videoflip[2], const char *parser,
- const char *decoder, const char *converter, const char *videosink, const char *videosin_options,
- bool initial_fullscreen, bool video_sync, bool h265_support);
+
+void video_renderer_init (logger_t *logger, const char *server_name, videoflip_t videoflip[2], const char *parser,
+ const char *decoder, const char *converter, const char *videosink, const char *videosink_options,
+ bool initial_fullscreen, bool video_sync, bool h265_support, const char *uri);
void video_renderer_start ();
void video_renderer_stop ();
void video_renderer_pause ();
+void video_renderer_seek(float position);
void video_renderer_resume ();
+bool video_renderer_is_paused();
void video_renderer_render_buffer (unsigned char* data, int *data_len, int *nal_count, uint64_t *ntp_time);
void video_renderer_flush ();
+unsigned int video_renderer_listen(void *loop, int id);
void video_renderer_destroy ();
void video_renderer_size(float *width_source, float *height_source, float *width, float *height);
+bool waiting_for_x11_window();
+bool video_get_playback_info(double *duration, double *position, float *rate);
void video_renderer_choose_codec(bool is_h265);
-
unsigned int video_renderer_listen(void *loop, int id);
unsigned int video_reset_callback(void *loop);
-
#ifdef __cplusplus
}
#endif
diff --git a/uxplay.1 b/uxplay.1
index 419780ac..4c391785 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -1,11 +1,11 @@
-.TH UXPLAY "1" "September 2024" "1.70" "User Commands"
+.TH UXPLAY "1" "December 2024" "1.71" "User Commands"
.SH NAME
uxplay \- start AirPlay server
.SH SYNOPSIS
.B uxplay
[\fI\,-n name\/\fR] [\fI\,-s wxh\/\fR] [\fI\,-p \/\fR[\fI\,n\/\fR]] [more \fI OPTIONS \/\fR ...]
.SH DESCRIPTION
-UxPlay 1.70: An open\-source AirPlay mirroring (+ audio streaming) server:
+UxPlay 1.71: An open\-source AirPlay mirroring (+ audio streaming) server:
.SH OPTIONS
.TP
.B
@@ -15,6 +15,8 @@ UxPlay 1.70: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-h265\fR Support h265 (4K) video (with h265 versions of h264 plugins)
.TP
+\fB\-hls\fR Support HTTP Live Streaming (currently YouTube video only)
+.TP
\fB\-pin\fI[xxxx]\fRUse a 4-digit pin code to control client access (default: no)
.IP
without option, pin is random: optionally use fixed pin xxxx.
diff --git a/uxplay.cpp b/uxplay.cpp
index b2805fa3..d6a2d1ee 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -62,7 +62,7 @@
#include "renderers/video_renderer.h"
#include "renderers/audio_renderer.h"
-#define VERSION "1.70"
+#define VERSION "1.71"
#define SECOND_IN_USECS 1000000
#define SECOND_IN_NSECS 1000000000UL
@@ -144,6 +144,11 @@ static double db_high = 0.0;
static bool taper_volume = false;
static bool h265_support = false;
static int n_renderers = 0;
+static bool hls_support = false;
+static std::string url = "";
+static guint gst_x11_window_id = 0;
+static guint gst_hls_position_id = 0;
+static bool preserve_connections = false;
/* logging */
@@ -360,6 +365,16 @@ static gboolean reset_callback(gpointer loop) {
return TRUE;
}
+static gboolean x11_window_callback(gpointer loop) {
+ /* called while trying to find an x11 window used by playbin (HLS mode) */
+ if (waiting_for_x11_window()) {
+ return TRUE;
+ }
+ g_source_remove(gst_x11_window_id);
+ gst_x11_window_id = 0;
+ return FALSE;
+}
+
static gboolean sigint_callback(gpointer loop) {
relaunch_video = false;
g_main_loop_quit((GMainLoop *) loop);
@@ -400,6 +415,15 @@ static void main_loop() {
relaunch_video = false;
if (use_video) {
relaunch_video = true;
+ if (url.empty()) {
+ n_renderers = h265_support ? 2 : 1;
+ gst_x11_window_id = 0;
+ } else {
+ /* hls video will be rendered */
+ n_renderers = 1;
+ url.erase();
+ gst_x11_window_id = g_timeout_add(100, (GSourceFunc) x11_window_callback, (gpointer) loop);
+ }
for (int i = 0; i < n_renderers; i++) {
gst_bus_watch_id[i] = (guint) video_renderer_listen((void *)loop, i);
}
@@ -408,12 +432,12 @@ static void main_loop() {
guint video_reset_watch_id = g_timeout_add(100, (GSourceFunc) video_reset_callback, (gpointer) loop);
guint sigterm_watch_id = g_unix_signal_add(SIGTERM, (GSourceFunc) sigterm_callback, (gpointer) loop);
guint sigint_watch_id = g_unix_signal_add(SIGINT, (GSourceFunc) sigint_callback, (gpointer) loop);
- //printf("********** main_loop_run *******************\n");
g_main_loop_run(loop);
- //printf("********** main_loop_exit *******************\n");
+
for (int i = 0; i < n_renderers; i++) {
if (gst_bus_watch_id[i] > 0) g_source_remove(gst_bus_watch_id[i]);
}
+ if (gst_x11_window_id > 0) g_source_remove(gst_x11_window_id);
if (sigint_watch_id > 0) g_source_remove(sigint_watch_id);
if (sigterm_watch_id > 0) g_source_remove(sigterm_watch_id);
if (reset_watch_id > 0) g_source_remove(reset_watch_id);
@@ -582,6 +606,7 @@ static void print_info (char *name) {
printf("-n name Specify the network name of the AirPlay server\n");
printf("-nh Do not add \"@hostname\" at the end of AirPlay server name\n");
printf("-h265 Support h265 (4K) video (with h265 versions of h264 plugins)\n");
+ printf("-hls Support HTTP Live Streaming (currently Youtube video only) \n");
printf("-pin[xxxx]Use a 4-digit pin code to control client access (default: no)\n");
printf(" default pin is random: optionally use fixed pin xxxx\n");
printf("-reg [fn] Keep a register in $HOME/.uxplay.register to verify returning\n");
@@ -593,7 +618,7 @@ static void print_info (char *name) {
printf("-async no Switch off audio/(client)video timestamp synchronization\n");
printf("-db l[:h] Set minimum volume attenuation to l dB (decibels, negative);\n");
printf(" optional: set maximum to h dB (+ or -) default: -30.0:0.0 dB\n");
- printf("-taper Use a \"tapered\" AirPlay volume-control profile\n");
+ printf("-taper Use a \"tapered\" AirPlay volume-control profile\n");
printf("-s wxh[@r]Request to client for video display resolution [refresh_rate]\n");
printf(" default 1920x1080[@60] (or 3840x2160[@60] with -h265 option)\n");
printf("-o Set display \"overscanned\" mode on (not usually needed)\n");
@@ -607,6 +632,7 @@ static void print_info (char *name) {
printf("-vd ... Choose the GStreamer h264 decoder; default \"decodebin\"\n");
printf(" choices: (software) avdec_h264; (hardware) v4l2h264dec,\n");
printf(" nvdec, nvh264dec, vaapih64dec, vtdec,etc.\n");
+ printf(" choices: avdec_h264,vaapih264dec,nvdec,nvh264dec,v4l2h264dec\n");
printf("-vc ... Choose the GStreamer videoconverter; default \"videoconvert\"\n");
printf(" another choice when using v4l2h264dec: v4l2convert\n");
printf("-vs ... Choose the GStreamer videosink; default \"autovideosink\"\n");
@@ -1145,6 +1171,8 @@ static void parse_arguments (int argc, char *argv[]) {
db_low = db1;
db_high = db2;
printf("db range %f:%f\n", db_low, db_high);
+ } else if (arg == "-hls") {
+ hls_support = true;
} else if (arg == "-h265") {
h265_support = true;
} else if (arg == "-nofreeze") {
@@ -1356,7 +1384,7 @@ static int start_dnssd(std::vector hw_addr, std::string name) {
}
/* after dnssd starts, reset the default feature set here
- * (overwrites features set in dnssdint.h).
+ * (overwrites features set in dnssdint.h)
* default: FEATURES_1 = 0x5A7FFEE6, FEATURES_2 = 0 */
dnssd_set_airplay_features(dnssd, 0, 0); // AirPlay video supported
@@ -1399,7 +1427,8 @@ static int start_dnssd(std::vector hw_addr, std::string name) {
dnssd_set_airplay_features(dnssd, 30, 1); // RAOP support: with this bit set, the AirTunes service is not required.
dnssd_set_airplay_features(dnssd, 31, 0); //
- /* bits 32-63 see https://emanualcozzi.net/docs/airplay2/features
+
+ /* bits 32-63: see https://emanualcozzi.net/docs/airplay2/features
dnssd_set_airplay_features(dnssd, 32, 0); // isCarPlay when ON,; Supports InitialVolume when OFF
dnssd_set_airplay_features(dnssd, 33, 0); // Supports Air Play Video Play Queue
dnssd_set_airplay_features(dnssd, 34, 0); // Supports Air Play from cloud (requires that bit 6 is ON)
@@ -1412,8 +1441,7 @@ static int start_dnssd(std::vector hw_addr, std::string name) {
dnssd_set_airplay_features(dnssd, 40, 0); // Supports Buffered Audio
dnssd_set_airplay_features(dnssd, 41, 0); // Supports PTP
-
- dnssd_set_airplay_features(dnssd, 42, 0); // Supports Screen Multi Codec (allows h265 video)
+ dnssd_set_airplay_features(dnssd, 42, 0); // Supports Screen Multi Codec (allows h265 video)
dnssd_set_airplay_features(dnssd, 43, 0); // Supports System Pairing
dnssd_set_airplay_features(dnssd, 44, 0); // is AP Valeria Screen Sender
@@ -1440,9 +1468,15 @@ static int start_dnssd(std::vector hw_addr, std::string name) {
dnssd_set_airplay_features(dnssd, 61, 0); // Supports RFC2198 redundancy
*/
+ /* needed for HLS video support */
+ dnssd_set_airplay_features(dnssd, 0, (int) hls_support);
+ dnssd_set_airplay_features(dnssd, 4, (int) hls_support);
+ // not sure about this one (bit 8, screen rotation supported):
+ //dnssd_set_airplay_features(dnssd, 8, (int) hls_support);
+
/* needed for h265 video support */
dnssd_set_airplay_features(dnssd, 42, (int) h265_support);
-
+
/* bit 27 of Features determines whether the AirPlay2 client-pairing protocol will be used (1) or not (0) */
dnssd_set_airplay_features(dnssd, 27, (int) setup_legacy_pairing);
return 0;
@@ -1475,6 +1509,8 @@ static bool check_blocked_client(char *deviceid) {
// Server callbacks
extern "C" void video_reset(void *cls) {
+ LOGD("video_reset");
+ url.erase();
reset_loop = true;
remote_clock_offset = 0;
relaunch_video = true;
@@ -1539,6 +1575,7 @@ extern "C" void conn_reset (void *cls, int timeouts, bool reset_video) {
LOGI(" Sometimes the network connection may recover after a longer delay:\n"
" the default timeout limit n = %d can be changed with the \"-reset n\" option", NTP_TIMEOUT_LIMIT);
}
+ printf("reset_video %d\n",(int) reset_video);
if (!nofreeze) {
close_window = reset_video; /* leave "frozen" window open if reset_video is false */
}
@@ -1801,6 +1838,53 @@ extern "C" bool check_register(void *cls, const char *client_pk) {
return false;
}
}
+/* control callbacks for video player (unimplemented) */
+
+extern "C" void on_video_play(void *cls, const char* location, const float start_position) {
+ /* start_position needs to be implemented */
+ url.erase();
+ url.append(location);
+ reset_loop = true;
+ relaunch_video = true;
+ preserve_connections = true;
+ LOGD("********************on_video_play: location = %s***********************", url.c_str());
+}
+
+extern "C" void on_video_scrub(void *cls, const float position) {
+ LOGI("on_video_scrub: position = %7.5f\n", position);
+ video_renderer_seek(position);
+}
+
+extern "C" void on_video_rate(void *cls, const float rate) {
+ LOGI("on_video_rate = %7.5f\n", rate);
+ if (rate == 1.0f) {
+ video_renderer_resume();
+ } else if (rate == 0.0f) {
+ video_renderer_pause();
+ } else {
+ LOGI("on_video_rate: ignoring unexpected value rate = %f\n", rate);
+ }
+}
+
+extern "C" void on_video_stop(void *cls) {
+ LOGI("on_video_stop\n");
+}
+
+extern "C" void on_video_acquire_playback_info (void *cls, playback_info_t *playback_info) {
+ int buffering_level;
+ LOGD("on_video_acquire_playback info\n");
+ bool still_playing = video_get_playback_info(&playback_info->duration, &playback_info->position,
+ &playback_info->rate);
+ LOGD("on_video_acquire_playback info done\n");
+ if (!still_playing) {
+ LOGI(" video has finished, %f", playback_info->position);
+ playback_info->position = -1.0;
+ playback_info->duration = -1.0;
+ printf("about to stop\n");
+ video_renderer_stop();
+ printf("stopped\n");
+ }
+}
extern "C" void log_callback (void *cls, int level, const char *msg) {
switch (level) {
@@ -1851,6 +1935,11 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
raop_cbs.export_dacp = export_dacp;
raop_cbs.video_reset = video_reset;
raop_cbs.video_set_codec = video_set_codec;
+ raop_cbs.on_video_play = on_video_play;
+ raop_cbs.on_video_scrub = on_video_scrub;
+ raop_cbs.on_video_rate = on_video_rate;
+ raop_cbs.on_video_stop = on_video_stop;
+ raop_cbs.on_video_acquire_playback_info = on_video_acquire_playback_info;
raop = raop_init(&raop_cbs);
if (raop == NULL) {
@@ -1879,6 +1968,7 @@ static int start_raop_server (unsigned short display[5], unsigned short tcp[3],
raop_set_plist(raop, "max_ntp_timeouts", max_ntp_timeouts);
if (audiodelay >= 0) raop_set_plist(raop, "audio_delay_micros", audiodelay);
if (require_password) raop_set_plist(raop, "pin", (int) pin);
+ if (hls_support) raop_set_plist(raop, "hls", 1);
/* network port selection (ports listed as "0" will be dynamically assigned) */
raop_set_tcp_ports(raop, tcp);
@@ -2070,9 +2160,9 @@ int main (int argc, char *argv[]) {
if (videosink == "d3d11videosink" && videosink_options.empty() && use_video) {
if (fullscreen) {
- videosink_options.append(" fullscreen-toggle-mode=GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY fullscreen=true ");
+ videosink_options.append(" fullscreen-toggle-mode=GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_PROPERTY fullscreen=true ");
} else {
- videosink_options.append(" fullscreen-toggle-mode=GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER ");
+ videosink_options.append(" fullscreen-toggle-mode=GST_D3D11_WINDOW_FULLSCREEN_TOGGLE_MODE_ALT_ENTER ");
}
LOGI("d3d11videosink is being used with option fullscreen-toggle-mode=alt-enter\n"
"Use Alt-Enter key combination to toggle into/out of full-screen mode");
@@ -2148,12 +2238,10 @@ int main (int argc, char *argv[]) {
} else {
LOGI("audio_disabled");
}
-
if (use_video) {
- n_renderers = h265_support ? 2 : 1;
video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(),
video_decoder.c_str(), video_converter.c_str(), videosink.c_str(),
- videosink_options.c_str(),fullscreen, video_sync, h265_support);
+ videosink_options.c_str(), fullscreen, video_sync, h265_support, NULL);
video_renderer_start();
}
@@ -2196,7 +2284,6 @@ int main (int argc, char *argv[]) {
if (start_dnssd(server_hw_addr, server_name)) {
goto cleanup;
}
-
if (start_raop_server(display, tcp, udp, debug_log)) {
stop_dnssd();
goto cleanup;
@@ -2209,7 +2296,7 @@ int main (int argc, char *argv[]) {
reconnect:
compression_type = 0;
close_window = new_window_closing_behavior;
-
+
main_loop();
if (relaunch_video || reset_loop) {
if(reset_loop) {
@@ -2218,12 +2305,18 @@ int main (int argc, char *argv[]) {
raop_stop(raop);
}
if (use_audio) audio_renderer_stop();
- if (use_video && close_window) {
+ if (use_video && (close_window || preserve_connections)) {
video_renderer_destroy();
- raop_remove_known_connections(raop);
+ if (!preserve_connections) {
+ raop_destroy_airplay_video(raop);
+ url.erase();
+ raop_remove_known_connections(raop);
+ }
+ preserve_connections = false;
+ const char *uri = (url.empty() ? NULL : url.c_str());
video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(),
- video_decoder.c_str(), video_converter.c_str(), videosink.c_str(),
- videosink_options.c_str(), fullscreen, video_sync, h265_support);
+ video_decoder.c_str(), video_converter.c_str(), videosink.c_str(),
+ videosink_options.c_str(), fullscreen, video_sync, h265_support, uri);
video_renderer_start();
}
if (relaunch_video) {
diff --git a/uxplay.spec b/uxplay.spec
index 93fed464..6e9c7dc1 100644
--- a/uxplay.spec
+++ b/uxplay.spec
@@ -1,5 +1,5 @@
Name: uxplay
-Version: 1.70
+Version: 1.71
Release: 1%{?dist}
%global gittag v%{version}
@@ -135,7 +135,7 @@ cd build
%{_docdir}/%{name}/llhttp/LICENSE-MIT
%changelog
-* Tue Sep 17 2024 UxPlay maintainer
+* Fri Nov 15 2024 UxPlay maintainer
Initial uxplay.spec: tested on Fedora 38, Rocky Linux 9.2, OpenSUSE
Leap 15.5, Mageia 9, OpenMandriva ROME, PCLinuxOS
-
From da7104fdc376d6e5d4e15f8908651d88b59e06c6 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 10 Dec 2024 12:38:09 -0500
Subject: [PATCH 12/23] Bump cmake min version to 3.10 (to silence deprecation
warning)
---
CMakeLists.txt | 2 +-
README.html | 7 +++++--
README.md | 3 ++-
README.txt | 7 +++++--
4 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 2a2ab756..03cd02d9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
if ( APPLE )
cmake_minimum_required( VERSION 3.13 )
else ()
- cmake_minimum_required( VERSION 3.5 )
+ cmake_minimum_required( VERSION 3.10 )
endif ()
project( uxplay )
diff --git a/README.html b/README.html
index 20814da6..7f0f9e77 100644
--- a/README.html
+++ b/README.html
@@ -11,7 +11,10 @@
- NEW in v1.71: Support for (YouTube) HLS (HTTP
Live Streaming) video with the new “-hls” option. Click on the
-airplay icon in the YouTube app to stream video.
+airplay icon in the YouTube app to stream video. (You may need to wait
+until advertisements have finished or been skipped before clicking the
+YouTube airplay icon.) Please report any issues with this new
+feature of UxPlay.
Highlights:
@@ -258,7 +261,7 @@ Debian-based systems:
“build-essential” for use in compiling software. You also need
pkg-config: if it is not found by “which pkg-config
”,
install pkg-config or its work-alike replacement pkgconf. Also make sure
-that cmake>=3.5 is installed: “sudo apt install cmake
”
+that cmake>=3.10 is installed: “sudo apt install cmake
”
(add build-essential
and pkg-config
(or
pkgconf
) to this if needed).
Make sure that your distribution provides OpenSSL 1.1.1 or later, and
diff --git a/README.md b/README.md
index 3bc3adcc..8cd5ac06 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
### **Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).**
* _**NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming) video with the new "-hls" option._ Click on the airplay icon in the YouTube app to stream video.
+ (You may need to wait until advertisements have finished or been skipped before clicking the YouTube airplay icon.) **Please report any issues with this new feature of UxPlay**.
## Highlights:
@@ -207,7 +208,7 @@ You need a C/C++ compiler (e.g. g++) with the standard development libraries
installed. Debian-based systems provide a package "build-essential" for use
in compiling software. You also need pkg-config: if it is not found
by "`which pkg-config`", install pkg-config or its work-alike replacement
-pkgconf. Also make sure that cmake>=3.5 is installed:
+pkgconf. Also make sure that cmake>=3.10 is installed:
"`sudo apt install cmake`" (add ``build-essential`` and `pkg-config`
(or ``pkgconf``) to this if needed).
diff --git a/README.txt b/README.txt
index c6a563fa..581d6d0e 100644
--- a/README.txt
+++ b/README.txt
@@ -4,7 +4,10 @@
- ***NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming)
video with the new "-hls" option.* Click on the airplay icon in the
- YouTube app to stream video.
+ YouTube app to stream video. (You may need to wait until
+ advertisements have finished or been skipped before clicking the
+ YouTube airplay icon.) **Please report any issues with this new
+ feature of UxPlay**.
## Highlights:
@@ -257,7 +260,7 @@ libraries installed. Debian-based systems provide a package
"build-essential" for use in compiling software. You also need
pkg-config: if it is not found by "`which pkg-config`", install
pkg-config or its work-alike replacement pkgconf. Also make sure that
-cmake\>=3.5 is installed: "`sudo apt install cmake`" (add
+cmake\>=3.10 is installed: "`sudo apt install cmake`" (add
`build-essential` and `pkg-config` (or `pkgconf`) to this if needed).
Make sure that your distribution provides OpenSSL 1.1.1 or later, and
From ab38339aae6cc36454dbefbbbae94a59cf76dadc Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Thu, 12 Dec 2024 00:46:57 -0500
Subject: [PATCH 13/23] cosmetic fixes
---
renderers/video_renderer.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 53784a8f..4473901b 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -3,7 +3,7 @@
* Copyright (C) 2019 Florian Draschbacher
* Modified for:
* UxPlay - An open-source AirPlay mirroring server
- * Copyright (C) 2021-23 F. Duncanh
+ * Copyright (C) 2021-24 F. Duncanh
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,9 +20,9 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include "video_renderer.h"
#include
#include
+#include "video_renderer.h"
#define SECOND_IN_NSECS 1000000000UL
#ifdef X_DISPLAY_FIX
@@ -203,7 +203,7 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
logger = render_logger;
logger_debug = (logger_get_level(logger) >= LOGGER_DEBUG);
video_terminate = false;
-
+
/* this call to g_set_application_name makes server_name appear in the X11 display window title bar, */
/* (instead of the program name uxplay taken from (argv[0]). It is only set one time. */
From 2ebc2b032f0fadef76296c6432f50d3d2f7fe225 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Thu, 12 Dec 2024 14:57:41 -0500
Subject: [PATCH 14/23] refactor analysis of HLS media playlist
---
lib/airplay_video.c | 31 ++++++++++++++++++++++-------
lib/airplay_video.h | 4 ++--
lib/http_handlers.h | 48 +++++++++++++++++++++------------------------
3 files changed, 48 insertions(+), 35 deletions(-)
diff --git a/lib/airplay_video.c b/lib/airplay_video.c
index 7a739bb9..57490570 100644
--- a/lib/airplay_video.c
+++ b/lib/airplay_video.c
@@ -79,7 +79,7 @@ int airplay_video_service_init(raop_t *raop, unsigned short http_port,
return -2;
}
- printf(" %p %p\n", airplay_video, get_airplay_video(raop));
+ //printf(" %p %p\n", airplay_video, get_airplay_video(raop));
airplay_video->raop = raop;
@@ -247,15 +247,16 @@ char * get_media_playlist_by_num(airplay_video_t *airplay_video, int num) {
return NULL;
}
-char * get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri) {
+int get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri) {
/* Problem: there can be more than one StreamInf playlist with the same uri:
* they differ by choice of partner Media (audio, subtitles) playlists
* If the same uri is requested again, one of the other ones will be returned
* (the least-previously-requested one will be served up)
- */
+ */
+ // modified to return the position of the media playlist in the master playlist
media_item_t *media_data_store = airplay_video->media_data_store;
if (media_data_store == NULL) {
- return NULL;
+ return -2;
}
int found = 0;;
int num = -1;
@@ -276,11 +277,11 @@ char * get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri
}
}
if (found) {
- printf("found %s\n", media_data_store[num].uri);
+ //printf("found %s\n", media_data_store[num].uri);
++media_data_store[num].access;
- return media_data_store[num].playlist;
+ return num;
}
- return NULL;
+ return -1;
}
char * get_media_uri_by_num(airplay_video_t *airplay_video, int num) {
@@ -303,3 +304,19 @@ int get_media_uri_num(airplay_video_t *airplay_video, char * uri) {
}
return -1;
}
+
+int analyze_media_playlist(char *playlist, float *duration) {
+ float next;
+ int count = 0;
+ char *ptr = strstr(playlist, "#EXTINF:");
+ *duration = 0.0f;
+ while (ptr != NULL) {
+ char *end;
+ ptr += strlen("#EXTINF:");
+ next = strtof(ptr, &end);
+ *duration += next;
+ count++;
+ ptr = strstr(end, "#EXTINF:");
+ }
+ return count;
+}
diff --git a/lib/airplay_video.h b/lib/airplay_video.h
index 3a62ba08..81fb5896 100644
--- a/lib/airplay_video.h
+++ b/lib/airplay_video.h
@@ -36,7 +36,7 @@ char *get_uri_local_prefix(airplay_video_t *airplay_video);
int get_next_FCUP_RequestID(airplay_video_t *airplay_video);
void set_next_media_uri_id(airplay_video_t *airplay_video, int id);
int get_next_media_uri_id(airplay_video_t *airplay_video);
-char * get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri);
+int get_media_playlist_by_uri(airplay_video_t *airplay_video, const char *uri);
void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist);
char *get_master_playlist(airplay_video_t *airplay_video);
int get_num_media_uri(airplay_video_t *airplay_video);
@@ -46,7 +46,7 @@ int store_media_data_playlist_by_num(airplay_video_t *airplay_video, char * medi
char *get_media_playlist_by_num(airplay_video_t *airplay_video, int num);
char *get_media_uri_by_num(airplay_video_t *airplay_video, int num);
int get_media_uri_num(airplay_video_t *airplay_video, char * uri);
-
+int analyze_media_playlist(char *playlist, float *duration);
void airplay_video_service_destroy(airplay_video_t *airplay_video);
diff --git a/lib/http_handlers.h b/lib/http_handlers.h
index 59fd78a5..a47f4827 100644
--- a/lib/http_handlers.h
+++ b/lib/http_handlers.h
@@ -105,7 +105,7 @@ http_handler_scrub(raop_conn_t *conn, http_request_t *request, http_response_t *
scrub_position);
}
}
- printf("**********************SCRUB %f ***********************\n",scrub_position);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "**********************SCRUB %f ***********************",scrub_position);
conn->raop->callbacks.on_video_scrub(conn->raop->callbacks.cls, scrub_position);
}
@@ -755,13 +755,11 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t
if (logger_debug) {
logger_log(conn->raop->logger, LOGGER_DEBUG, "FCUP_Response datalen = %d", fcup_response_datalen);
- char *ptr = fcup_response_data;
- printf("begin FCUP Response data:\n");
- for (int i = 0; i < fcup_response_datalen; i++) {
- printf("%c", *ptr);
- ptr++;
- }
- printf("end FCUP Response data\n");
+ char *data = malloc(fcup_response_datalen + 1);
+ memcpy(data, fcup_response_data, fcup_response_datalen);
+ data[fcup_response_datalen] = '\0';
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "begin FCUP Response data:\n%s\nend FCUP Response data",data);
+ free (data);
}
@@ -787,19 +785,12 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t
int uri_num = get_next_media_uri_id(conn->raop->airplay_video);
--uri_num; // (next num is current num + 1)
store_media_data_playlist_by_num(conn->raop->airplay_video, playlist, uri_num);
- float duration = 0.0f, next;
- int count = 0;
- ptr = strstr(fcup_response_data, "#EXTINF:");
- while (ptr != NULL) {
- char *end;
- ptr += strlen("#EXTINF:");
- next = strtof(ptr, &end);
- duration += next;
- count++;
- ptr = strstr(end, "#EXTINF:");
- }
+ float duration = 0.0f;
+ int count = analyze_media_playlist(playlist, &duration);
if (count) {
- printf("\n%s:\nplaylist has %5d chunks, total duration %9.3f secs\n", fcup_response_url, count, duration);
+ logger_log(conn->raop->logger, LOGGER_DEBUG,
+ "\n%s:\nreceived media playlist has %5d chunks, total duration %9.3f secs\n",
+ fcup_response_url, count, duration);
}
}
@@ -976,15 +967,20 @@ http_handler_hls(raop_conn_t *conn, http_request_t *request, http_response_t *r
*response_data = data;
*response_datalen = (int ) len;
} else {
- char * media_playlist = NULL;
- media_playlist = get_media_playlist_by_uri(conn->raop->airplay_video, url);
- if (media_playlist) {
+ int num = get_media_playlist_by_uri(conn->raop->airplay_video, url);
+ if (num < 0) {
+ logger_log(conn->raop->logger, LOGGER_ERR,"Requested playlist %s not found", url);
+ assert(0);
+ } else {
+ char *media_playlist = get_media_playlist_by_num(conn->raop->airplay_video, num);
+ assert(media_playlist);
char *data = adjust_yt_condensed_playlist(media_playlist);
*response_data = data;
*response_datalen = strlen(data);
- } else {
- printf("%s not found\n", url);
- assert(0);
+ float duration = 0.0f;
+ int chunks = analyze_media_playlist(data, &duration);
+ logger_log(conn->raop->logger, LOGGER_INFO,
+ "Requested media_playlist %s has %5d chunks, total duration %9.3f secs", url, chunks, duration);
}
}
From 45b8c0d1c2190142859e0795848b9eedf5c017ce Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Fri, 13 Dec 2024 10:32:50 -0500
Subject: [PATCH 15/23] cleanup printf statements
---
lib/crypto.c | 1 -
lib/httpd.c | 14 +++++++-------
lib/raop.c | 18 ++++++------------
lib/raop_rtp_mirror.c | 10 +---------
uxplay.cpp | 17 +++++++----------
5 files changed, 21 insertions(+), 39 deletions(-)
diff --git a/lib/crypto.c b/lib/crypto.c
index 906a2fe0..d530d975 100644
--- a/lib/crypto.c
+++ b/lib/crypto.c
@@ -343,7 +343,6 @@ int gcm_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *pl
return plaintext_len;
} else {
/* Verify failed */
- printf("failed\n");
return -1;
}
}
diff --git a/lib/httpd.c b/lib/httpd.c
index 1799e346..61479282 100644
--- a/lib/httpd.c
+++ b/lib/httpd.c
@@ -413,22 +413,22 @@ httpd_thread(void *arg)
logger_log(httpd->logger, LOGGER_DEBUG, "httpd receiving on socket %d, connection %d",
connection->socket_fd, i);
if (logger_debug) {
- printf("\nhttpd: current connections:\n");
+ logger_log(httpd->logger, LOGGER_DEBUG,"\nhttpd: current connections:");
for (int i = 0; i < httpd->max_connections; i++) {
http_connection_t *connection = &httpd->connections[i];
if(!connection->connected) {
continue;
}
if (!FD_ISSET(connection->socket_fd, &rfds)) {
- printf("connection %d type %d socket %d conn %p %s\n", i,
- connection->type, connection->socket_fd,
- connection->user_data, typename [connection->type]);
+ logger_log(httpd->logger, LOGGER_DEBUG, "connection %d type %d socket %d conn %p %s", i,
+ connection->type, connection->socket_fd,
+ connection->user_data, typename [connection->type]);
} else {
- printf("connection %d type %d socket %d conn %p %s ACTIVE CONNECTION\n", i, connection->type,
- connection->socket_fd, connection->user_data, typename [connection->type]);
+ logger_log(httpd->logger, LOGGER_DEBUG, "connection %d type %d socket %d conn %p %s ACTIVE CONNECTION",
+ i, connection->type, connection->socket_fd, connection->user_data, typename [connection->type]);
}
}
- printf("\n");
+ logger_log(httpd->logger, LOGGER_DEBUG, " ");
}
/* reverse-http responses from the client must not be sent to the llhttp parser:
* such messages start with "HTTP/1.1" */
diff --git a/lib/raop.c b/lib/raop.c
index 6bd3e0e9..c275dc1d 100644
--- a/lib/raop.c
+++ b/lib/raop.c
@@ -311,19 +311,16 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
char * plist_xml;
uint32_t plist_len;
plist_to_xml(req_root_node, &plist_xml, &plist_len);
- printf("%s\n",plist_xml);
- //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
plist_free(req_root_node);
} else if (data_is_text) {
char *data_str = utils_data_to_text((char *) request_data, request_datalen);
- printf("%s\n", data_str);
- //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
} else {
char *data_str = utils_data_to_string((unsigned char *) request_data, request_datalen, 16);
- printf("%s\n", data_str);
- //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
}
}
@@ -449,18 +446,15 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
uint32_t plist_len;
plist_to_xml(res_root_node, &plist_xml, &plist_len);
plist_free(res_root_node);
- printf("%s\n", plist_xml);
- //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", plist_xml);
free(plist_xml);
} else if (data_is_text) {
char *data_str = utils_data_to_text((char*) response_data, response_datalen);
- printf("%s\n", data_str);
- //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
} else {
char *data_str = utils_data_to_string((unsigned char *) response_data, response_datalen, 16);
- printf("%s\n", data_str);
- //logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
+ logger_log(conn->raop->logger, LOGGER_DEBUG, "%s", data_str);
free(data_str);
}
}
diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c
index e97f66f0..bc88472e 100644
--- a/lib/raop_rtp_mirror.c
+++ b/lib/raop_rtp_mirror.c
@@ -541,9 +541,6 @@ raop_rtp_mirror_thread(void *arg)
raop_rtp_mirror->callbacks.video_process(raop_rtp_mirror->callbacks.cls, raop_rtp_mirror->ntp, &video_data);
free(payload_out);
break;
- //char *str3 = utils_data_to_string(payload_out, video_data.data_len, 16);
- //printf("%s\n", str3);
- //free (str3);
case 0x01:
/* 128-byte observed packet header structure
bytes 0-15: length + timestamp
@@ -609,13 +606,12 @@ raop_rtp_mirror_thread(void *arg)
free(sps_pps);
sps_pps = NULL;
}
- /* test for a H265 VPS/SPs/PPS */
+ /* test for a H265 VPS/SPS/PPS */
unsigned char hvc1[] = { 0x68, 0x76, 0x63, 0x31 };
if (!memcmp(payload + 4, hvc1, 4)) {
/* hvc1 HECV detected */
codec = VIDEO_CODEC_H265;
- printf("h265 detected\n");
h265_video = true;
raop_rtp_mirror->callbacks.video_set_codec(raop_rtp_mirror->callbacks.cls, codec);
unsigned char vps_start_code[] = { 0xa0, 0x00, 0x01, 0x00 };
@@ -687,10 +683,6 @@ raop_rtp_mirror_thread(void *arg)
memcpy(ptr, nal_start_code, 4);
ptr += 4;
memcpy(ptr, pps, pps_size);
- // printf (" HEVC (hvc1) vps + sps + pps NALU\n");
- //char *str = utils_data_to_string(sps_pps, sps_pps_len, 16);
- //printf("%s\n", str);
- //free (str);
} else {
codec = VIDEO_CODEC_H264;
h265_video = false;
diff --git a/uxplay.cpp b/uxplay.cpp
index d6a2d1ee..3246ffdb 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -1575,7 +1575,6 @@ extern "C" void conn_reset (void *cls, int timeouts, bool reset_video) {
LOGI(" Sometimes the network connection may recover after a longer delay:\n"
" the default timeout limit n = %d can be changed with the \"-reset n\" option", NTP_TIMEOUT_LIMIT);
}
- printf("reset_video %d\n",(int) reset_video);
if (!nofreeze) {
close_window = reset_video; /* leave "frozen" window open if reset_video is false */
}
@@ -1880,9 +1879,7 @@ extern "C" void on_video_acquire_playback_info (void *cls, playback_info_t *play
LOGI(" video has finished, %f", playback_info->position);
playback_info->position = -1.0;
playback_info->duration = -1.0;
- printf("about to stop\n");
video_renderer_stop();
- printf("stopped\n");
}
}
@@ -2083,8 +2080,8 @@ static void read_config_file(const char * filename, const char * uxplay_name) {
void real_main (int argc, char *argv[]);
int main (int argc, char *argv[]) {
- printf("*=== Using gst_macos_main wrapper for GStreamer >= 1.22 on macOS ===*\n");
- return gst_macos_main ((GstMainFunc) real_main, argc, argv , NULL);
+ LOGI("*=== Using gst_macos_main wrapper for GStreamer >= 1.22 on macOS ===*");
+ return gst_macos_main ((GstMainFunc) real_main, argc, argv , NULL);
}
void real_main (int argc, char *argv[]) {
@@ -2124,22 +2121,22 @@ int main (int argc, char *argv[]) {
}
if (dump_video) {
if (video_dump_limit > 0) {
- printf("dump video using \"-vdmp %d %s\"\n", video_dump_limit, video_dumpfile_name.c_str());
+ LOGI("dump video using \"-vdmp %d %s\"", video_dump_limit, video_dumpfile_name.c_str());
} else {
- printf("dump video using \"-vdmp %s\"\n", video_dumpfile_name.c_str());
+ LOGI("dump video using \"-vdmp %s\"", video_dumpfile_name.c_str());
}
}
if (dump_audio) {
if (audio_dump_limit > 0) {
- printf("dump audio using \"-admp %d %s\"\n", audio_dump_limit, audio_dumpfile_name.c_str());
+ LOGI("dump audio using \"-admp %d %s\"", audio_dump_limit, audio_dumpfile_name.c_str());
} else {
- printf("dump audio using \"-admp %s\"\n", audio_dumpfile_name.c_str());
+ LOGI("dump audio using \"-admp %s\"", audio_dumpfile_name.c_str());
}
}
#if __APPLE__
/* force use of -nc option on macOS */
- LOGI("macOS detected: use -nc option as workaround for GStreamer problem");
+ LOGI("macOS detected: using -nc option as workaround for GStreamer problem");
new_window_closing_behavior = false;
#endif
From 39e0d8aeadc7819dd2ec754495b00414bafc5395 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Fri, 13 Dec 2024 10:52:38 -0500
Subject: [PATCH 16/23] readme update for 1.71 release
The addition of HLS support was made possible with invaluable assistance from
@thiccaxe @GenghisKhanDrip @shuax and others
---
README.html | 52 +-
README.md | 3217 +++++++++++++++++++++++++++++----------------------
README.txt | 6 +-
3 files changed, 1863 insertions(+), 1412 deletions(-)
diff --git a/README.html b/README.html
index 7f0f9e77..63f0d85d 100644
--- a/README.html
+++ b/README.html
@@ -4,10 +4,9 @@
(now also runs on Windows).
Now
-developed at the GitHub site https://github.com/FDH2/UxPlay
-(where ALL user issues should be posted, and latest versions can be
-found).
+developed at the GitHub site https://github.com/FDH2/UxPlay (where ALL user issues
+should be posted, and latest versions can be found).
- NEW in v1.71: Support for (YouTube) HLS (HTTP
Live Streaming) video with the new “-hls” option. Click on the
@@ -669,6 +668,7 @@
Starting and running UxPlay
- Tip: to start UxPlay on a remote host (such as a Raspberry Pi) using
ssh:
+
ssh user@remote_host
export DISPLAY=:0
nohup uxplay [options] > FILE &
@@ -691,9 +691,9 @@ Starting and running UxPlay
href="http://www.macports.org">MacPorts
(sudo port install cmake
), Homebrew (brew install cmake
), or
-by a download from https://cmake.org/download/. Also
-install git
if you will use it to fetch UxPlay.
+by a download from https://cmake.org/download/. Also install
+git
if you will use it to fetch UxPlay.
Next install libplist and openssl-3.x. Note that static versions of
these libraries will be used in the macOS builds, so they can be
uninstalled after building uxplay, if you wish.
@@ -709,11 +709,11 @@ Starting and running UxPlay
Next get the latest macOS release of GStreamer-1.0.
Using “Official” GStreamer (Recommended for both MacPorts and
Homebrew users): install the GStreamer release for macOS from
-https://gstreamer.freedesktop.org/download/.
-(This release contains its own pkg-config, so you don’t have to install
-one.) Install both the gstreamer-1.0 and gstreamer-1.0-devel packages.
-After downloading, Shift-Click on them to install (they install to
+https://gstreamer.freedesktop.org/download/. (This
+release contains its own pkg-config, so you don’t have to install one.)
+Install both the gstreamer-1.0 and gstreamer-1.0-devel packages. After
+downloading, Shift-Click on them to install (they install to
/Library/FrameWorks/GStreamer.framework). Homebrew or MacPorts users
should not install (or should uninstall) the GStreamer
supplied by their package manager, if they use the “official”
@@ -1350,8 +1350,7 @@
1. Avahi/DNS_SD
+ lo IPv4 UxPlay AirPlay Remote Video local
+ eno1 IPv6 863EA27598FE@UxPlay AirTunes Remote Audio local
+ eno1 IPv4 863EA27598FE@UxPlay AirTunes Remote Audio local
-+ lo IPv4 863EA27598FE@UxPlay AirTunes Remote Audio local
-
++ lo IPv4 863EA27598FE@UxPlay AirTunes Remote Audio local
If only the loopback (“lo”) entries are shown, a firewall on the
UxPlay host is probably blocking full DNS-SD service, and you need to
open the default UDP port 5353 for mDNS requests, as loopback-based
@@ -1582,8 +1581,8 @@
5. Mirror screen
introduced 2017, running tvOS 12.2.1), so it does not seem to matter
what version UxPlay claims to be.
Changelog
-
1.71 2024-12-10 Add support for HTTP Live Streaming (HLS), initially
-only for YouTube movies
+1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
+only for YouTube movies. Fix issue with NTP timeout on Windows.
1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
2160). Fix issue with GStreamer >= 1.24 when client sleeps, then
wakes.
@@ -1698,7 +1697,7 @@ Changelog
systems. Also modified timestamps from “DTS” to “PTS” for latency
improvement, plus internal cleanups.
1.49 2022-03-28 Addded options for dumping video and/or audio to
-file, for debugging, etc. h264 PPS/SPS NALU’s are shown with -d. Fixed
+file, for debugging, etc. h264 PPS/SPS NALU’s are shown with -d. Fixed
video-not-working for M1 Mac clients.
1.48 2022-03-11 Made the GStreamer video pipeline fully configurable,
for use with hardware h264 decoding. Support for Raspberry Pi.
@@ -1762,13 +1761,13 @@ Building OpenSSL >=
If you need to do this, note that you may be able to use a newer
version (OpenSSL-3.0.1 is known to work). You will need the standard
development toolset (autoconf, automake, libtool). Download the source
-code from https://www.openssl.org/source/.
-Install the downloaded openssl by opening a terminal in your Downloads
-directory, and unpacking the source distribution: (“tar -xvzf
-openssl-3.0.1.tar.gz ; cd openssl-3.0.1”). Then build/install with
-“./config ; make ; sudo make install_dev”. This will typically install
-the needed library libcrypto.*
, either in /usr/local/lib or
+code from https://www.openssl.org/source/. Install the downloaded
+openssl by opening a terminal in your Downloads directory, and unpacking
+the source distribution: (“tar -xvzf openssl-3.0.1.tar.gz ; cd
+openssl-3.0.1”). Then build/install with “./config ; make ; sudo make
+install_dev”. This will typically install the needed library
+libcrypto.*
, either in /usr/local/lib or
/usr/local/lib64.
(Ignore the following for builds on MacOS:) On some systems
like Debian or Ubuntu, you may also need to add a missing entry
@@ -1782,8 +1781,9 @@
Building libplist >=
10 or Ubuntu 18.04.) As well as the usual build tools (autoconf,
automake, libtool), you may need to also install some libpython*-dev
package. Download the latest source with git from https://github.com/libimobiledevice/libplist,
-or get the source from the Releases section (use the *.tar.bz2 release,
+href="https://github.com/libimobiledevice/libplist"
+class="uri">https://github.com/libimobiledevice/libplist, or get the
+source from the Releases section (use the *.tar.bz2 release,
not the *.zip or *.tar.gz versions): download libplist-2.3.0,
then unpack it (“tar -xvjf libplist-2.3.0.tar.bz2 ; cd libplist-2.3.0”),
diff --git a/README.md b/README.md
index 8cd5ac06..74181487 100644
--- a/README.md
+++ b/README.md
@@ -1,1492 +1,1943 @@
-# UxPlay 1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
+# UxPlay 1.71: AirPlay-Mirror and AirPlay-Audio server for Linux, macOS, and Unix (now also runs on Windows).
-### **Now developed at the GitHub site [https://github.com/FDH2/UxPlay](https://github.com/FDH2/UxPlay) (where ALL user issues should be posted, and latest versions can be found).**
+### **Now developed at the GitHub site (where ALL user issues should be posted, and latest versions can be found).**
+
+- ***NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming)
+ video with the new "-hls" option.* Click on the airplay icon in the
+ YouTube app to stream video. (You may need to wait until
+ advertisements have finished or been skipped before clicking the
+ YouTube airplay icon.) **Please report any issues with this new
+ feature of UxPlay**.
- * _**NEW in v1.71**: Support for (YouTube) HLS (HTTP Live Streaming) video with the new "-hls" option._ Click on the airplay icon in the YouTube app to stream video.
- (You may need to wait until advertisements have finished or been skipped before clicking the YouTube airplay icon.) **Please report any issues with this new feature of UxPlay**.
-
## Highlights:
- * GPLv3, open source.
- * Originally supported only AirPlay Mirror protocol, now has added support
- for AirPlay Audio-only (Apple Lossless ALAC) streaming
- from current iOS/iPadOS clients. **Now with support for Airplay HLS
- video-streaming (currently only YouTube video).**
- * macOS computers (2011 or later, both Intel and "Apple Silicon" M1/M2
- systems) can act either as AirPlay clients, or
- as the server running UxPlay. Using AirPlay, UxPlay can
- emulate a second display for macOS clients.
- * Support for older iOS clients (such as 32-bit iPad 2nd gen., iPod Touch 5th gen. and
- iPhone 4S, when upgraded to iOS 9.3.5, or later 64-bit devices), plus a
- Windows AirPlay-client emulator, AirMyPC.
- * Uses GStreamer plugins for audio and video rendering (with options
- to select different hardware-appropriate output "videosinks" and
- "audiosinks", and a fully-user-configurable video streaming pipeline).
- * Support for server behind a firewall.
- * Raspberry Pi support **both with and without hardware video decoding** by the
- Broadcom GPU. _Tested on Raspberry Pi Zero 2 W, 3 Model B+, 4 Model B, and 5._
- * Support for running on Microsoft Windows (builds with the MinGW-64 compiler in the
- unix-like MSYS2 environment).
-
-Note: AirPlay2 multi-room audio streaming is not supported: use [shairport-sync](https://github.com/mikebrady/shairport-sync) for that.
+- GPLv3, open source.
+- Originally supported only AirPlay Mirror protocol, now has added
+ support for AirPlay Audio-only (Apple Lossless ALAC) streaming from
+ current iOS/iPadOS clients. **Now with support for Airplay HLS
+ video-streaming (currently only YouTube video).**
+- macOS computers (2011 or later, both Intel and "Apple Silicon" M1/M2
+ systems) can act either as AirPlay clients, or as the server running
+ UxPlay. Using AirPlay, UxPlay can emulate a second display for macOS
+ clients.
+- Support for older iOS clients (such as 32-bit iPad 2nd gen., iPod
+ Touch 5th gen. and iPhone 4S, when upgraded to iOS 9.3.5, or later
+ 64-bit devices), plus a Windows AirPlay-client emulator, AirMyPC.
+- Uses GStreamer plugins for audio and video rendering (with options
+ to select different hardware-appropriate output "videosinks" and
+ "audiosinks", and a fully-user-configurable video streaming
+ pipeline).
+- Support for server behind a firewall.
+- Raspberry Pi support **both with and without hardware video
+ decoding** by the Broadcom GPU. *Tested on Raspberry Pi Zero 2 W, 3
+ Model B+, 4 Model B, and 5.*
+- Support for running on Microsoft Windows (builds with the MinGW-64
+ compiler in the unix-like MSYS2 environment).
+
+Note: AirPlay2 multi-room audio streaming is not supported: use
+[shairport-sync](https://github.com/mikebrady/shairport-sync) for that.
## Packaging status (Linux and \*BSD distributions)
-[](https://repology.org/project/uxplay/versions).
+[](https://repology.org/project/uxplay/versions).
-* Install uxplay on Debian-based Linux systems with "`sudo apt install uxplay`"; on FreeBSD
-with "``sudo pkg install uxplay``". Also available on Arch-based systems through AUR. Since v. 1.66,
-uxplay is now also packaged in RPM format by Fedora 38 ("``sudo dnf install uxplay``").
+- Install uxplay on Debian-based Linux systems with
+ "`sudo apt install uxplay`"; on FreeBSD with
+ "`sudo pkg install uxplay`". Also available on Arch-based systems
+ through AUR. Since v. 1.66, uxplay is now also packaged in RPM
+ format by Fedora 38 ("`sudo dnf install uxplay`").
-* For other RPM-based distributions which have not yet packaged UxPlay, a RPM "specfile" **uxplay.spec** is now provided with recent
-[releases](https://github.com/FDH2/UxPlay/releases) (see their "Assets"), and can also be found in the UxPlay source top directory.
-See the section on using this specfile for [building an installable RPM package](#building-an-installable-rpm-package).
+- For other RPM-based distributions which have not yet packaged
+ UxPlay, a RPM "specfile" **uxplay.spec** is now provided with recent
+ [releases](https://github.com/FDH2/UxPlay/releases) (see their
+ "Assets"), and can also be found in the UxPlay source top directory.
+ See the section on using this specfile for [building an installable
+ RPM package](#building-an-installable-rpm-package).
After installation:
-* (On Linux and \*BSD): if a firewall is active on the server hosting UxPlay,
-make sure the default network port (UDP 5353) for mDNS/DNS-SD queries is open (see
-[Troubleshooting](#troubleshooting) below for more details); also open three UDP and three TCP ports for
-Uxplay, and use the "uxplay -p " option (see "`man uxplay`" or "``uxplay -h``").
-
-* Even if you install your distribution's pre-compiled uxplay binary package, you may need to read the instructions below
-for [running UxPlay](#running-uxplay) to see which of your distribution's **GStreamer plugin packages** you should also install.
-
-* For Audio-only mode (Apple Music, etc.) best quality is obtained with the option "uxplay -async", but there is then
-a 2 second latency imposed by iOS.
-
-* Add any UxPlay options you want to use as defaults to a startup file `~/.uxplayrc`
-(see "`man uxplay`" or "``uxplay -h``" for format and other possible locations). In particular, if your system uses PipeWire audio or
-Wayland video systems, you may wish to add "as pipewiresink" or "vs waylandsink" as defaults to the file. _(Output from terminal commands "ps waux | grep pulse" or "pactl info" will contain "pipewire" if your Linux/BSD system uses it)._
-
-
-* On Raspberry Pi: If you use Ubuntu 22.10 or earlier, GStreamer must
-be [patched](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches) to use hardware video decoding by the Broadcom GPU
-(also recommended but optional
-for Raspberry Pi OS (Bullseye): use option "`uxplay -bt709`" if you do not use the patch).
-
-To (easily) compile the latest UxPlay from source, see the section [Getting UxPlay](#getting-uxplay).
+- (On Linux and \*BSD): if a firewall is active on the server hosting
+ UxPlay, make sure the default network port (UDP 5353) for
+ mDNS/DNS-SD queries is open (see [Troubleshooting](#troubleshooting)
+ below for more details); also open three UDP and three TCP ports for
+ Uxplay, and use the "uxplay -p ``{=html}" option (see
+ "`man uxplay`" or "`uxplay -h`").
+
+- Even if you install your distribution's pre-compiled uxplay binary
+ package, you may need to read the instructions below for [running
+ UxPlay](#running-uxplay) to see which of your distribution's
+ **GStreamer plugin packages** you should also install.
+
+- For Audio-only mode (Apple Music, etc.) best quality is obtained
+ with the option "uxplay -async", but there is then a 2 second
+ latency imposed by iOS.
+
+- Add any UxPlay options you want to use as defaults to a startup file
+ `~/.uxplayrc` (see "`man uxplay`" or "`uxplay -h`" for format and
+ other possible locations). In particular, if your system uses
+ PipeWire audio or Wayland video systems, you may wish to add "as
+ pipewiresink" or "vs waylandsink" as defaults to the file. *(Output
+ from terminal commands "ps waux \| grep pulse" or "pactl info" will
+ contain "pipewire" if your Linux/BSD system uses it).*
+
+- On Raspberry Pi: If you use Ubuntu 22.10 or earlier, GStreamer must
+ be
+ [patched](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches)
+ to use hardware video decoding by the Broadcom GPU (also recommended
+ but optional for Raspberry Pi OS (Bullseye): use option
+ "`uxplay -bt709`" if you do not use the patch).
+
+To (easily) compile the latest UxPlay from source, see the section
+[Getting UxPlay](#getting-uxplay).
# Detailed description of UxPlay
-This project is a GPLv3 open source unix AirPlay2 Mirror server for Linux, macOS, and \*BSD.
-It was initially developed by
-[antimof](http://github.com/antimof/Uxplay) using code
-from OpenMAX-based [RPiPlay](https://github.com/FD-/RPiPlay), which in turn derives from
-[AirplayServer](https://github.com/KqsMea8/AirplayServer),
-[shairplay](https://github.com/juhovh/shairplay), and [playfair](https://github.com/EstebanKubata/playfair).
-(The antimof site is no longer involved in
-development, but periodically posts updates pulled from the new
-main [UxPlay site](https://github.com/FDH2/UxPlay)).
-
-UxPlay is tested on a number of systems, including (among others) Debian (10 "Buster", 11 "Bullseye", 12 "Bookworm"),
-Ubuntu (20.04 LTS, 22.04 LTS, 23.04 (also Ubuntu derivatives Linux Mint, Pop!\_OS), Red Hat and clones (Fedora 38,
-Rocky Linux 9.2), openSUSE Leap 15.5, Mageia 9, OpenMandriva "ROME", PCLinuxOS, Arch Linux, Manjaro, and should run on any Linux system.
-Also tested on macOS Catalina and Ventura (Intel) and Sonoma (M2), FreeBSD 14.0, Windows 10 and 11 (64 bit).
-
-On Raspberry Pi 4 model B, it is tested on Raspberry Pi OS (Bullseye and Bookworm) (32- and 64-bit),
-Ubuntu 22.04 LTS and 23.04, Manjaro RPi4 23.02, and (without hardware video decoding) on openSUSE 15.5.
-Also tested on Raspberry Pi Zero 2 W, 3 model B+, and now 5.
-
-Its main use is to act like an AppleTV for screen-mirroring (with audio) of iOS/iPadOS/macOS clients
-(iPhone, iPod Touch, iPad, Mac computers) on the server display
-of a host running Linux, macOS, or other unix (and now also Microsoft Windows). UxPlay supports
-Apple's AirPlay2 protocol using "Legacy Protocol", but some features are missing.
-(Details of what is publicly known about Apple's AirPlay 2 protocol can be found
+
+This project is a GPLv3 open source unix AirPlay2 Mirror server for
+Linux, macOS, and \*BSD. It was initially developed by
+[antimof](http://github.com/antimof/Uxplay) using code from
+OpenMAX-based [RPiPlay](https://github.com/FD-/RPiPlay), which in turn
+derives from [AirplayServer](https://github.com/KqsMea8/AirplayServer),
+[shairplay](https://github.com/juhovh/shairplay), and
+[playfair](https://github.com/EstebanKubata/playfair). (The antimof site
+is no longer involved in development, but periodically posts updates
+pulled from the new main [UxPlay site](https://github.com/FDH2/UxPlay)).
+
+UxPlay is tested on a number of systems, including (among others) Debian
+(10 "Buster", 11 "Bullseye", 12 "Bookworm"), Ubuntu (20.04 LTS, 22.04
+LTS, 23.04 (also Ubuntu derivatives Linux Mint, Pop!\_OS), Red Hat and
+clones (Fedora 38, Rocky Linux 9.2), openSUSE Leap 15.5, Mageia 9,
+OpenMandriva "ROME", PCLinuxOS, Arch Linux, Manjaro, and should run on
+any Linux system. Also tested on macOS Catalina and Ventura (Intel) and
+Sonoma (M2), FreeBSD 14.0, Windows 10 and 11 (64 bit).
+
+On Raspberry Pi 4 model B, it is tested on Raspberry Pi OS (Bullseye and
+Bookworm) (32- and 64-bit), Ubuntu 22.04 LTS and 23.04, Manjaro RPi4
+23.02, and (without hardware video decoding) on openSUSE 15.5. Also
+tested on Raspberry Pi Zero 2 W, 3 model B+, and now 5.
+
+Its main use is to act like an AppleTV for screen-mirroring (with audio)
+of iOS/iPadOS/macOS clients (iPhone, iPod Touch, iPad, Mac computers) on
+the server display of a host running Linux, macOS, or other unix (and
+now also Microsoft Windows). UxPlay supports Apple's AirPlay2 protocol
+using "Legacy Protocol", but some features are missing. (Details of what
+is publicly known about Apple's AirPlay 2 protocol can be found
[here](https://openairplay.github.io/airplay-spec/),
-[here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol) and
-[here](https://emanuelecozzi.net/docs/airplay2); see also [pyatv](https://pyatv.dev/documentation/protocols) which could be
-a resource for adding modern protocols.) While there is no guarantee that future
-iOS releases will keep supporting "Legacy Protocol", iOS 17 continues support.
+[here](https://github.com/SteeBono/airplayreceiver/wiki/AirPlay2-Protocol)
+and [here](https://emanuelecozzi.net/docs/airplay2); see also
+[pyatv](https://pyatv.dev/documentation/protocols) which could be a
+resource for adding modern protocols.) While there is no guarantee that
+future iOS releases will keep supporting "Legacy Protocol", iOS 17
+continues support.
The UxPlay server and its client must be on the same local area network,
-on which a **Bonjour/Zeroconf mDNS/DNS-SD server** is also running
-(only DNS-SD "Service Discovery" service is strictly necessary, it is not necessary
-that the local network also be of the ".local" mDNS-based type).
-On Linux and BSD Unix servers, this is usually provided by [Avahi](https://www.avahi.org),
-through the avahi-daemon service, and is included in most Linux distributions (this
-service can also be provided by macOS, iOS or Windows servers).
-
-Connections to the UxPlay server by
-iOS/MacOS clients can be initiated both in **AirPlay Mirror** mode (which streams
-lossily-compressed AAC audio while mirroring the client screen,
-or in the alternative **AirPlay Audio** mode which streams
-Apple Lossless (ALAC) audio without screen mirroring. In **Audio** mode,
-metadata is displayed in the uxplay terminal;
-if UxPlay option ``-ca `` is used,
-the accompanying cover art is also output
-to a periodically-updated file ``, and can be viewed with
-a (reloading) graphics viewer of your choice.
-_Switching between_ **Mirror** _and_ **Audio** _modes during an active connection is
-possible: in_ **Mirror** _mode, stop mirroring (or close the mirror window) and start an_ **Audio** _mode connection,
-switch back by initiating a_ **Mirror** _mode connection; cover-art display stops/restarts as you leave/re-enter_ **Audio** _mode._
-
-* **Note that Apple video-DRM
-(as found in "Apple TV app" content on the client) cannot be decrypted by UxPlay, and
-the Apple TV app cannot be watched using UxPlay's AirPlay Mirror mode (only the unprotected audio will be streamed, in AAC format).**
-
-* **With the new "-hls" option, UxPlay now also supports non-Mirror AirPlay video streaming (where the
-client controls a web server on the AirPlay server that directly receives
-HLS content to avoid it being decoded and re-encoded by the client). This currently only supports streaming of YouTube videos.
-Without the -hls option, using the icon for AirPlay video in apps such as the YouTube app
-will only send audio (in lossless ALAC format) without the accompanying
-video.**
+on which a **Bonjour/Zeroconf mDNS/DNS-SD server** is also running (only
+DNS-SD "Service Discovery" service is strictly necessary, it is not
+necessary that the local network also be of the ".local" mDNS-based
+type). On Linux and BSD Unix servers, this is usually provided by
+[Avahi](https://www.avahi.org), through the avahi-daemon service, and is
+included in most Linux distributions (this service can also be provided
+by macOS, iOS or Windows servers).
+
+Connections to the UxPlay server by iOS/MacOS clients can be initiated
+both in **AirPlay Mirror** mode (which streams lossily-compressed AAC
+audio while mirroring the client screen, or in the alternative **AirPlay
+Audio** mode which streams Apple Lossless (ALAC) audio without screen
+mirroring. In **Audio** mode, metadata is displayed in the uxplay
+terminal; if UxPlay option `-ca ` is used, the accompanying cover
+art is also output to a periodically-updated file ``, and can be
+viewed with a (reloading) graphics viewer of your choice. *Switching
+between* **Mirror** *and* **Audio** *modes during an active connection
+is possible: in* **Mirror** *mode, stop mirroring (or close the mirror
+window) and start an* **Audio** *mode connection, switch back by
+initiating a* **Mirror** *mode connection; cover-art display
+stops/restarts as you leave/re-enter* **Audio** *mode.*
+
+- **Note that Apple video-DRM (as found in "Apple TV app" content on
+ the client) cannot be decrypted by UxPlay, and the Apple TV app
+ cannot be watched using UxPlay's AirPlay Mirror mode (only the
+ unprotected audio will be streamed, in AAC format).**
+
+- **With the new "-hls" option, UxPlay now also supports non-Mirror
+ AirPlay video streaming (where the client controls a web server on
+ the AirPlay server that directly receives HLS content to avoid it
+ being decoded and re-encoded by the client). This currently only
+ supports streaming of YouTube videos. Without the -hls option, using
+ the icon for AirPlay video in apps such as the YouTube app will only
+ send audio (in lossless ALAC format) without the accompanying
+ video.**
### Possibility for using hardware-accelerated h264/h265 video-decoding, if available.
-UxPlay uses [GStreamer](https://gstreamer.freedesktop.org) "plugins" for rendering
-audio and video. This means that video and audio are supported "out of the box",
-using a choice of plugins. AirPlay streams video in h264 format: gstreamer decoding
-is plugin agnostic, and uses accelerated GPU hardware h264 decoders if available;
-if not, software decoding is used.
-
-* **VAAPI for Intel and AMD integrated graphics, NVIDIA with "Nouveau" open-source driver**
-
- With an Intel or AMD GPU, hardware decoding with the open-source VAAPI gstreamer
- plugin is preferable. The open-source "Nouveau" drivers for NVIDIA graphics are
- also in principle supported:
- see [here](https://nouveau.freedesktop.org/VideoAcceleration.html), but this requires
- VAAPI to be supplemented with firmware extracted from the proprietary NVIDIA drivers.
-
-* **NVIDIA with proprietary drivers**
-
- The `nvh264dec` plugin
- (included in gstreamer1.0-plugins-bad since GStreamer-1.18.0)
- can be used for accelerated video decoding on the NVIDIA GPU after
- NVIDIA's CUDA driver `libcuda.so` is installed. For GStreamer-1.16.3
- or earlier, the plugin is called `nvdec`, and
- must be [built by the user](https://github.com/FDH2/UxPlay/wiki/NVIDIA-nvdec-and-nvenc-plugins).
-
-* **Video4Linux2 support for h264 hardware decoding on Raspberry Pi (Pi 4B and older)**
-
- Raspberry Pi (RPi) computers (tested on Pi 4 Model B) can now run UxPlay using software video decoding,
- but hardware-accelerated h264/h265 decoding by firmware in the Pi's Broadcom 2835
- GPU is prefered. UxPlay accesses this using the GStreamer-1.22 Video4Linux2 (v4l2) plugin;
- Uses the out-of-mainline Linux kernel module bcm2835-codec maintained by Raspberry Pi,
- so far only included in Raspberry Pi OS, and two other distributions (Ubuntu, Manjaro) available
- with Raspberry Pi Imager. _(For GStreamer < 1.22, see
- the [UxPlay Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches))_.
- Pi model 5 has no support for hardware H264 decoding,
- as its CPU is powerful enough for satisfactory software H264 decoding
-
-* **Support for h265 (HEVC) hardware decoding on Raspberry Pi (Pi 4 model B and Pi 5)**
-
- These Raspberry Pi models have a dedicated HEVC decoding block (not the GPU), with a driver
- "rpivid" which is not yet in the mainline Linux kernel (but is planned to be there in future). Unfortunately
- it produces decoded video in a non-standard pixel format (NC30 or "SAND") which will not be supported
- by GStreamer until the driver is in the mainline kernel; without this support, UxPlay support for HEVC
- hardware decoding on Raspberry Pi will not work.
-
+UxPlay uses [GStreamer](https://gstreamer.freedesktop.org) "plugins" for
+rendering audio and video. This means that video and audio are supported
+"out of the box", using a choice of plugins. AirPlay streams video in
+h264 format: gstreamer decoding is plugin agnostic, and uses accelerated
+GPU hardware h264 decoders if available; if not, software decoding is
+used.
+
+- **VAAPI for Intel and AMD integrated graphics, NVIDIA with "Nouveau"
+ open-source driver**
+
+ With an Intel or AMD GPU, hardware decoding with the open-source
+ VAAPI gstreamer plugin is preferable. The open-source "Nouveau"
+ drivers for NVIDIA graphics are also in principle supported: see
+ [here](https://nouveau.freedesktop.org/VideoAcceleration.html), but
+ this requires VAAPI to be supplemented with firmware extracted from
+ the proprietary NVIDIA drivers.
+
+- **NVIDIA with proprietary drivers**
+
+ The `nvh264dec` plugin (included in gstreamer1.0-plugins-bad since
+ GStreamer-1.18.0) can be used for accelerated video decoding on the
+ NVIDIA GPU after NVIDIA's CUDA driver `libcuda.so` is installed. For
+ GStreamer-1.16.3 or earlier, the plugin is called `nvdec`, and must
+ be [built by the
+ user](https://github.com/FDH2/UxPlay/wiki/NVIDIA-nvdec-and-nvenc-plugins).
+
+- **Video4Linux2 support for h264 hardware decoding on Raspberry Pi
+ (Pi 4B and older)**
+
+ Raspberry Pi (RPi) computers (tested on Pi 4 Model B) can now run
+ UxPlay using software video decoding, but hardware-accelerated
+ h264/h265 decoding by firmware in the Pi's Broadcom 2835 GPU is
+ prefered. UxPlay accesses this using the GStreamer-1.22 Video4Linux2
+ (v4l2) plugin; Uses the out-of-mainline Linux kernel module
+ bcm2835-codec maintained by Raspberry Pi, so far only included in
+ Raspberry Pi OS, and two other distributions (Ubuntu, Manjaro)
+ available with Raspberry Pi Imager. *(For GStreamer \< 1.22, see the
+ [UxPlay
+ Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches))*.
+ Pi model 5 has no support for hardware H264 decoding, as its CPU is
+ powerful enough for satisfactory software H264 decoding
+
+- **Support for h265 (HEVC) hardware decoding on Raspberry Pi (Pi 4
+ model B and Pi 5)**
+
+ These Raspberry Pi models have a dedicated HEVC decoding block (not
+ the GPU), with a driver "rpivid" which is not yet in the mainline
+ Linux kernel (but is planned to be there in future). Unfortunately
+ it produces decoded video in a non-standard pixel format (NC30 or
+ "SAND") which will not be supported by GStreamer until the driver is
+ in the mainline kernel; without this support, UxPlay support for
+ HEVC hardware decoding on Raspberry Pi will not work.
+
### Note to packagers:
-UxPlay's GPLv3 license does not have an added
-"GPL exception" explicitly allowing it to be distributed in compiled form when linked to OpenSSL versions
-**prior to v. 3.0.0** (older versions of OpenSSL have a license clause incompatible with the GPL unless
-OpenSSL can be regarded as a "System Library", which it is in *BSD). Many Linux distributions treat OpenSSL
-as a "System Library", but some (e.g. Debian) do not: in this case, the issue is solved by linking
-with OpenSSL-3.0.0 or later.
+UxPlay's GPLv3 license does not have an added "GPL exception" explicitly
+allowing it to be distributed in compiled form when linked to OpenSSL
+versions **prior to v. 3.0.0** (older versions of OpenSSL have a license
+clause incompatible with the GPL unless OpenSSL can be regarded as a
+"System Library", which it is in \*BSD). Many Linux distributions treat
+OpenSSL as a "System Library", but some (e.g. Debian) do not: in this
+case, the issue is solved by linking with OpenSSL-3.0.0 or later.
# Getting UxPlay
-Either download and unzip [UxPlay-master.zip](https://github.com/FDH2/UxPlay/archive/refs/heads/master.zip),
-or (if git is installed): "git clone https://github.com/FDH2/UxPlay". You
-can also download a recent or earlier version listed
-in [Releases](https://github.com/FDH2/UxPlay/releases).
+Either download and unzip
+[UxPlay-master.zip](https://github.com/FDH2/UxPlay/archive/refs/heads/master.zip),
+or (if git is installed): "git clone https://github.com/FDH2/UxPlay".
+You can also download a recent or earlier version listed in
+[Releases](https://github.com/FDH2/UxPlay/releases).
-* A recent UxPlay can also be found on the original [antimof site](https://github.com/antimof/UxPlay);
-that original project is inactive, but is usually kept current or almost-current with the
-[active UxPlay github site](https://github.com/FDH2/UxPlay) (thank you antimof!).
+- A recent UxPlay can also be found on the original [antimof
+ site](https://github.com/antimof/UxPlay); that original project is
+ inactive, but is usually kept current or almost-current with the
+ [active UxPlay github site](https://github.com/FDH2/UxPlay) (thank
+ you antimof!).
-## Building UxPlay on Linux (or \*BSD):
+## Building UxPlay on Linux (or \*BSD):
### Debian-based systems:
-(Adapt these instructions for non-Debian-based Linuxes or *BSD; for macOS,
-see specific instruction below). See [Troubleshooting](#troubleshooting) below for help with
-any difficulties.
+(Adapt these instructions for non-Debian-based Linuxes or \*BSD; for
+macOS, see specific instruction below). See
+[Troubleshooting](#troubleshooting) below for help with any
+difficulties.
-You need a C/C++ compiler (e.g. g++) with the standard development libraries
-installed. Debian-based systems provide a package "build-essential" for use
-in compiling software. You also need pkg-config: if it is not found
-by "`which pkg-config`", install pkg-config or its work-alike replacement
-pkgconf. Also make sure that cmake>=3.10 is installed:
-"`sudo apt install cmake`" (add ``build-essential`` and `pkg-config`
-(or ``pkgconf``) to this if needed).
+You need a C/C++ compiler (e.g. g++) with the standard development
+libraries installed. Debian-based systems provide a package
+"build-essential" for use in compiling software. You also need
+pkg-config: if it is not found by "`which pkg-config`", install
+pkg-config or its work-alike replacement pkgconf. Also make sure that
+cmake\>=3.10 is installed: "`sudo apt install cmake`" (add
+`build-essential` and `pkg-config` (or `pkgconf`) to this if needed).
Make sure that your distribution provides OpenSSL 1.1.1 or later, and
-libplist 2.0 or later. (This means Debian 10 "Buster" based systems (e.g, Ubuntu 18.04) or newer;
-on Debian 10 systems "libplist" is an older version, you need "libplist3".) If it does
-not, you may need to build and install these from
-source (see instructions at the end of this README).
-
-If you have a non-standard OpenSSL
-installation, you may need to set the environment variable OPENSSL_ROOT_DIR
-(_e.g._ , "`export OPENSSL_ROOT_DIR=/usr/local/lib64`" if that is where it is installed).
-Similarly, for non-standard (or multiple) GStreamer installations, set the
-environment variable GSTREAMER_ROOT_DIR to the directory that contains the
-".../gstreamer-1.0/" directory of the gstreamer installation that UxPlay should use
-(if this is _e.g._ "~/my_gstreamer/lib/gstreamer-1.0/", set this location
-with "`export GSTREAMER_ROOT_DIR=$HOME/my_gstreamer/lib`").
-
-* Most users will use the GStreamer supplied by their distribution, but a few (in particular users
-of Raspberry Pi OS Lite Legacy (Buster) on a Raspberry Pi model 4B who wish to stay on that
-unsupported Legacy OS for compatibility with other apps) should instead build a newer Gstreamer from source
-following [these instructions](https://github.com/FDH2/UxPlay/wiki/Building-latest-GStreamer-from-source-on-distributions-with-older-GStreamer-(e.g.-Raspberry-Pi-OS-).) . **Do this
-_before_ building UxPlay**.
-
+libplist 2.0 or later. (This means Debian 10 "Buster" based systems
+(e.g, Ubuntu 18.04) or newer; on Debian 10 systems "libplist" is an
+older version, you need "libplist3".) If it does not, you may need to
+build and install these from source (see instructions at the end of this
+README).
+
+If you have a non-standard OpenSSL installation, you may need to set the
+environment variable OPENSSL_ROOT_DIR (*e.g.* ,
+"`export OPENSSL_ROOT_DIR=/usr/local/lib64`" if that is where it is
+installed). Similarly, for non-standard (or multiple) GStreamer
+installations, set the environment variable GSTREAMER_ROOT_DIR to the
+directory that contains the ".../gstreamer-1.0/" directory of the
+gstreamer installation that UxPlay should use (if this is *e.g.*
+"\~/my_gstreamer/lib/gstreamer-1.0/", set this location with
+"`export GSTREAMER_ROOT_DIR=$HOME/my_gstreamer/lib`").
+
+- Most users will use the GStreamer supplied by their distribution,
+ but a few (in particular users of Raspberry Pi OS Lite Legacy
+ (Buster) on a Raspberry Pi model 4B who wish to stay on that
+ unsupported Legacy OS for compatibility with other apps) should
+ instead build a newer Gstreamer from source following [these
+ instructions](https://github.com/FDH2/UxPlay/wiki/Building-latest-GStreamer-from-source-on-distributions-with-older-GStreamer-(e.g.-Raspberry-Pi-OS-).)
+ . **Do this *before* building UxPlay**.
In a terminal window, change directories to the source directory of the
-downloaded source code ("UxPlay-\*", "\*" = "master" or the release tag for
-zipfile downloads, "UxPlay" for "git clone" downloads), then follow the instructions below:
+downloaded source code ("UxPlay-\*", "\*" = "master" or the release tag
+for zipfile downloads, "UxPlay" for "git clone" downloads), then follow
+the instructions below:
**Note:** By default UxPlay will be built with optimization for the
-computer it is built on; when this is not the case, as when you are packaging
-for a distribution, use the cmake option `-DNO_MARCH_NATIVE=ON`.
-
-If you use X11 Windows on Linux or *BSD, and wish to toggle in/out of fullscreen mode with a keypress
-(F11 or Alt_L+Enter)
-UxPlay needs to be built with a dependence on X11. Starting with UxPlay-1.59, this will be done by
-default **IF** the X11 development libraries are installed and detected. Install these with
-"`sudo apt install libx11-dev`". If GStreamer < 1.20 is detected, a fix needed by
-screen-sharing apps (_e.g._, Zoom) will also be made.
-
-* If X11 development libraries are present, but you
-wish to build UxPlay *without* any X11 dependence, use
-the cmake option `-DNO_X11_DEPS=ON`.
-
-1. `sudo apt install libssl-dev libplist-dev`".
- (_unless you need to build OpenSSL and libplist from source_).
+computer it is built on; when this is not the case, as when you are
+packaging for a distribution, use the cmake option
+`-DNO_MARCH_NATIVE=ON`.
+
+If you use X11 Windows on Linux or \*BSD, and wish to toggle in/out of
+fullscreen mode with a keypress (F11 or Alt_L+Enter) UxPlay needs to be
+built with a dependence on X11. Starting with UxPlay-1.59, this will be
+done by default **IF** the X11 development libraries are installed and
+detected. Install these with "`sudo apt install libx11-dev`". If
+GStreamer \< 1.20 is detected, a fix needed by screen-sharing apps
+(*e.g.*, Zoom) will also be made.
+
+- If X11 development libraries are present, but you wish to build
+ UxPlay *without* any X11 dependence, use the cmake option
+ `-DNO_X11_DEPS=ON`.
+
+1. `sudo apt install libssl-dev libplist-dev`". (*unless you need to
+ build OpenSSL and libplist from source*).
2. `sudo apt install libavahi-compat-libdnssd-dev`
-3. `sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev`. (\*_Skip if you built Gstreamer from source_)
-4. `cmake .` (_For a cleaner build, which is useful if you modify the source, replace this
- by_ "``mkdir build; cd build; cmake ..``": _you can then delete the contents of the
- `build` directory if needed, without affecting the source._) Also add any cmake "`-D`" options
- here as needed (e.g, `-DNO_X11_DEPS=ON` or ``-DNO_MARCH_NATIVE=ON``).
-5. `make`
-6. `sudo make install` (you can afterwards uninstall with ``sudo make uninstall``
- in the same directory in which this was run).
-
-This installs the executable file "`uxplay`" to `/usr/local/bin`, (and installs a manpage to
-somewhere standard like `/usr/local/share/man/man1` and README
-files to somewhere like `/usr/local/share/doc/uxplay`). (If "man uxplay" fails, check if $MANPATH is set:
-if so, the path to the manpage (usually /usr/local/share/man/) needs to be added to $MANPATH .)
-The uxplay executable can also be found in the build directory after the build
-process, if you wish to test before installing (in which case
-the GStreamer plugins must first be installed).
-
-
-
-### Building on non-Debian Linux and \*BSD
-**For those with RPM-based distributions, a RPM spec file uxplay.spec is also available: see
-[Building an installable rpm package](#building-an-installable-rpm-package).
-
-* **Red Hat, or clones like CentOS (now continued as Rocky Linux or Alma Linux):**
-(sudo dnf install, or sudo yum install) openssl-devel libplist-devel avahi-compat-libdns_sd-devel
-gstreamer1-devel gstreamer1-plugins-base-devel (+libX11-devel for fullscreen X11) _(some of these
-may be in the "CodeReady" add-on repository, called "PowerTools" by clones)_
-
-* **Mageia, PCLinuxOS, OpenMandriva:**
-Same as Red Hat, except for name changes: (Mageia) "gstreamer1.0-devel", "gstreamer-plugins-base1.0-devel";
-(OpenMandriva) "libopenssl-devel", "gstreamer-devel", "libgst-plugins-base1.0-devel". PCLinuxOS: same as Mageia,
-but uses synaptic (or apt) as its package manager.
-
- * **openSUSE:**
-(sudo zypper install) libopenssl-3-devel (formerly
- libopenssl-devel) libplist-2_0-devel (formerly libplist-devel)
-avahi-compat-mDNSResponder-devel gstreamer-devel
-gstreamer-plugins-base-devel (+ libX11-devel for fullscreen X11).
-
-* **Arch Linux** (_Also available as a package in AUR_):
-(sudo pacman -Syu) openssl libplist avahi gst-plugins-base.
-
-* **FreeBSD:** (sudo pkg install) libplist gstreamer1.
-Either avahi-libdns or mDNSResponder must also be installed to provide the dns_sd library.
-OpenSSL is already installed as a System Library.
+3. `sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev`.
+ (\**Skip if you built Gstreamer from source*)
+4. `cmake .` (*For a cleaner build, which is useful if you modify the
+ source, replace this by* "`mkdir build; cd build; cmake ..`": *you
+ can then delete the contents of the `build` directory if needed,
+ without affecting the source.*) Also add any cmake "`-D`" options
+ here as needed (e.g, `-DNO_X11_DEPS=ON` or `-DNO_MARCH_NATIVE=ON`).
+5. `make`
+6. `sudo make install` (you can afterwards uninstall with
+ `sudo make uninstall` in the same directory in which this was run).
+
+This installs the executable file "`uxplay`" to `/usr/local/bin`, (and
+installs a manpage to somewhere standard like
+`/usr/local/share/man/man1` and README files to somewhere like
+`/usr/local/share/doc/uxplay`). (If "man uxplay" fails, check if
+\$MANPATH is set: if so, the path to the manpage (usually
+/usr/local/share/man/) needs to be added to \$MANPATH .) The uxplay
+executable can also be found in the build directory after the build
+process, if you wish to test before installing (in which case the
+GStreamer plugins must first be installed).
+
+### Building on non-Debian Linux and \*BSD
+
+\*\*For those with RPM-based distributions, a RPM spec file uxplay.spec
+is also available: see [Building an installable rpm
+package](#building-an-installable-rpm-package).
+
+- **Red Hat, or clones like CentOS (now continued as Rocky Linux or
+ Alma Linux):** (sudo dnf install, or sudo yum install) openssl-devel
+ libplist-devel avahi-compat-libdns_sd-devel gstreamer1-devel
+ gstreamer1-plugins-base-devel (+libX11-devel for fullscreen X11)
+ *(some of these may be in the "CodeReady" add-on repository, called
+ "PowerTools" by clones)*
+
+- **Mageia, PCLinuxOS, OpenMandriva:** Same as Red Hat, except for
+ name changes: (Mageia) "gstreamer1.0-devel",
+ "gstreamer-plugins-base1.0-devel"; (OpenMandriva)
+ "libopenssl-devel", "gstreamer-devel",
+ "libgst-plugins-base1.0-devel". PCLinuxOS: same as Mageia, but uses
+ synaptic (or apt) as its package manager.
+
+- **openSUSE:** (sudo zypper install) libopenssl-3-devel (formerly
+ libopenssl-devel) libplist-2_0-devel (formerly libplist-devel)
+ avahi-compat-mDNSResponder-devel gstreamer-devel
+ gstreamer-plugins-base-devel (+ libX11-devel for fullscreen X11).
+
+- **Arch Linux** (*Also available as a package in AUR*): (sudo pacman
+ -Syu) openssl libplist avahi gst-plugins-base.
+
+- **FreeBSD:** (sudo pkg install) libplist gstreamer1. Either
+ avahi-libdns or mDNSResponder must also be installed to provide the
+ dns_sd library. OpenSSL is already installed as a System Library.
#### Building an installable RPM package
-First-time RPM builders should first install the rpm-build and rpmdevtools packages,
-then create the rpmbuild tree with "`rpmdev-setuptree`". Then download and
-copy uxplay.spec into ``~/rpmbuild/SPECS``. In that directory, run "`rpmdev-spectool -g -R uxplay.spec`" to download the corresponding
-source file `uxplay-*.tar.gz` into ``~/rpmbuild/SOURCES`` ("rpmdev-spectool" may also be just called "spectool"); then
-run "```rpmbuild -ba uxplay.spec```" (you will need to install
-any required dependencies this reports). This should create the uxplay RPM package in a subdirectory of `~/rpmbuild/RPMS`.
-(**uxplay.spec** is tested on Fedora 38, Rocky Linux 9.2, openSUSE Leap 15.5, Mageia 9, OpenMandriva, PCLinuxOS;
-it can be easily modified to include dependency lists for other RPM-based distributions.)
+
+First-time RPM builders should first install the rpm-build and
+rpmdevtools packages, then create the rpmbuild tree with
+"`rpmdev-setuptree`". Then download and copy uxplay.spec into
+`~/rpmbuild/SPECS`. In that directory, run
+"`rpmdev-spectool -g -R uxplay.spec`" to download the corresponding
+source file `uxplay-*.tar.gz` into `~/rpmbuild/SOURCES`
+("rpmdev-spectool" may also be just called "spectool"); then run
+"`rpmbuild -ba uxplay.spec`" (you will need to install any required
+dependencies this reports). This should create the uxplay RPM package in
+a subdirectory of `~/rpmbuild/RPMS`. (**uxplay.spec** is tested on
+Fedora 38, Rocky Linux 9.2, openSUSE Leap 15.5, Mageia 9, OpenMandriva,
+PCLinuxOS; it can be easily modified to include dependency lists for
+other RPM-based distributions.)
## Running UxPlay
-### Installing plugins (Debian-based Linux distributions, including Ubuntu and Raspberry Pi OS) (_skip if you built a complete GStreamer from source_)
-
-Next install the GStreamer plugins that are needed with `sudo apt install gstreamer1.0-`.
-Values of `` required are:
-
-1. "**plugins-base**"
-2. "**libav**" (for sound),
-3. "**plugins-good**" (for v4l2 hardware h264 decoding)
-4. "**plugins-bad**" (for h264 decoding).
-
-**Debian-based distributions split some of the plugin packages into smaller pieces:**
-some that may also be needed include "**gl**" for OpenGL support (this provides the "-vs glimagesink" videosink, which
-can be very useful in many systems (including Raspberry Pi), and should always be used when using h264/h265 decoding by a NVIDIA GPU), "**gtk3**" (which
-provides the "-vs gtksink" videosink), and "**x**" for
-X11 support, although these may already be installed; "**vaapi**"
-is needed for hardware-accelerated h264 video decoding by Intel
-or AMD graphics (but not for use with NVIDIA using proprietary drivers). If sound is
-not working, "**alsa**"", "**pulseaudio**", or "**pipewire**" plugins may need to be
-installed, depending on how your audio is set up.
-
-* Also install "**gstreamer1.0-tools**" to get the utility gst-inspect-1.0 for examining the GStreamer installation.
-
-
-### Installing plugins (Non-Debian-based Linux or \*BSD) (_skip if you built a complete GStreamer from source_)
-
-In some cases, because of patent issues,
-the libav plugin feature **avdec_aac** needed for decoding AAC audio in mirror mode is not provided in the official distribution:
-get it from community repositories for those distributions.
-
-* **Red Hat, or clones like CentOS (now continued as Rocky Linux or Alma Linux):**
-Install gstreamer1-libav gstreamer1-plugins-bad-free (+ gstreamer1-vaapi
-for Intel/AMD graphics). In recent Fedora, gstreamer1-libav is renamed gstreamer1-plugin-libav.
-**To get avdec_aac, install packages from [rpmfusion.org](https://rpmfusion.org)**: (get ffmpeg-libs from rpmfusion;
-on RHEL or clones, but not recent Fedora, also get gstreamer1-libav from there).
-
-* **Mageia, PCLinuxOS, OpenMandriva:**
-Install gstreamer1.0-libav gstreamer1.0-plugins-bad (+ gstreamer1.0-vaapi
-for Intel/AMD graphics). **On Mageia, to get avdec_aac, install ffmpeg from the "tainted" repository**,
-(which also provides a more complete gstreamer1.0-plugins-bad).
-
-* **openSUSE:**
-Install gstreamer-plugins-libav gstreamer-plugins-bad (+ gstreamer-plugins-vaapi
-for Intel/AMD graphics). **To get avdec_aac, install libav\* packages for openSUSE
-from [Packman](https://ftp.gwdg.de/pub/linux/misc/packman/suse/) "Essentials"**; recommendation: after adding the
-Packman repository, use the option in YaST Software management to switch
-all system packages for multimedia to Packman).
-
-* **Arch Linux**
-Install gst-plugins-good gst-plugins-bad gst-libav (+ gstreamer-vaapi
-for Intel/AMD graphics).
-
- * **FreeBSD:** Install gstreamer1-libav, gstreamer1-plugins, gstreamer1-plugins-*
-(\* = core, good, bad, x, gtk, gl, vulkan, pulse, v4l2, ...), (+ gstreamer1-vaapi for Intel/AMD graphics).
-
-
-### Starting and running UxPlay
-
-Since UxPlay-1.64, UxPlay can be started with options read from a configuration file, which will be the first found of
-(1) a file with a path given by environment variable `$UXPLAYRC`, (2) ``~/.uxplayrc`` in the user's home
-directory ("~"), (3) ``~/.config/uxplayrc``. The format is one option per line, omitting the initial ``"-"`` of
-the command-line option. Lines in the configuration file beginning with `"#"` are treated as comments and ignored.
-
-**Run uxplay in a terminal window**. On some systems, you can specify fullscreen mode with the `-fs` option, or
-toggle into and out of fullscreen mode
-with F11 or (held-down left Alt)+Enter keys. Use Ctrl-C (or close the window)
-to terminate it when done. If the UxPlay server is not seen by the
-iOS client's drop-down "Screen Mirroring" panel, check that your DNS-SD
-server (usually avahi-daemon) is running: do this in a terminal window
-with ```systemctl status avahi-daemon```.
-If this shows the avahi-daemon is not running, control it
-with ```sudo systemctl [start,stop,enable,disable] avahi-daemon``` (on non-systemd systems, such as \*BSD,
-use ``sudo service avahi-daemon [status, start, stop, restart, ...]``). If UxPlay is
-seen, but the client fails to connect
-when it is selected, there may be a firewall on the server that prevents
-UxPlay from receiving client connection requests unless some network ports
-are opened: **if a firewall is active, also open UDP port 5353 (for mDNS queries)
-needed by Avahi**. See [Troubleshooting](#troubleshooting) below for
-help with this or other problems.
-
-* Unlike an Apple TV, the UxPlay server
-does not by default require clients to initially "pair" with it using a pin code
-displayed by the server (after which the client "trusts" the server, and does not
-need to repeat this). Since v1.67, Uxplay offers such "pin-authentication" as an option:
-see "`-pin`" and "``-reg``" in [Usage](#usage) for details, if you wish to use
-it. _Some clients
-with MDM (Mobile Device Management, often present on employer-owned devices) are required to use pin-authentication: UxPlay will
-provide this even when running without the pin option._
-
-* By default, UxPlay is locked to
-its current client until that client drops the connection; since UxPlay-1.58, the option `-nohold` modifies this
-behavior so that when a new client requests a connection, it removes the current client and takes over. UxPlay 1.66 introduces
-a mechanism ( `-restrict`, ``-allow ``, ```-block ```) to control which clients are allowed to connect, using their
-"deviceID" (which in Apple devices appears to be immutable).
-
-* In Mirror mode, GStreamer has a choice of **two** methods to play video with its accompanying audio: prior to UxPlay-1.64,
-the video and audio streams were both played as soon as possible after they arrived (the GStreamer "_sync=false_" method), with
-a GStreamer internal clock used to try to keep them synchronized. **Starting with UxPlay-1.64, the other method
-(GStreamer's "_sync=true_" mode), which uses timestamps in the audio and video streams sent by the client, is the new default**.
-On low-decoding-power UxPlay hosts (such as Raspberry Pi Zero W or 3 B+ models) this will drop video frames that cannot be decoded in time
-to play with the audio, making the video jerky, but still synchronized.
-
-The older method which does not drop late video frames
-worked well on more powerful systems, and is still available with the UxPlay option "`-vsync no`"; this method is adapted
-to "live streaming", and may be better when using UxPlay as a second monitor for a Mac computer, for example, while the new default
-timestamp-based method is best for watching a video, to keep lip movements and voices synchronized. (Without use of timestamps,
-video will eventually lag behind audio if it cannot be decoded fast enough: hardware-accelerated video-decoding helped to prevent this
-previously when timestamps were not being used.)
-
-
-
-* In Audio-only mode the GStreamer "sync=false" mode (not using timestamps) is still the default, but if you want to keep the audio
-playing on the server synchronized with the video showing on the client, use the `-async` timestamp-based option. (An example might be
-if you want to follow the Apple Music lyrics on the client while listening to superior sound on the UxPlay server). This
-delays the video on the client to match audio on the server, so leads to
-a slight delay before a pause or track-change initiated on the client takes effect on the audio played by the server.
-
-AirPlay volume-control attenuates volume (gain) by up to -30dB: the decibel range -30:0 can be rescaled from _Low_:0, or _Low_:_High_, using the
-option `-db` ("-db _Low_ " or "-db _Low_:_High_ "), _Low_ must be negative. Rescaling is linear in decibels.
-Note that GStreamer's audio format will "clip" any audio gain above +20db, so keep *High* below that level. The
-option ```-taper``` provides a "tapered" AirPlay volume-control profile some users may prefer.
-
-The -vsync and -async options
-also allow an optional positive (or negative) audio-delay adjustment in _milliseconds_ for fine-tuning : `-vsync 20.5`
-delays audio relative to video by 0.0205 secs; a negative value advances it.)
-
-* you may find video is improved by the setting -fps 60 that allows some video to be played at 60 frames
-per second. (You can see what framerate is actually streaming by using -vs fpsdisplaysink, and/or -FPSdata.)
-When using this, you should use the default timestamp-based synchronization option `-vsync`.
-
-* Since UxPlay-1.54, you can display the accompanying "Cover Art" from sources like Apple Music in Audio-Only (ALAC) mode:
-run "`uxplay -ca &`" in the background, then run a image viewer with an autoreload feature: an example
-is "feh": run "``feh -R 1 ``"
-in the foreground; terminate feh and then Uxplay with "`ctrl-C fg ctrl-C`".
-
-By default, GStreamer uses an algorithm to search for the best "videosink" (GStreamer's term for a graphics driver to display images) to use.
-You can overide this with the uxplay option `-vs `. Which videosinks are available depends on your operating system and
-graphics hardware: use "`gst-inspect-1.0 | grep sink | grep -e video -e Video -e image`" to see what is available. Some possibilites on Linux/\*BSD are:
-
-* **glimagesink** (OpenGL), **waylandsink**
-
-* **xvimagesink**, **ximagesink** (X11)
-
-* **kmssink**, **fbdevsink** (console graphics without X11)
-
-* **vaapisink** (for Intel/AMD hardware-accelerated graphics); for NVIDIA hardware graphics (with CUDA) use **glimagesink** combined
- with "`-vd nvh264dec`" (or "nvh264sldec", a new variant which will become "nvh264dec" in GStreamer-1.24).
-
-* If the server is "headless" (no attached monitor, renders audio only) use `-vs 0`.
-
-GStreamer also searches for the best "audiosink"; override its choice with `-as `. Choices on Linux include
-pulsesink, alsasink, pipewiresink, oss4sink; see what is available with `gst-inspect-1.0 | grep sink | grep -e audio -e Audio`.
-
-**One common problem involves GStreamer
-attempting to use incorrectly-configured or absent accelerated hardware h264
-video decoding (e.g., VAAPI).
-Try "`uxplay -avdec`" to force software video decoding; if this works you can
-then try to fix accelerated hardware video decoding if you need it, or just uninstall the GStreamer vaapi plugin.**
+### Installing plugins (Debian-based Linux distributions, including Ubuntu and Raspberry Pi OS) (*skip if you built a complete GStreamer from source*)
+
+Next install the GStreamer plugins that are needed with
+`sudo apt install gstreamer1.0-`. Values of `` required
+are:
+
+1. "**plugins-base**"
+2. "**libav**" (for sound),
+3. "**plugins-good**" (for v4l2 hardware h264 decoding)
+4. "**plugins-bad**" (for h264 decoding).
+
+**Debian-based distributions split some of the plugin packages into
+smaller pieces:** some that may also be needed include "**gl**" for
+OpenGL support (this provides the "-vs glimagesink" videosink, which can
+be very useful in many systems (including Raspberry Pi), and should
+always be used when using h264/h265 decoding by a NVIDIA GPU),
+"**gtk3**" (which provides the "-vs gtksink" videosink), and "**x**" for
+X11 support, although these may already be installed; "**vaapi**" is
+needed for hardware-accelerated h264 video decoding by Intel or AMD
+graphics (but not for use with NVIDIA using proprietary drivers). If
+sound is not working, "**alsa**"","**pulseaudio**", or "**pipewire**"
+plugins may need to be installed, depending on how your audio is set up.
+
+- Also install "**gstreamer1.0-tools**" to get the utility
+ gst-inspect-1.0 for examining the GStreamer installation.
+
+### Installing plugins (Non-Debian-based Linux or \*BSD) (*skip if you built a complete GStreamer from source*)
+
+In some cases, because of patent issues, the libav plugin feature
+**avdec_aac** needed for decoding AAC audio in mirror mode is not
+provided in the official distribution: get it from community
+repositories for those distributions.
+
+- **Red Hat, or clones like CentOS (now continued as Rocky Linux or
+ Alma Linux):** Install gstreamer1-libav gstreamer1-plugins-bad-free
+ (+ gstreamer1-vaapi for Intel/AMD graphics). In recent Fedora,
+ gstreamer1-libav is renamed gstreamer1-plugin-libav. **To get
+ avdec_aac, install packages from
+ [rpmfusion.org](https://rpmfusion.org)**: (get ffmpeg-libs from
+ rpmfusion; on RHEL or clones, but not recent Fedora, also get
+ gstreamer1-libav from there).
+
+- **Mageia, PCLinuxOS, OpenMandriva:** Install gstreamer1.0-libav
+ gstreamer1.0-plugins-bad (+ gstreamer1.0-vaapi for Intel/AMD
+ graphics). **On Mageia, to get avdec_aac, install ffmpeg from the
+ "tainted" repository**, (which also provides a more complete
+ gstreamer1.0-plugins-bad).
+
+- **openSUSE:** Install gstreamer-plugins-libav gstreamer-plugins-bad
+ (+ gstreamer-plugins-vaapi for Intel/AMD graphics). **To get
+ avdec_aac, install libav\* packages for openSUSE from
+ [Packman](https://ftp.gwdg.de/pub/linux/misc/packman/suse/)
+ "Essentials"**; recommendation: after adding the Packman repository,
+ use the option in YaST Software management to switch all system
+ packages for multimedia to Packman).
+
+- **Arch Linux** Install gst-plugins-good gst-plugins-bad gst-libav (+
+ gstreamer-vaapi for Intel/AMD graphics).
+
+- **FreeBSD:** Install gstreamer1-libav, gstreamer1-plugins,
+ gstreamer1-plugins-\* (\* = core, good, bad, x, gtk, gl, vulkan,
+ pulse, v4l2, ...), (+ gstreamer1-vaapi for Intel/AMD graphics).
+
+### Starting and running UxPlay
+
+Since UxPlay-1.64, UxPlay can be started with options read from a
+configuration file, which will be the first found of (1) a file with a
+path given by environment variable `$UXPLAYRC`, (2) `~/.uxplayrc` in the
+user's home directory ("\~"), (3) `~/.config/uxplayrc`. The format is
+one option per line, omitting the initial `"-"` of the command-line
+option. Lines in the configuration file beginning with `"#"` are treated
+as comments and ignored.
+
+**Run uxplay in a terminal window**. On some systems, you can specify
+fullscreen mode with the `-fs` option, or toggle into and out of
+fullscreen mode with F11 or (held-down left Alt)+Enter keys. Use Ctrl-C
+(or close the window) to terminate it when done. If the UxPlay server is
+not seen by the iOS client's drop-down "Screen Mirroring" panel, check
+that your DNS-SD server (usually avahi-daemon) is running: do this in a
+terminal window with `systemctl status avahi-daemon`. If this shows the
+avahi-daemon is not running, control it with
+`sudo systemctl [start,stop,enable,disable] avahi-daemon` (on
+non-systemd systems, such as \*BSD, use
+`sudo service avahi-daemon [status, start, stop, restart, ...]`). If
+UxPlay is seen, but the client fails to connect when it is selected,
+there may be a firewall on the server that prevents UxPlay from
+receiving client connection requests unless some network ports are
+opened: **if a firewall is active, also open UDP port 5353 (for mDNS
+queries) needed by Avahi**. See [Troubleshooting](#troubleshooting)
+below for help with this or other problems.
+
+- Unlike an Apple TV, the UxPlay server does not by default require
+ clients to initially "pair" with it using a pin code displayed by
+ the server (after which the client "trusts" the server, and does not
+ need to repeat this). Since v1.67, Uxplay offers such
+ "pin-authentication" as an option: see "`-pin`" and "`-reg`" in
+ [Usage](#usage) for details, if you wish to use it. *Some clients
+ with MDM (Mobile Device Management, often present on employer-owned
+ devices) are required to use pin-authentication: UxPlay will provide
+ this even when running without the pin option.*
+
+- By default, UxPlay is locked to its current client until that client
+ drops the connection; since UxPlay-1.58, the option `-nohold`
+ modifies this behavior so that when a new client requests a
+ connection, it removes the current client and takes over. UxPlay
+ 1.66 introduces a mechanism ( `-restrict`, `-allow `,
+ `-block `) to control which clients are allowed to connect,
+ using their "deviceID" (which in Apple devices appears to be
+ immutable).
+
+- In Mirror mode, GStreamer has a choice of **two** methods to play
+ video with its accompanying audio: prior to UxPlay-1.64, the video
+ and audio streams were both played as soon as possible after they
+ arrived (the GStreamer "*sync=false*" method), with a GStreamer
+ internal clock used to try to keep them synchronized. **Starting
+ with UxPlay-1.64, the other method (GStreamer's "*sync=true*" mode),
+ which uses timestamps in the audio and video streams sent by the
+ client, is the new default**. On low-decoding-power UxPlay hosts
+ (such as Raspberry Pi Zero W or 3 B+ models) this will drop video
+ frames that cannot be decoded in time to play with the audio, making
+ the video jerky, but still synchronized.
+
+The older method which does not drop late video frames worked well on
+more powerful systems, and is still available with the UxPlay option
+"`-vsync no`"; this method is adapted to "live streaming", and may be
+better when using UxPlay as a second monitor for a Mac computer, for
+example, while the new default timestamp-based method is best for
+watching a video, to keep lip movements and voices synchronized.
+(Without use of timestamps, video will eventually lag behind audio if it
+cannot be decoded fast enough: hardware-accelerated video-decoding
+helped to prevent this previously when timestamps were not being used.)
+
+- In Audio-only mode the GStreamer "sync=false" mode (not using
+ timestamps) is still the default, but if you want to keep the audio
+ playing on the server synchronized with the video showing on the
+ client, use the `-async` timestamp-based option. (An example might
+ be if you want to follow the Apple Music lyrics on the client while
+ listening to superior sound on the UxPlay server). This delays the
+ video on the client to match audio on the server, so leads to a
+ slight delay before a pause or track-change initiated on the client
+ takes effect on the audio played by the server.
+
+AirPlay volume-control attenuates volume (gain) by up to -30dB: the
+decibel range -30:0 can be rescaled from *Low*:0, or *Low*:*High*, using
+the option `-db` ("-db *Low*" or "-db *Low*:*High*"), *Low* must be
+negative. Rescaling is linear in decibels. Note that GStreamer's audio
+format will "clip" any audio gain above +20db, so keep *High* below that
+level. The option `-taper` provides a "tapered" AirPlay volume-control
+profile some users may prefer.
+
+The -vsync and -async options also allow an optional positive (or
+negative) audio-delay adjustment in *milliseconds* for fine-tuning :
+`-vsync 20.5` delays audio relative to video by 0.0205 secs; a negative
+value advances it.)
+
+- you may find video is improved by the setting -fps 60 that allows
+ some video to be played at 60 frames per second. (You can see what
+ framerate is actually streaming by using -vs fpsdisplaysink, and/or
+ -FPSdata.) When using this, you should use the default
+ timestamp-based synchronization option `-vsync`.
+
+- Since UxPlay-1.54, you can display the accompanying "Cover Art" from
+ sources like Apple Music in Audio-Only (ALAC) mode: run
+ "`uxplay -ca &`" in the background, then run a image viewer
+ with an autoreload feature: an example is "feh": run
+ "`feh -R 1 `" in the foreground; terminate feh and then Uxplay
+ with "`ctrl-C fg ctrl-C`".
+
+By default, GStreamer uses an algorithm to search for the best
+"videosink" (GStreamer's term for a graphics driver to display images)
+to use. You can overide this with the uxplay option `-vs `.
+Which videosinks are available depends on your operating system and
+graphics hardware: use
+"`gst-inspect-1.0 | grep sink | grep -e video -e Video -e image`" to see
+what is available. Some possibilites on Linux/\*BSD are:
+
+- **glimagesink** (OpenGL), **waylandsink**
+
+- **xvimagesink**, **ximagesink** (X11)
+
+- **kmssink**, **fbdevsink** (console graphics without X11)
+
+- **vaapisink** (for Intel/AMD hardware-accelerated graphics); for
+ NVIDIA hardware graphics (with CUDA) use **glimagesink** combined
+ with "`-vd nvh264dec`" (or "nvh264sldec", a new variant which will
+ become "nvh264dec" in GStreamer-1.24).
+
+- If the server is "headless" (no attached monitor, renders audio
+ only) use `-vs 0`.
+
+GStreamer also searches for the best "audiosink"; override its choice
+with `-as `. Choices on Linux include pulsesink, alsasink,
+pipewiresink, oss4sink; see what is available with
+`gst-inspect-1.0 | grep sink | grep -e audio -e Audio`.
+
+**One common problem involves GStreamer attempting to use
+incorrectly-configured or absent accelerated hardware h264 video
+decoding (e.g., VAAPI). Try "`uxplay -avdec`" to force software video
+decoding; if this works you can then try to fix accelerated hardware
+video decoding if you need it, or just uninstall the GStreamer vaapi
+plugin.**
See [Usage](#usage) for more run-time options.
-
-### **Special instructions for Raspberry Pi (tested on Raspberry Pi Zero 2 W, 3 Model B+, 4 Model B, and 5 only)**:
-
-* For Framebuffer video (for Raspberry Pi OS "Lite" and other non-X11 distributions) use the KMS
- videosink "-vs kmssink" (the DirectFB framebuffer videosink "dfbvideosink" is broken on the Pi, and segfaults).
- _In this case you should explicitly use the "-vs kmssink" option, as without it, autovideosink does not find the correct videosink._
-
-* Raspberry Pi 5 does not provide hardware H264 decoding (and does not need it).
-
-* Pi Zero 2 W, 3 Model B+ and 4 Model B should use hardware H264 decoding by the Broadcom GPU,
- but it requires an out-of-mainstream kernel module bcm2835_codec maintained in
- the [Raspberry Pi kernel tree](https://github.com/raspberrypi/linux);
- distributions that are known to supply it include Raspberry Pi OS, Ubuntu, and Manjaro-RPi4. Use
- software decoding (option -avdec) if this module is not available.
-
-* Uxplay uses the Video4Linux2 (v4l2) plugin from GStreamer-1.22 and later to access the GPU, if hardware H264 decoding is used. This
- should happen automatically. The option -v4l2 can be used, but it is usually best to just let GStreamer find the
- best video pipeline by itself.
-
-* On older distributions (GStreamer < 1.22), the v4l2 plugin needs a patch: see
- the [UxPlay Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches). Legacy
- Raspberry Pi OS (Bullseye) has a partially-patched GStreamer-1.18.4 which needs the uxplay option -bt709 (and don't use -v4l2); it
- is still better to apply the full patch from the UxPlay Wiki in this case.
-
-* For "double-legacy" Raspberry Pi OS (Buster), there is no patch for GStreamer-1.14.
- Instead, first build a complete newer GStreamer-1.18.6 from source
- using [these instructions](https://github.com/FDH2/UxPlay/wiki/Building-latest-GStreamer-from-source-on-distributions-with-older-GStreamer-(e.g.-Raspberry-Pi-OS-).) before
- building UxPlay.
-
-* Raspberry Pi 3 Model B+ running a 32 bit OS can also access the GPU with the GStreamer OMX plugin
- (use option "`-vd omxh264dec`"), but this is broken by Pi 4 Model B firmware. OMX support was removed from
- Raspberry Pi OS (Bullseye), but is present in Buster.
-
-* **H265 (4K)** video is potentially supported by hardware decoding on Raspberry Pi 5 models, as well as
- on Raspberry Pi 4 model B, using a dedicated HEVC decoding block, but the "rpivid" kernel driver for this
- is not yet supported by GStreamer (this driver decodes video into a non-standard format that cannot be supported
- by GStreamer until the driver is in the mainline Linux kernel). Raspberry Pi provides a version of ffmpeg that
- can use that format, but at present UxPlay cannot use this. The best solution would be for the driver to be
- "upstreamed" to the kernel, allowing GStreamer support. (Software HEVC decoding works, but does not seem to
- give satisfactory results on the Pi).
-
-Even with GPU video decoding, some frames may be dropped by the lower-power models to keep audio and video synchronized
-using timestamps. In Legacy Raspberry Pi OS (Bullseye), raspi-config "Performance Options" allows specifying how much memory
-to allocate to the GPU, but this setting appears to be absent in Bookworm (but it can still be set to e.g. 128MB by adding a line "gpu_mem=128" in /boot/config.txt).
-A Pi Zero 2 W (which has 512MB memory) worked well when tested in 32 bit Bullseye or Bookworm Lite
-with 128MB allocated to the GPU (default seems to be 64MB).
-
-The basic uxplay options for R Pi are ```uxplay [-vs ]```. The
-choice `` = ``glimagesink`` is sometimes useful.
-With the Wayland video compositor, use `` = ``waylandsink``.
-With framebuffer video, use `` = ``kmssink``.
-
-
-* Tip: to start UxPlay on a remote host (such as a Raspberry Pi) using ssh:
-
+### **Special instructions for Raspberry Pi (tested on Raspberry Pi Zero 2 W, 3 Model B+, 4 Model B, and 5 only)**:
+
+- For Framebuffer video (for Raspberry Pi OS "Lite" and other non-X11
+ distributions) use the KMS videosink "-vs kmssink" (the DirectFB
+ framebuffer videosink "dfbvideosink" is broken on the Pi, and
+ segfaults). *In this case you should explicitly use the "-vs
+ kmssink" option, as without it, autovideosink does not find the
+ correct videosink.*
+
+- Raspberry Pi 5 does not provide hardware H264 decoding (and does not
+ need it).
+
+- Pi Zero 2 W, 3 Model B+ and 4 Model B should use hardware H264
+ decoding by the Broadcom GPU, but it requires an out-of-mainstream
+ kernel module bcm2835_codec maintained in the [Raspberry Pi kernel
+ tree](https://github.com/raspberrypi/linux); distributions that are
+ known to supply it include Raspberry Pi OS, Ubuntu, and
+ Manjaro-RPi4. Use software decoding (option -avdec) if this module
+ is not available.
+
+- Uxplay uses the Video4Linux2 (v4l2) plugin from GStreamer-1.22 and
+ later to access the GPU, if hardware H264 decoding is used. This
+ should happen automatically. The option -v4l2 can be used, but it is
+ usually best to just let GStreamer find the best video pipeline by
+ itself.
+
+- On older distributions (GStreamer \< 1.22), the v4l2 plugin needs a
+ patch: see the [UxPlay
+ Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches).
+ Legacy Raspberry Pi OS (Bullseye) has a partially-patched
+ GStreamer-1.18.4 which needs the uxplay option -bt709 (and don't use
+ -v4l2); it is still better to apply the full patch from the UxPlay
+ Wiki in this case.
+
+- For "double-legacy" Raspberry Pi OS (Buster), there is no patch for
+ GStreamer-1.14. Instead, first build a complete newer
+ GStreamer-1.18.6 from source using [these
+ instructions](https://github.com/FDH2/UxPlay/wiki/Building-latest-GStreamer-from-source-on-distributions-with-older-GStreamer-(e.g.-Raspberry-Pi-OS-).)
+ before building UxPlay.
+
+- Raspberry Pi 3 Model B+ running a 32 bit OS can also access the GPU
+ with the GStreamer OMX plugin (use option "`-vd omxh264dec`"), but
+ this is broken by Pi 4 Model B firmware. OMX support was removed
+ from Raspberry Pi OS (Bullseye), but is present in Buster.
+
+- **H265 (4K)** video is potentially supported by hardware decoding on
+ Raspberry Pi 5 models, as well as on Raspberry Pi 4 model B, using a
+ dedicated HEVC decoding block, but the "rpivid" kernel driver for
+ this is not yet supported by GStreamer (this driver decodes video
+ into a non-standard format that cannot be supported by GStreamer
+ until the driver is in the mainline Linux kernel). Raspberry Pi
+ provides a version of ffmpeg that can use that format, but at
+ present UxPlay cannot use this. The best solution would be for the
+ driver to be "upstreamed" to the kernel, allowing GStreamer support.
+ (Software HEVC decoding works, but does not seem to give
+ satisfactory results on the Pi).
+
+Even with GPU video decoding, some frames may be dropped by the
+lower-power models to keep audio and video synchronized using
+timestamps. In Legacy Raspberry Pi OS (Bullseye), raspi-config
+"Performance Options" allows specifying how much memory to allocate to
+the GPU, but this setting appears to be absent in Bookworm (but it can
+still be set to e.g. 128MB by adding a line "gpu_mem=128" in
+/boot/config.txt). A Pi Zero 2 W (which has 512MB memory) worked well
+when tested in 32 bit Bullseye or Bookworm Lite with 128MB allocated to
+the GPU (default seems to be 64MB).
+
+The basic uxplay options for R Pi are `uxplay [-vs ]`. The
+choice `` = `glimagesink` is sometimes useful. With the
+Wayland video compositor, use `` = `waylandsink`. With
+framebuffer video, use `` = `kmssink`.
+
+- Tip: to start UxPlay on a remote host (such as a Raspberry Pi) using
+ ssh:
+
+```{=html}
+
```
- ssh user@remote_host
- export DISPLAY=:0
- nohup uxplay [options] > FILE &
-```
- Sound and video will play on the remote host; "nohup" will keep uxplay running if the ssh session is
- closed. Terminal output is saved to FILE (which can be /dev/null to discard it)
+ ssh user@remote_host
+ export DISPLAY=:0
+ nohup uxplay [options] > FILE &
-## Building UxPlay on macOS: **(Intel X86_64 and "Apple Silicon" M1/M2 Macs)**
+Sound and video will play on the remote host; "nohup" will keep uxplay
+running if the ssh session is closed. Terminal output is saved to FILE
+(which can be /dev/null to discard it)
-_Note: A native AirPlay Server feature is included in macOS 12 Monterey, but is restricted to recent hardware.
-UxPlay can run on older macOS systems that will not be able to run Monterey, or can run Monterey but not AirPlay._
+## Building UxPlay on macOS: **(Intel X86_64 and "Apple Silicon" M1/M2 Macs)**
-These instructions for macOS assume that the Xcode command-line developer tools are installed (if Xcode is
-installed, open the Terminal, type "sudo xcode-select --install" and accept the conditions).
+*Note: A native AirPlay Server feature is included in macOS 12 Monterey,
+but is restricted to recent hardware. UxPlay can run on older macOS
+systems that will not be able to run Monterey, or can run Monterey but
+not AirPlay.*
-It is also assumed that CMake >= 3.13 is installed:
-this can be done with package managers [MacPorts](http://www.macports.org) (`sudo port install cmake`),
-[Homebrew](http://brew.sh) (`brew install cmake`), or by a download from
-[https://cmake.org/download/](https://cmake.org/download/). Also install `git` if you will use it to fetch UxPlay.
+These instructions for macOS assume that the Xcode command-line
+developer tools are installed (if Xcode is installed, open the Terminal,
+type "sudo xcode-select --install" and accept the conditions).
-Next install libplist and openssl-3.x. Note that static versions of these libraries will be
-used in the macOS builds, so they can be uninstalled after building uxplay, if you wish.
+It is also assumed that CMake \>= 3.13 is installed: this can be done
+with package managers [MacPorts](http://www.macports.org)
+(`sudo port install cmake`), [Homebrew](http://brew.sh)
+(`brew install cmake`), or by a download from
+. Also install `git` if you will use it to
+fetch UxPlay.
-* If you use Homebrew: `brew install libplist openssl@3`
+Next install libplist and openssl-3.x. Note that static versions of
+these libraries will be used in the macOS builds, so they can be
+uninstalled after building uxplay, if you wish.
-* if you use MacPorts: `sudo port install libplist-devel openssl3`
+- If you use Homebrew: `brew install libplist openssl@3`
-Otherwise, build libplist and openssl from source: see instructions near the end of this README;
-requires development tools (autoconf, automake, libtool, _etc._) to be installed.
+- if you use MacPorts: `sudo port install libplist-devel openssl3`
+Otherwise, build libplist and openssl from source: see instructions near
+the end of this README; requires development tools (autoconf, automake,
+libtool, *etc.*) to be installed.
Next get the latest macOS release of GStreamer-1.0.
-**Using "Official" GStreamer (Recommended for both MacPorts and Homebrew users)**: install
-the GStreamer release for macOS
-from [https://gstreamer.freedesktop.org/download/](https://gstreamer.freedesktop.org/download/).
-(This release contains its own pkg-config,
-so you don't have to install one.) Install both the gstreamer-1.0 and gstreamer-1.0-devel packages. After downloading, Shift-Click on them
-to install (they install to /Library/FrameWorks/GStreamer.framework). Homebrew or MacPorts users should **not** install (or should uninstall)
-the GStreamer supplied by their package manager, if they use the "official" release.
-
-* Since GStreamer v1.22, the "Official" (gstreamer.freedesktop.org) macOS binaries require a wrapper "gst_macos_main"
-around the actual main program (uxplay). This should have been applied during the UxPlay compilation process, and
-the initial UxPlay terminal message should confirm it is being used. (UxPlay can also be built using "Official" GStreamer v.1.20.7
-binaries, which work without the wrapper.)
-
-**Using Homebrew's GStreamer**: pkg-config is needed: ("brew install pkg-config gstreamer").
-This causes a large number of extra packages to be installed by Homebrew as dependencies.
-The [Homebrew gstreamer installation](https://formulae.brew.sh/formula/gstreamer#default) has recently been
-reworked into a single "formula" named `gstreamer`, which now works without needing GST_PLUGIN_PATH to be
-set in the enviroment. Homebrew installs gstreamer to `HOMEBREW_PREFIX/lib/gstreamer-1.0` where by default ``HOMEBREW_PREFIX/*`` is
-`/opt/homebrew/*` on Apple Silicon Macs, and ``/usr/local/*`` on Intel Macs; do not put any
-extra non-Homebrew plugins (that you build yourself) there, and instead set GST_PLUGIN_PATH to point to
-their location (Homebrew does not supply a complete GStreamer, but seems to have everything needed for UxPlay).
-**New: the UxPlay build script will now also detect Homebrew installations in non-standard locations indicated by
-the environment variable `$HOMEBREW_PREFIX`.**
-
-
-**Using GStreamer installed from MacPorts**: this is **not** recommended, as currently the MacPorts GStreamer
-is old (v1.16.2), unmaintained, and built to use X11:
-
- * Instead [build gstreamer yourself](https://github.com/FDH2/UxPlay/wiki/Building-GStreamer-from-Source-on-macOS-with-MacPorts)
-if you use MacPorts and do not want to use the "Official" Gstreamer binaries.
-
-_(If you really wish to use the MacPorts GStreamer-1.16.2,
-install pkgconf ("sudo port install pkgconf"), then
-"sudo port install gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good gstreamer1-gst-plugins-bad gstreamer1-gst-libav".
-For X11 support on macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and run
-it from an XQuartz terminal with -vs ximagesink; older non-retina macs require a lower resolution
-when using X11: `uxplay -s 800x600`.)_
-
-
-
-After installing GStreamer, build and install uxplay: open a terminal and change into the UxPlay source directory
-("UxPlay-master" for zipfile downloads, "UxPlay" for "git clone" downloads) and build/install with
-"cmake . ; make ; sudo make install " (same as for Linux).
-
- * Running UxPlay while checking for GStreamer warnings (do this with "export GST_DEBUG=2" before runnng UxPlay) reveals
- that with the default (since UxPlay 1.64) use of timestamps for video synchonization, many video frames are being dropped
- (only on macOS), perhaps due to another error (about videometa) that shows up in the GStreamer warnings. **Recommendation:
- use the new UxPlay "no timestamp" option "`-vsync no`"** (you can add a line "vsync no" in the uxplayrc configuration file).
-
- * On macOS with this installation of GStreamer, the only videosinks available seem to be glimagesink (default choice made by
- autovideosink) and osxvideosink. The window title does not show the Airplay server name, but the window is visible to
- screen-sharing apps (e.g., Zoom). The only available audiosink seems to be osxaudiosink.
-
- * The option -nc is always used, whether or not it is selected.
- This is a workaround for a problem with GStreamer videosinks on macOS:
- if the GStreamer pipeline is destroyed while the mirror window is still open, a segfault occurs.
-
- * In the case of glimagesink, the resolution settings "-s wxh" do not affect
- the (small) initial OpenGL mirror window size, but the window can be expanded using the mouse or trackpad.
- In contrast, a window created with "-vs osxvideosink" is initially big, but has the wrong aspect ratio (stretched image);
- in this case the aspect ratio changes when the window width is changed by dragging its side;
- the option `-vs "osxvideosink force-aspect-ratio=true"` can be used to make the window have the
- correct aspect ratio when it first opens.
+**Using "Official" GStreamer (Recommended for both MacPorts and Homebrew
+users)**: install the GStreamer release for macOS from
+. (This release contains
+its own pkg-config, so you don't have to install one.) Install both the
+gstreamer-1.0 and gstreamer-1.0-devel packages. After downloading,
+Shift-Click on them to install (they install to
+/Library/FrameWorks/GStreamer.framework). Homebrew or MacPorts users
+should **not** install (or should uninstall) the GStreamer supplied by
+their package manager, if they use the "official" release.
+
+- Since GStreamer v1.22, the "Official" (gstreamer.freedesktop.org)
+ macOS binaries require a wrapper "gst_macos_main" around the actual
+ main program (uxplay). This should have been applied during the
+ UxPlay compilation process, and the initial UxPlay terminal message
+ should confirm it is being used. (UxPlay can also be built using
+ "Official" GStreamer v.1.20.7 binaries, which work without the
+ wrapper.)
+
+**Using Homebrew's GStreamer**: pkg-config is needed: ("brew install
+pkg-config gstreamer"). This causes a large number of extra packages to
+be installed by Homebrew as dependencies. The [Homebrew gstreamer
+installation](https://formulae.brew.sh/formula/gstreamer#default) has
+recently been reworked into a single "formula" named `gstreamer`, which
+now works without needing GST_PLUGIN_PATH to be set in the enviroment.
+Homebrew installs gstreamer to `HOMEBREW_PREFIX/lib/gstreamer-1.0` where
+by default `HOMEBREW_PREFIX/*` is `/opt/homebrew/*` on Apple Silicon
+Macs, and `/usr/local/*` on Intel Macs; do not put any extra
+non-Homebrew plugins (that you build yourself) there, and instead set
+GST_PLUGIN_PATH to point to their location (Homebrew does not supply a
+complete GStreamer, but seems to have everything needed for UxPlay).
+**New: the UxPlay build script will now also detect Homebrew
+installations in non-standard locations indicated by the environment
+variable `$HOMEBREW_PREFIX`.**
+
+**Using GStreamer installed from MacPorts**: this is **not**
+recommended, as currently the MacPorts GStreamer is old (v1.16.2),
+unmaintained, and built to use X11:
+
+- Instead [build gstreamer
+ yourself](https://github.com/FDH2/UxPlay/wiki/Building-GStreamer-from-Source-on-macOS-with-MacPorts)
+ if you use MacPorts and do not want to use the "Official" Gstreamer
+ binaries.
+
+*(If you really wish to use the MacPorts GStreamer-1.16.2, install
+pkgconf ("sudo port install pkgconf"), then "sudo port install
+gstreamer1-gst-plugins-base gstreamer1-gst-plugins-good
+gstreamer1-gst-plugins-bad gstreamer1-gst-libav". For X11 support on
+macOS, compile UxPlay using a special cmake option `-DUSE_X11=ON`, and
+run it from an XQuartz terminal with -vs ximagesink; older non-retina
+macs require a lower resolution when using X11: `uxplay -s 800x600`.)*
+
+After installing GStreamer, build and install uxplay: open a terminal
+and change into the UxPlay source directory ("UxPlay-master" for zipfile
+downloads, "UxPlay" for "git clone" downloads) and build/install with
+"cmake . ; make ; sudo make install" (same as for Linux).
+
+- Running UxPlay while checking for GStreamer warnings (do this with
+ "export GST_DEBUG=2" before runnng UxPlay) reveals that with the
+ default (since UxPlay 1.64) use of timestamps for video
+ synchonization, many video frames are being dropped (only on macOS),
+ perhaps due to another error (about videometa) that shows up in the
+ GStreamer warnings. **Recommendation: use the new UxPlay "no
+ timestamp" option "`-vsync no`"** (you can add a line "vsync no" in
+ the uxplayrc configuration file).
+
+- On macOS with this installation of GStreamer, the only videosinks
+ available seem to be glimagesink (default choice made by
+ autovideosink) and osxvideosink. The window title does not show the
+ Airplay server name, but the window is visible to screen-sharing
+ apps (e.g., Zoom). The only available audiosink seems to be
+ osxaudiosink.
+
+- The option -nc is always used, whether or not it is selected. This
+ is a workaround for a problem with GStreamer videosinks on macOS: if
+ the GStreamer pipeline is destroyed while the mirror window is still
+ open, a segfault occurs.
+
+- In the case of glimagesink, the resolution settings "-s wxh" do not
+ affect the (small) initial OpenGL mirror window size, but the window
+ can be expanded using the mouse or trackpad. In contrast, a window
+ created with "-vs osxvideosink" is initially big, but has the wrong
+ aspect ratio (stretched image); in this case the aspect ratio
+ changes when the window width is changed by dragging its side; the
+ option `-vs "osxvideosink force-aspect-ratio=true"` can be used to
+ make the window have the correct aspect ratio when it first opens.
## Building UxPlay on Microsoft Windows, using MSYS2 with the MinGW-64 compiler.
-* tested on Windows 10 and 11, 64-bit.
-
-1. Download and install **Bonjour SDK for Windows v3.0**. You can download the SDK without any registration
- at [softpedia.com](https://www.softpedia.com/get/Programming/SDK-DDK/Bonjour-SDK.shtml), or get it from the official Apple
- site [https://developer.apple.com/download](https://developer.apple.com/download/all/?q=Bonjour%20SDK%20for%20Windows) (Apple makes
- you register as a developer to access it from their site). This should install the Bonjour SDK as `C:\Program Files\Bonjour SDK`.
-
-2. (This is for 64-bit Windows; a build for 32-bit Windows should be possible, but is not tested.) The
- unix-like MSYS2 build environment will be used: download and install MSYS2 from the official
- site [https://www.msys2.org/](https://www.msys2.org). Accept the default installation location `C:\mysys64`.
-
-3. [MSYS2 packages](https://packages.msys2.org/package/) are installed with a
- variant of the "pacman" package manager used by Arch Linux. Open a "MSYS2 MINGW64" terminal
- from the MSYS2 tab in the Windows Start menu, and update the new
- MSYS2 installation with "pacman -Syu". Then install the **MinGW-64** compiler and **cmake**
-
- ```
- pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc
- ```
-
- The compiler with all required dependencies will be installed in the msys64 directory, with
- default path `C:/msys64/mingw64`. Here we will simply build UxPlay from the command line
- in the MSYS2 environment (this uses "`ninja`" in place of "``make``" for the build system).
-
-4. Download the latest UxPlay from github **(to use `git`, install it with ``pacman -S git``,
- then "`git clone https://github.com/FDH2/UxPlay`")**, then install UxPlay dependencies (openssl is already
- installed with MSYS2):
-
- `pacman -S mingw-w64-x86_64-libplist mingw-w64-x86_64-gstreamer mingw-w64-x86_64-gst-plugins-base`
-
- If you are trying a different Windows build system, MSVC versions of GStreamer
- for Windows are available from the [official GStreamer site](https://gstreamer.freedesktop.org/download/),
- but only the MinGW 64-bit build on MSYS2 has been tested.
-
-5. cd to the UxPlay source directory, then "`mkdir build`" and "``cd build``". The build process assumes that
- the Bonjour SDK is installed at `C:\Program Files\Bonjour SDK`. If it is somewhere else, set the enviroment
- variable BONJOUR_SDK_HOME to point to its location. Then build UxPlay with
-
- `cmake ..`
-
- `ninja`
-
-6. Assuming no error in either of these, you will have built the uxplay executable **uxplay.exe** in the
- current ("build") directory. The "sudo make install" and "sudo make uninstall" features offered in the
- other builds are not available on Windows; instead, the MSYS2 environment has
- `/mingw64/...` available, and you can install the uxplay.exe executable
- in `C:/msys64/mingw64/bin` (plus manpage and documentation in ``C:/msys64/mingw64/share/...``) with
+- tested on Windows 10 and 11, 64-bit.
+
+1. Download and install **Bonjour SDK for Windows v3.0**. You can
+ download the SDK without any registration at
+ [softpedia.com](https://www.softpedia.com/get/Programming/SDK-DDK/Bonjour-SDK.shtml),
+ or get it from the official Apple site
+ [https://developer.apple.com/download](https://developer.apple.com/download/all/?q=Bonjour%20SDK%20for%20Windows)
+ (Apple makes you register as a developer to access it from their
+ site). This should install the Bonjour SDK as
+ `C:\Program Files\Bonjour SDK`.
+
+2. (This is for 64-bit Windows; a build for 32-bit Windows should be
+ possible, but is not tested.) The unix-like MSYS2 build environment
+ will be used: download and install MSYS2 from the official site
+ [https://www.msys2.org/](https://www.msys2.org). Accept the default
+ installation location `C:\mysys64`.
+
+3. [MSYS2 packages](https://packages.msys2.org/package/) are installed
+ with a variant of the "pacman" package manager used by Arch Linux.
+ Open a "MSYS2 MINGW64" terminal from the MSYS2 tab in the Windows
+ Start menu, and update the new MSYS2 installation with "pacman
+ -Syu". Then install the **MinGW-64** compiler and **cmake**
+
+ pacman -S mingw-w64-x86_64-cmake mingw-w64-x86_64-gcc
+
+ The compiler with all required dependencies will be installed in the
+ msys64 directory, with default path `C:/msys64/mingw64`. Here we
+ will simply build UxPlay from the command line in the MSYS2
+ environment (this uses "`ninja`" in place of "`make`" for the build
+ system).
+
+4. Download the latest UxPlay from github **(to use `git`, install it
+ with `pacman -S git`, then
+ "`git clone https://github.com/FDH2/UxPlay`")**, then install UxPlay
+ dependencies (openssl is already installed with MSYS2):
+
+ `pacman -S mingw-w64-x86_64-libplist mingw-w64-x86_64-gstreamer mingw-w64-x86_64-gst-plugins-base`
+
+ If you are trying a different Windows build system, MSVC versions of
+ GStreamer for Windows are available from the [official GStreamer
+ site](https://gstreamer.freedesktop.org/download/), but only the
+ MinGW 64-bit build on MSYS2 has been tested.
+
+5. cd to the UxPlay source directory, then "`mkdir build`" and
+ "`cd build`". The build process assumes that the Bonjour SDK is
+ installed at `C:\Program Files\Bonjour SDK`. If it is somewhere
+ else, set the enviroment variable BONJOUR_SDK_HOME to point to its
+ location. Then build UxPlay with
+
+ `cmake ..`
+
+ `ninja`
+
+6. Assuming no error in either of these, you will have built the uxplay
+ executable **uxplay.exe** in the current ("build") directory. The
+ "sudo make install" and "sudo make uninstall" features offered in
+ the other builds are not available on Windows; instead, the MSYS2
+ environment has `/mingw64/...` available, and you can install the
+ uxplay.exe executable in `C:/msys64/mingw64/bin` (plus manpage and
+ documentation in `C:/msys64/mingw64/share/...`) with
`cmake --install . --prefix /mingw64`
-
- To be able to view the manpage, you need to install the manpage viewer with "`pacman -S man`".
+
+ To be able to view the manpage, you need to install the manpage
+ viewer with "`pacman -S man`".
To run **uxplay.exe** you need to install some gstreamer plugin packages
-with `pacman -S mingw-w64-x86_64-gst-`, where the required ones have ```` given by
+with `pacman -S mingw-w64-x86_64-gst-`, where the required ones
+have `` given by
+
+1. **libav**
+2. **plugins-good**
+3. **plugins-bad**
- 1. **libav**
- 2. **plugins-good**
- 3. **plugins-bad**
-
Other possible MSYS2 gstreamer plugin packages you might use are listed
in [MSYS2 packages](https://packages.msys2.org/package/).
-
-You also will need to grant permission to the uxplay executable uxplay.exe to access data through the Windows
-firewall. You may automatically be offered the choice to do this when you first run uxplay, or you may need to do it
-using **Windows Settings->Update and Security->Windows Security->Firewall & network protection -> allow an app
-through firewall**. If your virus protection flags uxplay.exe as "suspicious" (but without a true malware signature)
-you may need to give it an exception.
-
-Now test by running "`uxplay`" (in a MSYS2 terminal window). If you
-need to specify the audiosink, there are two main choices on Windows: the older DirectSound
-plugin "`-as directsoundsink`", and the more modern Windows Audio Session API (wasapi)
-plugin "`-as wasapisink`", which supports [additional options](https://gstreamer.freedesktop.org/documentation/wasapi/wasapisink.html) such as
-```
-uxplay -as 'wasapisink device=\"\"'
-```
-where `` specifies an available audio device by its GUID, which can be found using
-"`gst-device-monitor-1.0 Audio`": ```` has a form
-like ```\{0.0.0.00000000\}.\{98e35b2b-8eba-412e-b840-fd2c2492cf44\}```. If "`device`" is not specified, the
-default audio device is used.
-
-If you wish to specify the videosink using the `-vs ` option, some choices for `` are
-`d3d11videosink`, ``d3dvideosink``, ```glimagesink```,
-`gtksink`.
-
-* With Direct3D 11.0 or greater, you can either always be in fullscreen mode using
-option `-vs "d3d11videosink fullscreen-toggle-mode=property fullscreen=true"`, or
-get the ability to toggle into and out of fullscreen mode using the Alt-Enter key combination with
-option `-vs "d3d11videosink fullscreen-toggle-mode=alt-enter"`.
-For convenience, these options will be added if just ``-vs d3d11videosink`` with or without the fullscreen
-option "-fs" is used. _(Windows users may wish to add "``vs d3d11videosink``" (no initial "`-`") to the
-UxPlay startup options file; see "man uxplay" or "uxplay -h".)_
-
-The executable uxplay.exe can also be run without the MSYS2 environment, in
-the Windows Terminal, with `C:\msys64\mingw64\bin\uxplay`.
+You also will need to grant permission to the uxplay executable
+uxplay.exe to access data through the Windows firewall. You may
+automatically be offered the choice to do this when you first run
+uxplay, or you may need to do it using **Windows Settings-\>Update and
+Security-\>Windows Security-\>Firewall & network protection -\> allow an
+app through firewall**. If your virus protection flags uxplay.exe as
+"suspicious" (but without a true malware signature) you may need to give
+it an exception.
+
+Now test by running "`uxplay`" (in a MSYS2 terminal window). If you need
+to specify the audiosink, there are two main choices on Windows: the
+older DirectSound plugin "`-as directsoundsink`", and the more modern
+Windows Audio Session API (wasapi) plugin "`-as wasapisink`", which
+supports [additional
+options](https://gstreamer.freedesktop.org/documentation/wasapi/wasapisink.html)
+such as
+
+ uxplay -as 'wasapisink device=\"\"'
+
+where `` specifies an available audio device by its GUID, which
+can be found using "`gst-device-monitor-1.0 Audio`": `` has a form
+like `\{0.0.0.00000000\}.\{98e35b2b-8eba-412e-b840-fd2c2492cf44\}`. If
+"`device`" is not specified, the default audio device is used.
+
+If you wish to specify the videosink using the `-vs ` option,
+some choices for `` are `d3d11videosink`, `d3dvideosink`,
+`glimagesink`, `gtksink`.
+
+- With Direct3D 11.0 or greater, you can either always be in
+ fullscreen mode using option
+ `-vs "d3d11videosink fullscreen-toggle-mode=property fullscreen=true"`,
+ or get the ability to toggle into and out of fullscreen mode using
+ the Alt-Enter key combination with option
+ `-vs "d3d11videosink fullscreen-toggle-mode=alt-enter"`. For
+ convenience, these options will be added if just
+ `-vs d3d11videosink` with or without the fullscreen option "-fs" is
+ used. *(Windows users may wish to add "`vs d3d11videosink`" (no
+ initial "`-`") to the UxPlay startup options file; see "man uxplay"
+ or "uxplay -h".)*
+
+The executable uxplay.exe can also be run without the MSYS2 environment,
+in the Windows Terminal, with `C:\msys64\mingw64\bin\uxplay`.
# Usage
Options:
-* These can also be written (one option per line, without the initial "`-`" character) in the UxPlay startup file (either given by
-environment variable `$UXPLAYRC`, or ``~/.uxplayrc`` or ```~/.config/uxplayrc```); lines begining
-with "`#`" are treated as comments, and ignored. Command line options supersede options in the startup file.
-
-**-n server_name** (Default: UxPlay); server_name@_hostname_ will be the name that appears offering
- AirPlay services to your iPad, iPhone etc, where _hostname_ is the name of the server running uxplay.
- This will also now be the name shown above the mirror display (X11) window.
-
-**-nh** Do not append "@_hostname_" at the end of the AirPlay server name.
-
-**-h265** Activate "ScreenMultiCodec" support (AirPlay "Features" bit 42) for accepting h265 (4K/HEVC) video in addition to h264
- video (1080p) in screen-mirror mode. When this option is used, two "video pipelines" (one for h264, one for h265) are created.
- If any GStreamer plugins in the pipeline are specific for h264 or h265, the correct version will be used in each pipeline.
- A wired Client-Server ethernet connection is preferred over Wifi for 4K video, and might be required by the client. Only recent Apple devices
- (M1/M2 Macs or iPads, and some iPhones) can send h265 video if a resolution "-s wxh" with h > 1080 is requested.
- The "-h265" option changes the default resolution ("-s" option) from 1920x1080 to 3840x2160, and leaves default maximum
- framerate ("-fps" option) at 30fps.
-
-**-hls** Activate HTTP Live Streaming support. With this option YouTube videos can be streamed directly from
- YouTube servers to UxPlay (without passing through the client)
- by clicking on the AirPlay icon in the YouTube app.
-
-**-pin [nnnn]**: (since v1.67) use Apple-style (one-time) "pin" authentication when a new client connects for the first time: a four-digit pin code is
- displayed on the terminal, and the client screen shows a login prompt for this to be entered. When "-pin" is used by itself, a new random
- pin code is chosen for each authentication; if "-pin nnnn" (e.g., "-pin 3939") is used, this will set an unchanging fixed code. Authentication adds the server to the client's list of
- "trusted servers" and the client will not need to reauthenticate provided that the client and server public keys remain unchanged. (By default since v1.68, the server public key is
- generated from the MAC address, which can be changed with the -m option; see the -key option for an alternative method of key
- generation). _(Add a line "pin" in the UxPlay startup file if you wish the UxPlay server to use the pin authentication protocol)._
-
-**-reg [_filename_]**: (since v1.68). If "-pin" is used, this option
- maintains a register of pin-authenticated "trusted clients" in $HOME/.uxplay.register (or optionally, in _filename_).
- Without this option, returning clients that skip pin-authentication are trusted and not checked. This option may be useful if UxPlay is used
- in a more public environment, to record client details; the register is text, one line per client, with client's public
- key (base-64 format), Device ID, and Device name; commenting out (with "#") or deleting a line deregisters the
- corresponding client (see options -restrict, -block, -allow for more ways to control client access). _(Add a line "reg" in the startup file if you wish to use this feature.)_
-
-**-vsync [x]** (In Mirror mode:) this option (**now the default**) uses timestamps to synchronize audio with video on the server,
- with an optional audio delay in (decimal) milliseconds (_x_ = "20.5" means 0.0205 seconds delay: positive or
- negative delays less than a second are allowed.) It is needed on low-power systems such as Raspberry Pi without hardware
- video decoding.
-
-**-vsync no** (In Mirror mode:) this switches off timestamp-based audio-video synchronization, restoring the default behavior prior to
-UxPlay-1.64. Standard desktop systems seem to work well without use of timestamps: this mode is appropriate for "live streaming" such as
-using UxPlay as a second monitor for a mac computer, or monitoring a webcam; with it, no video frames are dropped.
-
-**-async [x]** (In Audio-Only (ALAC) mode:) this option uses timestamps to synchronize audio on the server with video on the client,
- with an optional audio delay in (decimal) milliseconds (_x_ = "20.5" means 0.0205 seconds delay: positive or
- negative delays less than a second are allowed.) Because the client adds a video
- delay to account for latency, the server in -async mode adds an equivalent audio delay, which means that
- audio changes such as a pause or a track-change will not take effect
- immediately. _This might in principle be mitigated by using the `-al` audio latency setting to change the latency (default 0.25 secs)
- that the server reports to the client, but at present changing this does not seem to have any effect_.
-
-**-async no**. This is the still the default behavior in Audio-only mode, but this option may be useful as a command-line option to switch off a
-`-async` option set in a "uxplayrc" configuration file.
-
-**-db _low_[:_high_]** Rescales the AirPlay volume-control attenuation (gain) from -30dB:0dB to _low_:0dB or _low_:_high_. The lower limit _low_
- must be negative (attenuation); the upper limit _high_ can be either sign. (GStreamer restricts volume-augmentation by _high_ so that it
- cannot exceed +20dB).
- The rescaling is "flat", so that for -db -50:10, a change in Airplay attenuation by -7dB is translated to a -7 x (60/30) = -14dB attenuation,
- and the maximum volume (AirPlay 0dB) is a 10dB augmentation, and Airplay -30dB would become -50dB. Note that the minimum AirPlay value (-30dB exactly)
- is translated to "mute".
-
-**-taper** Provides a "tapered" Airplay volume-control profile (matching the one called "dasl-tapering"
- in [shairport-sync](https://github.com/mikebrady/shairport-sync)): each time the length of the
- volume slider (or the number of steps above mute, where 16 steps = full volume) is reduced by 50%, the perceived volume is halved (a 10dB attenuation).
- (This is modified at low volumes, to use the "untapered" volume if it is louder.)
-
-**-s wxh** e.g. -s 1920x1080 (= "1080p"), the default width and height resolutions in pixels for h264 video. (The default becomes
- 3840x2160 (= "4K") when the -h265 option is used.) This is just a
- request made to the AirPlay client, and perhaps will not
- be the final resolution you get. w and h are whole numbers with four
- digits or less. Note that the **height** pixel size is the controlling
- one used by the client for determining the streaming format; the width is
- dynamically adjusted to the shape of the image (portrait or landscape
- format, depending on how an iPad is held, for example).
-
-**-s wxh@r** As above, but also informs the AirPlay client about the screen
- refresh rate of the display. Default is r=60 (60 Hz); r must be a whole number
- less than 256.
-
-**-o** turns on an "overscanned" option for the display window. This
- reduces the image resolution by using some of the pixels requested
- by option -s wxh (or their default values 1920x1080) by adding an empty
- boundary frame of unused pixels (which would be lost in a full-screen
- display that overscans, and is not displayed by gstreamer).
- Recommendation: **don't use this option** unless there is some special
- reason to use it.
-
-**-fs** uses fullscreen mode, but only works with X11, Wayland, VAAPI, and D3D11 (Windows).
-
-**-p** allows you to select the network ports used by UxPlay (these need
- to be opened if the server is behind a firewall). By itself, -p sets
- "legacy" ports TCP 7100, 7000, 7001, UDP 6000, 6001, 7011. -p n (e.g. -p
- 35000) sets TCP and UDP ports n, n+1, n+2. -p n1,n2,n3 (comma-separated
- values) sets each port separately; -p n1,n2 sets ports n1,n2,n2+1. -p tcp n
- or -p udp n sets just the TCP or UDP ports. Ports must be in the range
- [1024-65535].
-
-If the -p option is not used, the ports are chosen dynamically (randomly),
-which will not work if a firewall is running.
-
-**-avdec** forces use of software h264 decoding using Gstreamer element avdec_h264 (libav h264 decoder). This
- option should prevent autovideosink choosing a hardware-accelerated videosink plugin such as vaapisink.
-
-**-vp _parser_** choses the GStreamer pipeline's h264 parser element, default is h264parse. Using
- quotes "..." allows options to be added.
-
-**-vd _decoder_** chooses the GStreamer pipeline's h264 decoder element, instead of the default value
- "decodebin" which chooses it for you. Software decoding is done by avdec_h264; various hardware decoders
- include: vaapih264dec, nvdec, nvh264dec, v4l2h264dec (these require that the appropriate hardware is
- available). Using quotes "..." allows some parameters to be included with the decoder name.
-
-**-vc _converter_** chooses the GStreamer pipeline's videoconverter element, instead of the default
- value "videoconvert". When using Video4Linux2 hardware-decoding by a GPU,`-vc v4l2convert` will also use
- the GPU for video conversion. Using quotes "..." allows some parameters to be included with the converter name.
-
-**-vs _videosink_** chooses the GStreamer videosink, instead of the default value
- "autovideosink" which chooses it for you. Some videosink choices are: ximagesink, xvimagesink,
- vaapisink (for intel graphics), gtksink, glimagesink, waylandsink, osxvideosink (for macOS), kmssink (for
- systems without X11, like Raspberry Pi OS lite) or
- fpsdisplaysink (which shows the streaming framerate in fps). Using quotes
- "..." allows some parameters to be included with the videosink name.
- For example, **fullscreen** mode is supported by the vaapisink plugin, and is
- obtained using ``-vs "vaapisink fullscreen=true"``; this also works with ``waylandsink``.
- The syntax of such options is specific to a given plugin (see GStreamer documentation), and some choices of videosink
- might not work on your system.
-
-**-vs 0** suppresses display of streamed video. In mirror mode, the client's screen
- is still mirrored at a reduced rate of 1 frame per second, but is not rendered or displayed. This
- option should always be used if the server is "headless" (with no attached screen to display video),
- and only used to render audio, which will be AAC lossily-compressed audio in mirror mode with unrendered video, and
- superior-quality ALAC Apple Lossless audio in Airplay audio-only mode.
-
-**-v4l2** Video settings for hardware h264 video decoding in the GPU by Video4Linux2. Equivalent to
- `-vd v4l2h264dec -vc v4l2convert`.
-
-**-bt709** A workaround for the failure of the older Video4Linux2 plugin to recognize Apple's
- use of an uncommon (but permitted) "full-range color" variant of the bt709 color standard for digital TV.
- This is no longer needed by GStreamer-1.20.4 and backports from it.
-
-**-rpi** Equivalent to "-v4l2 " (Not valid for Raspberry Pi model 5, and removed in UxPlay 1.67)
-
-**-rpigl** Equivalent to "-rpi -vs glimagesink". (Removed since UxPlay 1.67)
+- These can also be written (one option per line, without the initial
+ "`-`" character) in the UxPlay startup file (either given by
+ environment variable `$UXPLAYRC`, or `~/.uxplayrc` or
+ `~/.config/uxplayrc`); lines begining with "`#`" are treated as
+ comments, and ignored. Command line options supersede options in the
+ startup file.
+
+**-n server_name** (Default: UxPlay); server_name@\_hostname\_ will be
+the name that appears offering AirPlay services to your iPad, iPhone
+etc, where *hostname* is the name of the server running uxplay. This
+will also now be the name shown above the mirror display (X11) window.
+
+**-nh** Do not append "@_hostname_" at the end of the AirPlay server
+name.
+
+**-h265** Activate "ScreenMultiCodec" support (AirPlay "Features" bit
+42) for accepting h265 (4K/HEVC) video in addition to h264 video (1080p)
+in screen-mirror mode. When this option is used, two "video pipelines"
+(one for h264, one for h265) are created. If any GStreamer plugins in
+the pipeline are specific for h264 or h265, the correct version will be
+used in each pipeline. A wired Client-Server ethernet connection is
+preferred over Wifi for 4K video, and might be required by the client.
+Only recent Apple devices (M1/M2 Macs or iPads, and some iPhones) can
+send h265 video if a resolution "-s wxh" with h \> 1080 is requested.
+The "-h265" option changes the default resolution ("-s" option) from
+1920x1080 to 3840x2160, and leaves default maximum framerate ("-fps"
+option) at 30fps.
+
+**-hls** Activate HTTP Live Streaming support. With this option YouTube
+videos can be streamed directly from YouTube servers to UxPlay (without
+passing through the client) by clicking on the AirPlay icon in the
+YouTube app.
+
+**-pin \[nnnn\]**: (since v1.67) use Apple-style (one-time) "pin"
+authentication when a new client connects for the first time: a
+four-digit pin code is displayed on the terminal, and the client screen
+shows a login prompt for this to be entered. When "-pin" is used by
+itself, a new random pin code is chosen for each authentication; if
+"-pin nnnn" (e.g., "-pin 3939") is used, this will set an unchanging
+fixed code. Authentication adds the server to the client's list of
+"trusted servers" and the client will not need to reauthenticate
+provided that the client and server public keys remain unchanged. (By
+default since v1.68, the server public key is generated from the MAC
+address, which can be changed with the -m option; see the -key option
+for an alternative method of key generation). *(Add a line "pin" in the
+UxPlay startup file if you wish the UxPlay server to use the pin
+authentication protocol).*
+
+**-reg \[*filename*\]**: (since v1.68). If "-pin" is used, this option
+maintains a register of pin-authenticated "trusted clients" in
+\$HOME/.uxplay.register (or optionally, in *filename*). Without this
+option, returning clients that skip pin-authentication are trusted and
+not checked. This option may be useful if UxPlay is used in a more
+public environment, to record client details; the register is text, one
+line per client, with client's public key (base-64 format), Device ID,
+and Device name; commenting out (with "\#") or deleting a line
+deregisters the corresponding client (see options -restrict, -block,
+-allow for more ways to control client access). *(Add a line "reg" in
+the startup file if you wish to use this feature.)*
+
+**-vsync \[x\]** (In Mirror mode:) this option (**now the default**)
+uses timestamps to synchronize audio with video on the server, with an
+optional audio delay in (decimal) milliseconds (*x* = "20.5" means
+0.0205 seconds delay: positive or negative delays less than a second are
+allowed.) It is needed on low-power systems such as Raspberry Pi without
+hardware video decoding.
+
+**-vsync no** (In Mirror mode:) this switches off timestamp-based
+audio-video synchronization, restoring the default behavior prior to
+UxPlay-1.64. Standard desktop systems seem to work well without use of
+timestamps: this mode is appropriate for "live streaming" such as using
+UxPlay as a second monitor for a mac computer, or monitoring a webcam;
+with it, no video frames are dropped.
+
+**-async \[x\]** (In Audio-Only (ALAC) mode:) this option uses
+timestamps to synchronize audio on the server with video on the client,
+with an optional audio delay in (decimal) milliseconds (*x* = "20.5"
+means 0.0205 seconds delay: positive or negative delays less than a
+second are allowed.) Because the client adds a video delay to account
+for latency, the server in -async mode adds an equivalent audio delay,
+which means that audio changes such as a pause or a track-change will
+not take effect immediately. *This might in principle be mitigated by
+using the `-al` audio latency setting to change the latency (default
+0.25 secs) that the server reports to the client, but at present
+changing this does not seem to have any effect*.
+
+**-async no**. This is the still the default behavior in Audio-only
+mode, but this option may be useful as a command-line option to switch
+off a `-async` option set in a "uxplayrc" configuration file.
+
+**-db *low*\[:*high*\]** Rescales the AirPlay volume-control attenuation
+(gain) from -30dB:0dB to *low*:0dB or *low*:*high*. The lower limit
+*low* must be negative (attenuation); the upper limit *high* can be
+either sign. (GStreamer restricts volume-augmentation by *high* so that
+it cannot exceed +20dB). The rescaling is "flat", so that for -db
+-50:10, a change in Airplay attenuation by -7dB is translated to a -7 x
+(60/30) = -14dB attenuation, and the maximum volume (AirPlay 0dB) is a
+10dB augmentation, and Airplay -30dB would become -50dB. Note that the
+minimum AirPlay value (-30dB exactly) is translated to "mute".
+
+**-taper** Provides a "tapered" Airplay volume-control profile (matching
+the one called "dasl-tapering" in
+[shairport-sync](https://github.com/mikebrady/shairport-sync)): each
+time the length of the volume slider (or the number of steps above mute,
+where 16 steps = full volume) is reduced by 50%, the perceived volume is
+halved (a 10dB attenuation). (This is modified at low volumes, to use
+the "untapered" volume if it is louder.)
+
+**-s wxh** e.g. -s 1920x1080 (= "1080p"), the default width and height
+resolutions in pixels for h264 video. (The default becomes 3840x2160 (=
+"4K") when the -h265 option is used.) This is just a request made to the
+AirPlay client, and perhaps will not be the final resolution you get. w
+and h are whole numbers with four digits or less. Note that the
+**height** pixel size is the controlling one used by the client for
+determining the streaming format; the width is dynamically adjusted to
+the shape of the image (portrait or landscape format, depending on how
+an iPad is held, for example).
+
+**-s wxh@r** As above, but also informs the AirPlay client about the
+screen refresh rate of the display. Default is r=60 (60 Hz); r must be a
+whole number less than 256.
+
+**-o** turns on an "overscanned" option for the display window. This
+reduces the image resolution by using some of the pixels requested by
+option -s wxh (or their default values 1920x1080) by adding an empty
+boundary frame of unused pixels (which would be lost in a full-screen
+display that overscans, and is not displayed by gstreamer).
+Recommendation: **don't use this option** unless there is some special
+reason to use it.
+
+**-fs** uses fullscreen mode, but only works with X11, Wayland, VAAPI,
+and D3D11 (Windows).
+
+**-p** allows you to select the network ports used by UxPlay (these need
+to be opened if the server is behind a firewall). By itself, -p sets
+"legacy" ports TCP 7100, 7000, 7001, UDP 6000, 6001, 7011. -p n (e.g. -p
+35000) sets TCP and UDP ports n, n+1, n+2. -p n1,n2,n3 (comma-separated
+values) sets each port separately; -p n1,n2 sets ports n1,n2,n2+1. -p
+tcp n or -p udp n sets just the TCP or UDP ports. Ports must be in the
+range \[1024-65535\].
+
+If the -p option is not used, the ports are chosen dynamically
+(randomly), which will not work if a firewall is running.
+
+**-avdec** forces use of software h264 decoding using Gstreamer element
+avdec_h264 (libav h264 decoder). This option should prevent
+autovideosink choosing a hardware-accelerated videosink plugin such as
+vaapisink.
+
+**-vp *parser*** choses the GStreamer pipeline's h264 parser element,
+default is h264parse. Using quotes "..." allows options to be added.
+
+**-vd *decoder*** chooses the GStreamer pipeline's h264 decoder element,
+instead of the default value "decodebin" which chooses it for you.
+Software decoding is done by avdec_h264; various hardware decoders
+include: vaapih264dec, nvdec, nvh264dec, v4l2h264dec (these require that
+the appropriate hardware is available). Using quotes "..." allows some
+parameters to be included with the decoder name.
+
+**-vc *converter*** chooses the GStreamer pipeline's videoconverter
+element, instead of the default value "videoconvert". When using
+Video4Linux2 hardware-decoding by a GPU,`-vc v4l2convert` will also use
+the GPU for video conversion. Using quotes "..." allows some parameters
+to be included with the converter name.
+
+**-vs *videosink*** chooses the GStreamer videosink, instead of the
+default value "autovideosink" which chooses it for you. Some videosink
+choices are: ximagesink, xvimagesink, vaapisink (for intel graphics),
+gtksink, glimagesink, waylandsink, osxvideosink (for macOS), kmssink
+(for systems without X11, like Raspberry Pi OS lite) or fpsdisplaysink
+(which shows the streaming framerate in fps). Using quotes "..." allows
+some parameters to be included with the videosink name. For example,
+**fullscreen** mode is supported by the vaapisink plugin, and is
+obtained using `-vs "vaapisink fullscreen=true"`; this also works with
+`waylandsink`. The syntax of such options is specific to a given plugin
+(see GStreamer documentation), and some choices of videosink might not
+work on your system.
+
+**-vs 0** suppresses display of streamed video. In mirror mode, the
+client's screen is still mirrored at a reduced rate of 1 frame per
+second, but is not rendered or displayed. This option should always be
+used if the server is "headless" (with no attached screen to display
+video), and only used to render audio, which will be AAC
+lossily-compressed audio in mirror mode with unrendered video, and
+superior-quality ALAC Apple Lossless audio in Airplay audio-only mode.
+
+**-v4l2** Video settings for hardware h264 video decoding in the GPU by
+Video4Linux2. Equivalent to `-vd v4l2h264dec -vc v4l2convert`.
+
+**-bt709** A workaround for the failure of the older Video4Linux2 plugin
+to recognize Apple's use of an uncommon (but permitted) "full-range
+color" variant of the bt709 color standard for digital TV. This is no
+longer needed by GStreamer-1.20.4 and backports from it.
+
+**-rpi** Equivalent to "-v4l2" (Not valid for Raspberry Pi model 5, and
+removed in UxPlay 1.67)
+
+**-rpigl** Equivalent to "-rpi -vs glimagesink". (Removed since UxPlay
+1.67)
**-rpifb** Equivalent to "-rpi -vs kmssink" (Removed since UxPlay 1.67)
-**-rpiwl** Equivalent to "-rpi -vs waylandsink". (Removed since UxPlay 1.67)
-
-**-as _audiosink_** chooses the GStreamer audiosink, instead of letting
- autoaudiosink pick it for you. Some audiosink choices are: pulsesink, alsasink, pipewiresink,
- osssink, oss4sink, jackaudiosink, osxaudiosink (for macOS), wasapisink, directsoundsink (for Windows).
- Using quotes "..." might allow some optional parameters (e.g. `-as "alsasink device=..."` to specify a non-default output device).
- The syntax of such options is specific to a given plugin (see GStreamer documentation), and some choices of audiosink
- might not work on your system.
-
-**-as 0** (or just **-a**) suppresses playing of streamed audio, but displays streamed video.
-
-**-al _x_** specifies an audio latency _x_ in (decimal) seconds in Audio-only (ALAC), that is reported to the client. Values
- in the range [0.0, 10.0] seconds are allowed, and will be converted to a whole number of microseconds. Default
- is 0.25 sec (250000 usec). _(However, the client appears to ignore this reported latency, so this option seems non-functional.)_
-
-**-ca _filename_** provides a file (where _filename_ can include a full path) used for output of "cover art"
- (from Apple Music, _etc._,) in audio-only ALAC mode. This file is overwritten with the latest cover art as
- it arrives. Cover art (jpeg format) is discarded if this option is not used. Use with a image viewer that reloads the image
- if it changes, or regularly (_e.g._ once per second.). To achieve this, run "`uxplay -ca [path/to/]filename &`" in the background,
- then run the the image viewer in the foreground. Example, using `feh` as the viewer: run "``feh -R 1 [path/to/]filename``" (in
- the same terminal window in which uxplay was put into the background). To quit, use ```ctrl-C fg ctrl-C``` to terminate
- the image viewer, bring ``uxplay`` into the foreground, and terminate it too.
-
-**-reset n** sets a limit of _n_ consecutive timeout failures of the client to respond to ntp requests
- from the server (these are sent every 3 seconds to check if the client is still present, and synchronize with it). After
- _n_ failures, the client will be presumed to be offline, and the connection will be reset to allow a new
- connection. The default value of _n_ is 5; the value _n_ = 0 means "no limit" on timeouts.
-
-**-nofreeze** closes the video window after a reset due to ntp timeout (default is to leave window
- open to allow a smoother reconection to the same client). This option may be useful in fullscreen mode.
-
-**-nc** maintains previous UxPlay < 1.45 behavior that does **not close** the video window when the the client
- sends the "Stop Mirroring" signal. _This option is currently used by default in macOS,
- as the window created in macOS by GStreamer does not terminate correctly (it causes a segfault)
- if it is still open when the GStreamer pipeline is closed._
-
-**-nohold** Drops the current connection when a new client attempts to connect. Without this option,
- the current client maintains exclusive ownership of UxPlay until it disconnects.
-
-**-restrict** Restrict clients allowed to connect to those specified by `-allow `. The deviceID has the
- form of a MAC address which is displayed by UxPlay when the client attempts to connect, and appears to be immutable. It
- has the format `XX:XX:XX:XX:XX:XX`, X = 0-9,A-F, and is possibly the "true" hardware
- MAC address of the device. Note that iOS clients generally expose different random "private Wi_Fi addresses" ("fake" MAC addresses) to
- different networks (for privacy reasons, to prevent tracking), which may change, and do not correpond to the deviceID.
-
-**-restrict no** Remove restrictions (default). This is useful as a command-line argument to overide restrictions set
- in the Startup file.
-
-**-allow _id_** Adds the deviceID = _id_ to the list of allowed clients when client restrictions
- are being enforced. Usually this will be an entry in the uxplayrc startup file.
-
-**-block _id_** Always block clients with deviceID = _id_, even when client restrictions are not
- being enforced generally. Usually this will be an entry in the uxplayrc startup file.
-
-**-FPSdata** Turns on monitoring of regular reports about video streaming performance
- that are sent by the client. These will be displayed in the terminal window if this
- option is used. The data is updated by the client at 1 second intervals.
-
-**-fps n** sets a maximum frame rate (in frames per second) for the AirPlay
- client to stream video; n must be a whole number less than 256.
- (The client may choose to serve video at any frame rate lower
- than this; default is 30 fps.) A setting of 60 fps may give you improved video
- but is not recommended on Raspberry Pi. A setting below 30 fps might be useful to
- reduce latency if you are running more than one instance of uxplay at the same time.
- _This setting is only an advisory to
- the client device, so setting a high value will not force a high framerate._
- (You can test using "-vs fpsdisplaysink" to see what framerate is being
- received, or use the option -FPSdata which displays video-stream performance data
- continuously sent by the client during video-streaming.)
-
-**-f {H|V|I}** implements "videoflip" image transforms: H = horizontal flip
- (right-left flip, or mirror image); V = vertical flip ; I =
- 180 degree rotation or inversion (which is the combination of H with V).
-
-**-r {R|L}** 90 degree Right (clockwise) or Left (counter-clockwise)
- rotations; these image transforms are carried out after any **-f** transforms.
-
-**-m [mac]** changes the MAC address (Device ID) used by UxPlay (default is to use
- the true hardware MAC address reported by the host computer's network card).
- (Different server_name, MAC
- addresses, and network ports are needed for each running uxplay if you
- attempt to run more than one instance of uxplay on the same computer.)
- If [mac] (in form xx:xx:xx:xx:xx:xx, 6 hex octets) is not given, a random
- MAC address is generated.
- If UxPlay fails to find the true MAC address of a network card, (more
- specifically, the MAC address used by the first active network interface detected)
- a random MAC address will be used even if option **-m** was not specified.
- (Note that a random MAC address will be different each time UxPlay is started).
-
-**-key [_filename_]**: This (more secure) option for generating and storing a persistant public key (needed for
- the -pin option) has been replaced by default with a (less secure) method which generates a key from the server's "device ID"
- (MAC address, which can be changed with the -m option, conveniently as a startup file option).
- When the -key option is used, a securely generated keypair is generated and stored in `$HOME/.uxplay.pem`, if that file does not exist,
- or read from it, if it exists. (Optionally, the key can be stored in _filename_.) This method is more secure than the new default method,
- (because the Device ID is broadcast in the DNS_SD announcement) but still leaves the private key exposed to anyone who can access the pem file.
- This option should be set in the UxPlay startup file
- as a line "key" or "key _filename_" (no initial "-"), where _filename_ is a full path which should be enclosed
- in quotes (`"...."`) if it contains any blank spaces.
- **Because the default method is simpler, and security of client access to uxplay is unlikely to be an important issue,
- the -key option is no longer recommended**.
-
-**-dacp [_filename_]**: Export current client DACP-ID and Active-Remote key to file: default is $HOME/.uxplay.dacp.
- (optionally can be changed to _filename_). Can be used by remote control applications. File is transient: only exists
- while client is connected.
-
-**-vdmp** Dumps h264 video to file videodump.h264. -vdmp n dumps not more than n NAL units to
- videodump.x.h264; x= 1,2,... increases each time a SPS/PPS NAL unit arrives. To change the name
- _videodump_, use -vdmp [n] _filename_.
-
-**-admp** Dumps audio to file audiodump.x.aac (AAC-ELD format audio), audiodump.x.alac (ALAC format audio) or audiodump.x.aud
- (other-format audio), where x = 1,2,3... increases each time the audio format changes. -admp _n_ restricts the number of
- packets dumped to a file to _n_ or less. To change the name _audiodump_, use -admp [n] _filename_. _Note that (unlike dumped video)
- the dumped audio is currently only useful for debugging, as it is not containerized to make it playable with standard audio players._
-
-**-d** Enable debug output. Note: this does not show GStreamer error or debug messages. To see GStreamer error
- and warning messages, set the environment variable GST_DEBUG with "export GST_DEBUG=2" before running uxplay.
- To see GStreamer information messages, set GST_DEBUG=4; for DEBUG messages, GST_DEBUG=5; increase this to see even
- more of the GStreamer inner workings.
+**-rpiwl** Equivalent to "-rpi -vs waylandsink". (Removed since UxPlay
+1.67)
+
+**-as *audiosink*** chooses the GStreamer audiosink, instead of letting
+autoaudiosink pick it for you. Some audiosink choices are: pulsesink,
+alsasink, pipewiresink, osssink, oss4sink, jackaudiosink, osxaudiosink
+(for macOS), wasapisink, directsoundsink (for Windows). Using quotes
+"..." might allow some optional parameters
+(e.g. `-as "alsasink device=..."` to specify a non-default output
+device). The syntax of such options is specific to a given plugin (see
+GStreamer documentation), and some choices of audiosink might not work
+on your system.
+
+**-as 0** (or just **-a**) suppresses playing of streamed audio, but
+displays streamed video.
+
+**-al *x*** specifies an audio latency *x* in (decimal) seconds in
+Audio-only (ALAC), that is reported to the client. Values in the range
+\[0.0, 10.0\] seconds are allowed, and will be converted to a whole
+number of microseconds. Default is 0.25 sec (250000 usec). *(However,
+the client appears to ignore this reported latency, so this option seems
+non-functional.)*
+
+**-ca *filename*** provides a file (where *filename* can include a full
+path) used for output of "cover art" (from Apple Music, *etc.*,) in
+audio-only ALAC mode. This file is overwritten with the latest cover art
+as it arrives. Cover art (jpeg format) is discarded if this option is
+not used. Use with a image viewer that reloads the image if it changes,
+or regularly (*e.g.* once per second.). To achieve this, run
+"`uxplay -ca [path/to/]filename &`" in the background, then run the the
+image viewer in the foreground. Example, using `feh` as the viewer: run
+"`feh -R 1 [path/to/]filename`" (in the same terminal window in which
+uxplay was put into the background). To quit, use `ctrl-C fg ctrl-C` to
+terminate the image viewer, bring `uxplay` into the foreground, and
+terminate it too.
+
+**-reset n** sets a limit of *n* consecutive timeout failures of the
+client to respond to ntp requests from the server (these are sent every
+3 seconds to check if the client is still present, and synchronize with
+it). After *n* failures, the client will be presumed to be offline, and
+the connection will be reset to allow a new connection. The default
+value of *n* is 5; the value *n* = 0 means "no limit" on timeouts.
+
+**-nofreeze** closes the video window after a reset due to ntp timeout
+(default is to leave window open to allow a smoother reconection to the
+same client). This option may be useful in fullscreen mode.
+
+**-nc** maintains previous UxPlay \< 1.45 behavior that does **not
+close** the video window when the the client sends the "Stop Mirroring"
+signal. *This option is currently used by default in macOS, as the
+window created in macOS by GStreamer does not terminate correctly (it
+causes a segfault) if it is still open when the GStreamer pipeline is
+closed.*
+
+**-nohold** Drops the current connection when a new client attempts to
+connect. Without this option, the current client maintains exclusive
+ownership of UxPlay until it disconnects.
+
+**-restrict** Restrict clients allowed to connect to those specified by
+`-allow `. The deviceID has the form of a MAC address which is
+displayed by UxPlay when the client attempts to connect, and appears to
+be immutable. It has the format `XX:XX:XX:XX:XX:XX`, X = 0-9,A-F, and is
+possibly the "true" hardware MAC address of the device. Note that iOS
+clients generally expose different random "private Wi_Fi addresses"
+("fake" MAC addresses) to different networks (for privacy reasons, to
+prevent tracking), which may change, and do not correpond to the
+deviceID.
+
+**-restrict no** Remove restrictions (default). This is useful as a
+command-line argument to overide restrictions set in the Startup file.
+
+**-allow *id*** Adds the deviceID = *id* to the list of allowed clients
+when client restrictions are being enforced. Usually this will be an
+entry in the uxplayrc startup file.
+
+**-block *id*** Always block clients with deviceID = *id*, even when
+client restrictions are not being enforced generally. Usually this will
+be an entry in the uxplayrc startup file.
+
+**-FPSdata** Turns on monitoring of regular reports about video
+streaming performance that are sent by the client. These will be
+displayed in the terminal window if this option is used. The data is
+updated by the client at 1 second intervals.
+
+**-fps n** sets a maximum frame rate (in frames per second) for the
+AirPlay client to stream video; n must be a whole number less than 256.
+(The client may choose to serve video at any frame rate lower than this;
+default is 30 fps.) A setting of 60 fps may give you improved video but
+is not recommended on Raspberry Pi. A setting below 30 fps might be
+useful to reduce latency if you are running more than one instance of
+uxplay at the same time. *This setting is only an advisory to the client
+device, so setting a high value will not force a high framerate.* (You
+can test using "-vs fpsdisplaysink" to see what framerate is being
+received, or use the option -FPSdata which displays video-stream
+performance data continuously sent by the client during
+video-streaming.)
+
+**-f {H\|V\|I}** implements "videoflip" image transforms: H = horizontal
+flip (right-left flip, or mirror image); V = vertical flip ; I = 180
+degree rotation or inversion (which is the combination of H with V).
+
+**-r {R\|L}** 90 degree Right (clockwise) or Left (counter-clockwise)
+rotations; these image transforms are carried out after any **-f**
+transforms.
+
+**-m \[mac\]** changes the MAC address (Device ID) used by UxPlay
+(default is to use the true hardware MAC address reported by the host
+computer's network card). (Different server_name, MAC addresses, and
+network ports are needed for each running uxplay if you attempt to run
+more than one instance of uxplay on the same computer.) If \[mac\] (in
+form xx:xx:xx:xx:xx:xx, 6 hex octets) is not given, a random MAC address
+is generated. If UxPlay fails to find the true MAC address of a network
+card, (more specifically, the MAC address used by the first active
+network interface detected) a random MAC address will be used even if
+option **-m** was not specified. (Note that a random MAC address will be
+different each time UxPlay is started).
+
+**-key \[*filename*\]**: This (more secure) option for generating and
+storing a persistant public key (needed for the -pin option) has been
+replaced by default with a (less secure) method which generates a key
+from the server's "device ID" (MAC address, which can be changed with
+the -m option, conveniently as a startup file option). When the -key
+option is used, a securely generated keypair is generated and stored in
+`$HOME/.uxplay.pem`, if that file does not exist, or read from it, if it
+exists. (Optionally, the key can be stored in *filename*.) This method
+is more secure than the new default method, (because the Device ID is
+broadcast in the DNS_SD announcement) but still leaves the private key
+exposed to anyone who can access the pem file. This option should be set
+in the UxPlay startup file as a line "key" or "key *filename*" (no
+initial "-"), where *filename* is a full path which should be enclosed
+in quotes (`"...."`) if it contains any blank spaces. **Because the
+default method is simpler, and security of client access to uxplay is
+unlikely to be an important issue, the -key option is no longer
+recommended**.
+
+**-dacp \[*filename*\]**: Export current client DACP-ID and
+Active-Remote key to file: default is \$HOME/.uxplay.dacp. (optionally
+can be changed to *filename*). Can be used by remote control
+applications. File is transient: only exists while client is connected.
+
+**-vdmp** Dumps h264 video to file videodump.h264. -vdmp n dumps not
+more than n NAL units to videodump.x.h264; x= 1,2,... increases each
+time a SPS/PPS NAL unit arrives. To change the name *videodump*, use
+-vdmp \[n\] *filename*.
+
+**-admp** Dumps audio to file audiodump.x.aac (AAC-ELD format audio),
+audiodump.x.alac (ALAC format audio) or audiodump.x.aud (other-format
+audio), where x = 1,2,3... increases each time the audio format changes.
+-admp *n* restricts the number of packets dumped to a file to *n* or
+less. To change the name *audiodump*, use -admp \[n\] *filename*. *Note
+that (unlike dumped video) the dumped audio is currently only useful for
+debugging, as it is not containerized to make it playable with standard
+audio players.*
+
+**-d** Enable debug output. Note: this does not show GStreamer error or
+debug messages. To see GStreamer error and warning messages, set the
+environment variable GST_DEBUG with "export GST_DEBUG=2" before running
+uxplay. To see GStreamer information messages, set GST_DEBUG=4; for
+DEBUG messages, GST_DEBUG=5; increase this to see even more of the
+GStreamer inner workings.
# Troubleshooting
-Note: ```uxplay``` is run from a terminal command line, and informational messages are written to the terminal.
-
-### 0. Problems in compiling UxPlay.
-
-One user (on Ubuntu) found compilation failed with messages about linking to "usr/local/lib/libcrypto.a" and "zlib".
-This was because (in addition to the standard ubuntu installation of libssl-dev), the user was unaware that a second installation
-with libcrypto in /usr/local was present.
-Solution: when more than one installation of OpenSSL is present, set the environment variable OPEN_SSL_ROOT_DIR to point to the correct one;
-on 64-bit Ubuntu, this is done by
-running `export OPENSSL_ROOT_DIR=/usr/lib/X86_64-linux-gnu/` before running cmake.
-
-### 1. **Avahi/DNS_SD Bonjour/Zeroconf issues**
-
-The DNS_SD Service-Discovery ("Bonjour" or "Zeroconf") service is required for UxPlay to work. On Linux, it will be usually provided by Avahi, and to troubleshoot this, you
-should use the tool `avahi-browse`. (You may need to install a separate package with a name like ``avahi-utils`` to get this.)
-
-On Linux, make sure Avahi is installed,
-and start the avahi-daemon service on the system running uxplay (your distribution will document how to do this, for example:
-`sudo systemctl avahi-daemon` or ``sudo service avahi-daemon ``, with
-```` one of enable, disable, start, stop, status.
-You might need to edit the avahi-daemon.conf file (it is typically in /etc/avahi/, find it with "`sudo find /etc -name avahi-daemon.conf`"):
-make sure that "disable-publishing" is **not** a selected option).
-Some systems may instead use the mdnsd daemon as an alternative to provide DNS-SD service.
-(FreeBSD offers both alternatives, but only Avahi was tested; see [here](https://gist.github.com/reidransom/6033227).)
-
-* **uxplay starts, but either stalls or stops after "Initialized server socket(s)" appears (_without the server name showing on the client_)**.
-
-If UxPlay stops with the "No DNS-SD Server found" message, this means that your network **does not have a running Bonjour/zeroconf DNS-SD server.**
-Before v1.60, UxPlay used to stall silently if DNS-SD service registration failed, but now stops with an error message returned by the
-DNSServiceRegister function: kDNSServiceErr_Unknown if no DNS-SD server was found:
-_(A NixOS user found that in NixOS, this error can also occur if avahi-daemon service IS running with publishing enabled, but
-reports "the error disappeared on NixOS by setting services.avahi.openFirewall to true".)_
-Other mDNS error codes are in the range FFFE FF00 (-65792) to FFFE FFFF (-65537), and are listed in the
-dnssd.h file. An older version of this (the one used by avahi) is found [here](https://github.com/lathiat/avahi/blob/master/avahi-compat-libdns_sd/dns_sd.h).
-A few additional error codes are defined in a later version
-from [Apple](https://opensource.apple.com/source/mDNSResponder/mDNSResponder-544/mDNSShared/dns_sd.h.auto.html).
-
+Note: `uxplay` is run from a terminal command line, and informational
+messages are written to the terminal.
-If UxPlay stalls _without an error message_ and _without the server name showing on the client_, **this is a network problem** (if your UxPlay version
-is older than 1.60, it is also the behavior when no DNS-SD server is found.)
+### 0. Problems in compiling UxPlay.
-A useful tool for examining such network problems from the client end is the (free) Discovery DNS-SD
-browser [available in the Apple App Store](https://apps.apple.com/us/developer/lily-ballard/id305441020) for both iOS (works on iPadOS too) and macOS.
+One user (on Ubuntu) found compilation failed with messages about
+linking to "usr/local/lib/libcrypto.a" and "zlib". This was because (in
+addition to the standard ubuntu installation of libssl-dev), the user
+was unaware that a second installation with libcrypto in /usr/local was
+present. Solution: when more than one installation of OpenSSL is
+present, set the environment variable OPEN_SSL_ROOT_DIR to point to the
+correct one; on 64-bit Ubuntu, this is done by running
+`export OPENSSL_ROOT_DIR=/usr/lib/X86_64-linux-gnu/` before running
+cmake.
-* Some users using dual-band (2.4GHz/5GHz) routers have reported that clients using the 5GHz band (sometimes) "fail to see
-UxPlay" (i.e., do not get a response to their mDNS queries), but the 2.4GHz band works. Other projects using Bonjour/mDNS have had similar reports;
-the issue seems to be router-specific, perhaps related to "auto" rather than fixed channel selection (5GHz has many more
-channels to switch between), or channel width selections; one speculation is that since mDNS uses UDP protocol (where
-"lost" messages are not resent), a mDNS query might get lost if channel switching occurs during the query.
-
-If your router has this problem, a reported "fix" is to (at least on 5GHz) use fixed channel and/or fixed (not dynamic) channel width.
-
-* **Avahi works at first, but new clients do not see UxPlay, or clients that initially saw it stop doing so after they disconnect**.
-
-This is usually because Avahi is only using the "loopback" network interface, and is not receiving mDNS queries from new clients that were not
-listening when UxPlay started.
-
-To check this, after starting uxplay, use the utility ``avahi-browse -a -t`` **in a different terminal window** on the server to
-verify that the UxPlay AirTunes and AirPlay services are correctly registered (only the AirTunes service is
-used in the "Legacy" AirPlay Mirror mode used by UxPlay, but the AirPlay service is used for the initial contact).
-
-The results returned by avahi-browse should show entries for
-uxplay like
-
-
-```
-+ eno1 IPv6 UxPlay AirPlay Remote Video local
-+ eno1 IPv4 UxPlay AirPlay Remote Video local
-+ lo IPv4 UxPlay AirPlay Remote Video local
-+ eno1 IPv6 863EA27598FE@UxPlay AirTunes Remote Audio local
-+ eno1 IPv4 863EA27598FE@UxPlay AirTunes Remote Audio local
-+ lo IPv4 863EA27598FE@UxPlay AirTunes Remote Audio local
-
-```
-If only the loopback ("lo") entries are shown, a firewall on the UxPlay host
-is probably blocking full DNS-SD service, and you need to open the default UDP port 5353 for mDNS requests,
-as loopback-based DNS-SD service is unreliable.
-
-If the UxPlay services are listed by avahi-browse as above, but are not seen by the client,
-the problem is likely to be a problem with the local network.
+### 1. **Avahi/DNS_SD Bonjour/Zeroconf issues**
+The DNS_SD Service-Discovery ("Bonjour" or "Zeroconf") service is
+required for UxPlay to work. On Linux, it will be usually provided by
+Avahi, and to troubleshoot this, you should use the tool `avahi-browse`.
+(You may need to install a separate package with a name like
+`avahi-utils` to get this.)
+
+On Linux, make sure Avahi is installed, and start the avahi-daemon
+service on the system running uxplay (your distribution will document
+how to do this, for example: `sudo systemctl avahi-daemon` or
+`sudo service avahi-daemon `, with `` one of enable, disable,
+start, stop, status. You might need to edit the avahi-daemon.conf file
+(it is typically in /etc/avahi/, find it with
+"`sudo find /etc -name avahi-daemon.conf`"): make sure that
+"disable-publishing" is **not** a selected option). Some systems may
+instead use the mdnsd daemon as an alternative to provide DNS-SD
+service. (FreeBSD offers both alternatives, but only Avahi was tested;
+see [here](https://gist.github.com/reidransom/6033227).)
+
+- **uxplay starts, but either stalls or stops after "Initialized
+ server socket(s)" appears (*without the server name showing on the
+ client*)**.
+
+If UxPlay stops with the "No DNS-SD Server found" message, this means
+that your network **does not have a running Bonjour/zeroconf DNS-SD
+server.** Before v1.60, UxPlay used to stall silently if DNS-SD service
+registration failed, but now stops with an error message returned by the
+DNSServiceRegister function: kDNSServiceErr_Unknown if no DNS-SD server
+was found: *(A NixOS user found that in NixOS, this error can also occur
+if avahi-daemon service IS running with publishing enabled, but reports
+"the error disappeared on NixOS by setting services.avahi.openFirewall
+to true".)* Other mDNS error codes are in the range FFFE FF00 (-65792)
+to FFFE FFFF (-65537), and are listed in the dnssd.h file. An older
+version of this (the one used by avahi) is found
+[here](https://github.com/lathiat/avahi/blob/master/avahi-compat-libdns_sd/dns_sd.h).
+A few additional error codes are defined in a later version from
+[Apple](https://opensource.apple.com/source/mDNSResponder/mDNSResponder-544/mDNSShared/dns_sd.h.auto.html).
+
+If UxPlay stalls *without an error message* and *without the server name
+showing on the client*, **this is a network problem** (if your UxPlay
+version is older than 1.60, it is also the behavior when no DNS-SD
+server is found.)
+
+A useful tool for examining such network problems from the client end is
+the (free) Discovery DNS-SD browser [available in the Apple App
+Store](https://apps.apple.com/us/developer/lily-ballard/id305441020) for
+both iOS (works on iPadOS too) and macOS.
+
+- Some users using dual-band (2.4GHz/5GHz) routers have reported that
+ clients using the 5GHz band (sometimes) "fail to see UxPlay" (i.e.,
+ do not get a response to their mDNS queries), but the 2.4GHz band
+ works. Other projects using Bonjour/mDNS have had similar reports;
+ the issue seems to be router-specific, perhaps related to "auto"
+ rather than fixed channel selection (5GHz has many more channels to
+ switch between), or channel width selections; one speculation is
+ that since mDNS uses UDP protocol (where "lost" messages are not
+ resent), a mDNS query might get lost if channel switching occurs
+ during the query.
+
+If your router has this problem, a reported "fix" is to (at least on
+5GHz) use fixed channel and/or fixed (not dynamic) channel width.
+
+- **Avahi works at first, but new clients do not see UxPlay, or
+ clients that initially saw it stop doing so after they disconnect**.
+
+This is usually because Avahi is only using the "loopback" network
+interface, and is not receiving mDNS queries from new clients that were
+not listening when UxPlay started.
+
+To check this, after starting uxplay, use the utility
+`avahi-browse -a -t` **in a different terminal window** on the server to
+verify that the UxPlay AirTunes and AirPlay services are correctly
+registered (only the AirTunes service is used in the "Legacy" AirPlay
+Mirror mode used by UxPlay, but the AirPlay service is used for the
+initial contact).
+
+The results returned by avahi-browse should show entries for uxplay like
+
+ + eno1 IPv6 UxPlay AirPlay Remote Video local
+ + eno1 IPv4 UxPlay AirPlay Remote Video local
+ + lo IPv4 UxPlay AirPlay Remote Video local
+ + eno1 IPv6 863EA27598FE@UxPlay AirTunes Remote Audio local
+ + eno1 IPv4 863EA27598FE@UxPlay AirTunes Remote Audio local
+ + lo IPv4 863EA27598FE@UxPlay AirTunes Remote Audio local
+
+If only the loopback ("lo") entries are shown, a firewall on the UxPlay
+host is probably blocking full DNS-SD service, and you need to open the
+default UDP port 5353 for mDNS requests, as loopback-based DNS-SD
+service is unreliable.
+
+If the UxPlay services are listed by avahi-browse as above, but are not
+seen by the client, the problem is likely to be a problem with the local
+network.
### 2. uxplay starts, but stalls after "Initialized server socket(s)" appears, *with the server name showing on the client* (but the client fails to connect when the UxPlay server is selected).
-This shows that a *DNS-SD* service is working, clients hear UxPlay is available, but the UxPlay server is not receiving the response from the client.
-This is usually because
-a firewall on the server is blocking the connection request from the client.
-(One user who insisted that the firewall had been turned off turned out to have had _two_ active firewalls (*firewalld* and *ufw*)
-_both_ running on the server!) If possible, either turn off the firewall
-to see if that is the problem, or get three consecutive network ports,
-starting at port n, all three in the range 1024-65535, opened for both tcp and udp, and use "uxplay -p n"
+This shows that a *DNS-SD* service is working, clients hear UxPlay is
+available, but the UxPlay server is not receiving the response from the
+client. This is usually because a firewall on the server is blocking the
+connection request from the client. (One user who insisted that the
+firewall had been turned off turned out to have had *two* active
+firewalls (*firewalld* and *ufw*) *both* running on the server!) If
+possible, either turn off the firewall to see if that is the problem, or
+get three consecutive network ports, starting at port n, all three in
+the range 1024-65535, opened for both tcp and udp, and use "uxplay -p n"
(or open UDP 7011,6001,6000 TCP 7100,7000,7001 and use "uxplay -p").
-If you are _really_ sure there is no firewall, you may need to investigate your network transmissions with a tool like netstat, but almost always this
-is a firewall issue.
-
-### 3. Problems _after_ the client-server connection has been made:
-
-If you do _not_ see the message ``raop_rtp_mirror starting mirroring``, something went wrong before the client-server negotiations were finished.
-For such problems, use "uxplay -d " (debug log option) to see what is happening: it will show how far the connection process gets before
-the failure occurs. You can compare your debug output to
-that from a successful start of UxPlay in the [UxPlay Wiki](https://github.com/FDH2/UxPlay/wiki).
-
-**If UxPlay reports that mirroring started, but you get no video or audio, the problem is probably from a
-GStreamer plugin that doesn't work on your system** (by default,
-GStreamer uses the "autovideosink" and "autoaudiosink" algorithms
-to guess what are the "best" plugins to use on your system).
-A different reason for no audio occurred when a user with a firewall only opened two udp network
-ports: **three** are required (the third one receives the audio data).
-
-**Raspberry Pi** devices (_Pi 4B+ and earlier: this does not apply to the Pi 5, which does not provide hardware h264 decoding, and does not
-need it_) work best with hardware GPU h264 video decoding if the Video4Linux2 plugin in GStreamer v1.20.x or earlier has
-been patched (see the UxPlay [Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches) for patches).
-This is fixed in GStreamer-1.22, and by backport patches from this in distributions such as Raspberry Pi OS (Bullseye): **use option `-bt709`
-with the GStreamer-1.18.4 from Raspberry Pi OS**.
-This also needs the bcm2835-codec kernel module that is not in the standard Linux kernel (it is available in Raspberry Pi OS, Ubuntu and Manjaro).
-
-* **If this kernel module is not available in your Raspberry Pi operating system, or if GStreamer < 1.22 is not patched, use option `-avdec`
-for software h264-decoding.**
-
-Sometimes "autovideosink" may select the OpenGL renderer "glimagesink" which
-may not work correctly on your system. Try the options "-vs ximagesink" or
-"-vs xvimagesink" to see if using one of these fixes the problem.
-
-Other reported problems are connected to the GStreamer VAAPI plugin
-(for hardware-accelerated Intel graphics, but not NVIDIA graphics).
-Use the option "-avdec"
-to force software h264 video decoding: this should prevent autovideosink from selecting the vaapisink videosink.
-Alternatively, find out if the
-gstreamer1.0-vaapi plugin is installed, and if so, uninstall it.
-(If this does not fix the problem, you can reinstall it.)
-
-There are some reports of other GStreamer problems with hardware-accelerated Intel HD graphics. One user
-(on Debian) solved this with "sudo apt install intel-media-va-driver-non-free". This is a driver for 8'th (or later) generation
-"*-lake" Intel chips, that seems to be related to VAAPI accelerated graphics.
-
-If you _do_ have Intel HD graphics, and have installed the vaapi plugin, but ``-vs vaapisink`` does not work, check that vaapi is not "blacklisted" in your GStreamer installation: run ``gst-inspect-1.0 vaapi``, if this reports ``0 features``, you need to ``export GST_VAAPI_ALL_DRIVERS=1`` before running uxplay, or set this in the default environment.
-
-You can try to fix audio or video problems by using the "`-as `" or "``-vs ``" options to choose the GStreamer audiosink or videosink , rather than
-letting GStreamer choose one for you. (See above, in [Starting and running UxPlay](#starting-and-running-uxplay) for choices of `` or ````.)
-
-The "OpenGL renderer" window created on Linux by "-vs glimagesink" sometimes does not close properly when its "close" button is clicked.
-(this is a GStreamer issue). You may need to terminate uxplay with Ctrl-C to close a "zombie" OpenGl window. If similar problems happen when
-the client sends the "Stop Mirroring" signal, try the no-close option "-nc" that leaves the video window open.
-
-### 4. GStreamer issues (missing plugins, etc.):
-
-* clearing the user's GStreamer cache with `rm -rf ~/.cache/gstreamer-1.0/*` may be the solution to problems
- where gst-inspect-1.0 does not show a plugin that you believe is installed. The cache will be regenerated next time
-GStreamer is started. **This is the solution to puzzling problems that turn out to come from corruption of the cache, and should be tried first.**
-
-If UxPlay fails to start, with a message that a required GStreamer plugin (such as "libav") was not found, first check with the GStreamer tool
-gst-inspect-1.0 to see what GStreamer knows is available. (You may need to install some additional GStreamer "tools" package to get gst-inspect-1.0).
-For, _e.g._ a libav problem, check with "`gst-inspect-1.0 libav`". If it is not shown as available to GStreamer, but your package manager
-shows the relevant package as installed (as one user found), try entirely removing and reinstalling the package.
-That user found that a solution to a "**Required gstreamer plugin 'libav' not found**" message that kept recurring was to clear the user's gstreamer
+If you are *really* sure there is no firewall, you may need to
+investigate your network transmissions with a tool like netstat, but
+almost always this is a firewall issue.
+
+### 3. Problems *after* the client-server connection has been made:
+
+If you do *not* see the message `raop_rtp_mirror starting mirroring`,
+something went wrong before the client-server negotiations were
+finished. For such problems, use "uxplay -d" (debug log option) to see
+what is happening: it will show how far the connection process gets
+before the failure occurs. You can compare your debug output to that
+from a successful start of UxPlay in the [UxPlay
+Wiki](https://github.com/FDH2/UxPlay/wiki).
+
+**If UxPlay reports that mirroring started, but you get no video or
+audio, the problem is probably from a GStreamer plugin that doesn't work
+on your system** (by default, GStreamer uses the "autovideosink" and
+"autoaudiosink" algorithms to guess what are the "best" plugins to use
+on your system). A different reason for no audio occurred when a user
+with a firewall only opened two udp network ports: **three** are
+required (the third one receives the audio data).
+
+**Raspberry Pi** devices (*Pi 4B+ and earlier: this does not apply to
+the Pi 5, which does not provide hardware h264 decoding, and does not
+need it*) work best with hardware GPU h264 video decoding if the
+Video4Linux2 plugin in GStreamer v1.20.x or earlier has been patched
+(see the UxPlay
+[Wiki](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches)
+for patches). This is fixed in GStreamer-1.22, and by backport patches
+from this in distributions such as Raspberry Pi OS (Bullseye): **use
+option `-bt709` with the GStreamer-1.18.4 from Raspberry Pi OS**. This
+also needs the bcm2835-codec kernel module that is not in the standard
+Linux kernel (it is available in Raspberry Pi OS, Ubuntu and Manjaro).
+
+- **If this kernel module is not available in your Raspberry Pi
+ operating system, or if GStreamer \< 1.22 is not patched, use option
+ `-avdec` for software h264-decoding.**
+
+Sometimes "autovideosink" may select the OpenGL renderer "glimagesink"
+which may not work correctly on your system. Try the options "-vs
+ximagesink" or "-vs xvimagesink" to see if using one of these fixes the
+problem.
+
+Other reported problems are connected to the GStreamer VAAPI plugin (for
+hardware-accelerated Intel graphics, but not NVIDIA graphics). Use the
+option "-avdec" to force software h264 video decoding: this should
+prevent autovideosink from selecting the vaapisink videosink.
+Alternatively, find out if the gstreamer1.0-vaapi plugin is installed,
+and if so, uninstall it. (If this does not fix the problem, you can
+reinstall it.)
+
+There are some reports of other GStreamer problems with
+hardware-accelerated Intel HD graphics. One user (on Debian) solved this
+with "sudo apt install intel-media-va-driver-non-free". This is a driver
+for 8'th (or later) generation "\*-lake" Intel chips, that seems to be
+related to VAAPI accelerated graphics.
+
+If you *do* have Intel HD graphics, and have installed the vaapi plugin,
+but `-vs vaapisink` does not work, check that vaapi is not "blacklisted"
+in your GStreamer installation: run `gst-inspect-1.0 vaapi`, if this
+reports `0 features`, you need to `export GST_VAAPI_ALL_DRIVERS=1`
+before running uxplay, or set this in the default environment.
+
+You can try to fix audio or video problems by using the
+"`-as `" or "`-vs `" options to choose the
+GStreamer audiosink or videosink , rather than letting GStreamer choose
+one for you. (See above, in [Starting and running
+UxPlay](#starting-and-running-uxplay) for choices of `` or
+``.)
+
+The "OpenGL renderer" window created on Linux by "-vs glimagesink"
+sometimes does not close properly when its "close" button is clicked.
+(this is a GStreamer issue). You may need to terminate uxplay with
+Ctrl-C to close a "zombie" OpenGl window. If similar problems happen
+when the client sends the "Stop Mirroring" signal, try the no-close
+option "-nc" that leaves the video window open.
+
+### 4. GStreamer issues (missing plugins, etc.):
+
+- clearing the user's GStreamer cache with
+ `rm -rf ~/.cache/gstreamer-1.0/*` may be the solution to problems
+ where gst-inspect-1.0 does not show a plugin that you believe is
+ installed. The cache will be regenerated next time GStreamer is
+ started. **This is the solution to puzzling problems that turn out
+ to come from corruption of the cache, and should be tried first.**
+
+If UxPlay fails to start, with a message that a required GStreamer
+plugin (such as "libav") was not found, first check with the GStreamer
+tool gst-inspect-1.0 to see what GStreamer knows is available. (You may
+need to install some additional GStreamer "tools" package to get
+gst-inspect-1.0). For, *e.g.* a libav problem, check with
+"`gst-inspect-1.0 libav`". If it is not shown as available to GStreamer,
+but your package manager shows the relevant package as installed (as one
+user found), try entirely removing and reinstalling the package. That
+user found that a solution to a "**Required gstreamer plugin 'libav' not
+found**" message that kept recurring was to clear the user's gstreamer
cache.
-
-If it fails to start with an error like '`no element "avdec_aac"`' this is
-because even though gstreamer-libav is installed. it is incomplete because some plugin features are missing: "`gst-inspect-1.0 | grep avdec_aac`" will
-show if avdec_aac is available. Unlike other GStreamer plugins, the libav plugin is a front end to FFmpeg codecs which provide avdec_*.
-
-* Some distributions (RedHat, SUSE, etc) provide incomplete versions of FFmpeg because of patent issues with codecs used by
-certain plugins. In those cases there will be some "extra package" provider like [RPM fusion](https://rpmfusion.org) (RedHat),
-[packman](http://packman.links2linux.org/) (SUSE) where you can get complete packages (your
-distribution will usually provide instructions for this, Mageia puts them in an optional "tainted" repo). The packages
-needed may be "ffmpeg\*" or "libav\*" packages: the GStreamer libav plugin package does not contain any codecs itself, it just provides a way
-for GStreamer to use ffmpeg/libav codec libraries which must be installed separately. For similar reasons, distributions may ship incomplete packages
-of GStreamer "plugins-bad". Use user on Fedora thought they had installed from rpmfusion, but the system had not obeyed: _"Adding --allowerasing to
-the dnf command fixed it after a restart"_.
-
-* starting with release UxPlay-1.65.3, UxPlay will continue to function, but without audio in mirror mode, if avdec_aac is missing.
-
-To troubleshoot GStreamer execute "export GST_DEBUG=2"
-to set the GStreamer debug-level environment-variable in the terminal
-where you will run uxplay, so that you see warning and error messages;
-see [GStreamer debugging tools](https://gstreamer.freedesktop.org/documentation/tutorials/basic/debugging-tools.html)
-for how to see much more of what is happening inside
-GStreamer. Run "gst-inspect-1.0" to see which GStreamer plugins are
-installed on your system.
-
-Some extra GStreamer packages for special plugins may need to be installed (or reinstalled: a user using a Wayland display system as an alternative to X11
-reported that after reinstalling Lubuntu 18.4, UxPlay would not work until gstreamer1.0-x was installed, presumably for Wayland's X11-compatibility mode).
-Different distributions may break up GStreamer 1.x into packages in different ways; the packages listed above in the build instructions should bring in
-other required GStreamer packages as dependencies, but will not install all possible plugins.
-
-The GStreamer video pipeline, which is shown in the initial output from `uxplay -d`,
-has the default form
-```
-appsrc name=video_source ! queue ! h264parse ! decodebin ! videoconvert ! autovideosink name=video_sink sync=false
-```
-
-The pipeline is fully configurable: default elements "h264parse", "decodebin", "videoconvert", and "autovideosink" can respectively be replaced by using uxplay
-options `-vp`, ``-vd``, ```-vc```, and ````-vs````, if there is any need to
-modify it (entries can be given in quotes "..." to include options).
-
-### 5. Mirror screen freezes (a network problem):
-
-This can happen if the TCP video stream from the client stops arriving at the server, probably because of network problems (the UDP audio stream may continue to arrive). At 3-second
-intervals, UxPlay checks that the client is still connected by sending it a request for a NTP time signal. If a reply is not received from the client within a 0.3 sec
-time-window, an "ntp timeout" is registered. If a certain number (currently 5) of consecutive ntp timeouts occur, UxPlay assumes that the client is "dead", and resets the connection,
-becoming available for connection to a new client, or reconnection to the previous one. Sometimes the connection may recover before the timeout limit is reached, and if the
-default limit is not right for your network, it can be modified using the option "-reset _n_", where _n_ is the desired timeout-limit value (_n_ = 0 means "no limit"). If the connection
-starts to recover after ntp timeouts, a corrupt video packet from before the timeout may trigger a "connection reset by peer" error, which also causes UxPlay to reset the
-connection.
-
-* When the connection is reset, the "frozen" mirror screen of the previous connection is left in place, but does **not** block
-new connections, and will be taken over by a new client connection when it is made.
+If it fails to start with an error like '`no element "avdec_aac"`' this
+is because even though gstreamer-libav is installed. it is incomplete
+because some plugin features are missing:
+"`gst-inspect-1.0 | grep avdec_aac`" will show if avdec_aac is
+available. Unlike other GStreamer plugins, the libav plugin is a front
+end to FFmpeg codecs which provide avdec\_\*.
+
+- Some distributions (RedHat, SUSE, etc) provide incomplete versions
+ of FFmpeg because of patent issues with codecs used by certain
+ plugins. In those cases there will be some "extra package" provider
+ like [RPM fusion](https://rpmfusion.org) (RedHat),
+ [packman](http://packman.links2linux.org/) (SUSE) where you can get
+ complete packages (your distribution will usually provide
+ instructions for this, Mageia puts them in an optional "tainted"
+ repo). The packages needed may be "ffmpeg\*" or "libav\*" packages:
+ the GStreamer libav plugin package does not contain any codecs
+ itself, it just provides a way for GStreamer to use ffmpeg/libav
+ codec libraries which must be installed separately. For similar
+ reasons, distributions may ship incomplete packages of GStreamer
+ "plugins-bad". Use user on Fedora thought they had installed from
+ rpmfusion, but the system had not obeyed: *"Adding --allowerasing to
+ the dnf command fixed it after a restart"*.
+
+- starting with release UxPlay-1.65.3, UxPlay will continue to
+ function, but without audio in mirror mode, if avdec_aac is missing.
+
+To troubleshoot GStreamer execute "export GST_DEBUG=2" to set the
+GStreamer debug-level environment-variable in the terminal where you
+will run uxplay, so that you see warning and error messages; see
+[GStreamer debugging
+tools](https://gstreamer.freedesktop.org/documentation/tutorials/basic/debugging-tools.html)
+for how to see much more of what is happening inside GStreamer. Run
+"gst-inspect-1.0" to see which GStreamer plugins are installed on your
+system.
+
+Some extra GStreamer packages for special plugins may need to be
+installed (or reinstalled: a user using a Wayland display system as an
+alternative to X11 reported that after reinstalling Lubuntu 18.4, UxPlay
+would not work until gstreamer1.0-x was installed, presumably for
+Wayland's X11-compatibility mode). Different distributions may break up
+GStreamer 1.x into packages in different ways; the packages listed above
+in the build instructions should bring in other required GStreamer
+packages as dependencies, but will not install all possible plugins.
+
+The GStreamer video pipeline, which is shown in the initial output from
+`uxplay -d`, has the default form
+
+ appsrc name=video_source ! queue ! h264parse ! decodebin ! videoconvert ! autovideosink name=video_sink sync=false
+
+The pipeline is fully configurable: default elements "h264parse",
+"decodebin", "videoconvert", and "autovideosink" can respectively be
+replaced by using uxplay options `-vp`, `-vd`, `-vc`, and `-vs`, if
+there is any need to modify it (entries can be given in quotes "..." to
+include options).
+
+### 5. Mirror screen freezes (a network problem):
+
+This can happen if the TCP video stream from the client stops arriving
+at the server, probably because of network problems (the UDP audio
+stream may continue to arrive). At 3-second intervals, UxPlay checks
+that the client is still connected by sending it a request for a NTP
+time signal. If a reply is not received from the client within a 0.3 sec
+time-window, an "ntp timeout" is registered. If a certain number
+(currently 5) of consecutive ntp timeouts occur, UxPlay assumes that the
+client is "dead", and resets the connection, becoming available for
+connection to a new client, or reconnection to the previous one.
+Sometimes the connection may recover before the timeout limit is
+reached, and if the default limit is not right for your network, it can
+be modified using the option "-reset *n*", where *n* is the desired
+timeout-limit value (*n* = 0 means "no limit"). If the connection starts
+to recover after ntp timeouts, a corrupt video packet from before the
+timeout may trigger a "connection reset by peer" error, which also
+causes UxPlay to reset the connection.
+
+- When the connection is reset, the "frozen" mirror screen of the
+ previous connection is left in place, but does **not** block new
+ connections, and will be taken over by a new client connection when
+ it is made.
### 6. Protocol issues (with decryption of the encrypted audio and video streams sent by the client).
-A protocol failure may trigger an unending stream of error messages, and means that the
-audio decryption key (also used in video decryption)
-was not correctly extracted from data sent by the client.
-
-The protocol was modifed in UxPlay-1.65 after it was discovered that the client-server "pairing" step could be avoided (leading to a much quicker
-connection setup, without a 5 second delay) by disabling "Supports Legacy Pairing" (bit 27) in the "features" code UxPlay advertises
-on DNS-SD Service Discovery. Most clients will then not attempt the setup of a "shared secret key" when pairing, which is used by AppleTV for simultaneous
-handling of multiple clients (UxPlay only supports one client at a time).
-**This change is now well-tested, but in case it causes any protocol failures, UxPlay can be reverted to the previous behavior by uncommenting the previous "FEATURES_1" setting
-(and commenting out the new one) in lib/dnssdint.h, and then rebuilding UxPlay.** ("Pairing" is re-enabled when the new Apple-style one-time "pin" authentication is activated by running UxPlay with the "-pin" option introduced in UxPlay 1.67.)
-
-
-Protocol failure should not happen for iOS 9.3 or later clients. However, if a client
-uses the same older version of the protocol that is used by the Windows-based
-AirPlay client emulator _AirMyPC_, the protocol can be switched to the older version
-by the setting ```OLD_PROTOCOL_CLIENT_USER_AGENT_LIST```
-in `UxPlay/lib/global.h`.
-UxPlay reports the client's "User Agent" string when it connects. If
-some other client also fails to decrypt all audio and video, try adding
-its "User Agent" string in place of "xxx" in the entry "AirMyPC/2.0;xxx"
-in global.h and rebuild uxplay.
-
-Note that for DNS-SD Service Discovery, Uxplay declares itself to be an AppleTV3,2 (a 32 bit device) with a
-sourceVersion 220.68; this can also be changed in global.h.
-UxPlay also works if it declares itself as an AppleTV6,2 with
-sourceVersion 380.20.1 (an AppleTV 4K 1st gen, introduced 2017, running
-tvOS 12.2.1), so it does not seem to matter what version UxPlay claims to be.
-
+A protocol failure may trigger an unending stream of error messages, and
+means that the audio decryption key (also used in video decryption) was
+not correctly extracted from data sent by the client.
+
+The protocol was modifed in UxPlay-1.65 after it was discovered that the
+client-server "pairing" step could be avoided (leading to a much quicker
+connection setup, without a 5 second delay) by disabling "Supports
+Legacy Pairing" (bit 27) in the "features" code UxPlay advertises on
+DNS-SD Service Discovery. Most clients will then not attempt the setup
+of a "shared secret key" when pairing, which is used by AppleTV for
+simultaneous handling of multiple clients (UxPlay only supports one
+client at a time). **This change is now well-tested, but in case it
+causes any protocol failures, UxPlay can be reverted to the previous
+behavior by uncommenting the previous "FEATURES_1" setting (and
+commenting out the new one) in lib/dnssdint.h, and then rebuilding
+UxPlay.** ("Pairing" is re-enabled when the new Apple-style one-time
+"pin" authentication is activated by running UxPlay with the "-pin"
+option introduced in UxPlay 1.67.)
+
+Protocol failure should not happen for iOS 9.3 or later clients.
+However, if a client uses the same older version of the protocol that is
+used by the Windows-based AirPlay client emulator *AirMyPC*, the
+protocol can be switched to the older version by the setting
+`OLD_PROTOCOL_CLIENT_USER_AGENT_LIST` in `UxPlay/lib/global.h`. UxPlay
+reports the client's "User Agent" string when it connects. If some other
+client also fails to decrypt all audio and video, try adding its "User
+Agent" string in place of "xxx" in the entry "AirMyPC/2.0;xxx" in
+global.h and rebuild uxplay.
+
+Note that for DNS-SD Service Discovery, Uxplay declares itself to be an
+AppleTV3,2 (a 32 bit device) with a sourceVersion 220.68; this can also
+be changed in global.h. UxPlay also works if it declares itself as an
+AppleTV6,2 with sourceVersion 380.20.1 (an AppleTV 4K 1st gen,
+introduced 2017, running tvOS 12.2.1), so it does not seem to matter
+what version UxPlay claims to be.
# Changelog
-1.71 2024-12-10 Add support for HTTP Live Streaming (HLS), initially only for YouTube movies
-
-1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x 2160). Fix issue
- with GStreamer >= 1.24 when client sleeps, then wakes.
-
-1.69 2024-08-09 Internal improvements (e.g. in -nohold option, identifying GStreamer videosink
- selected by autovideosink, finding X11 display) in anticipation of future HLS video support.
- New -nofreeze option to not leave frozen video in place when a network connection is reset.
- Fixes for GStreamer-1.24.x changes.
-
-1.68 2023-12-31 New simpler (default) method for generating a persistent public key from the server MAC
- address (which can now be set with the -m option). (The previous method is still available
- with -key option). New option -reg to maintain a register of pin-authenticated clients. Corrected
- volume-control: now interprets AirPlay volume range -30dB:0dB as decibel gain attenuation,
- with new option -db low[:high] for "flat" rescaling of the dB range. Add -taper option for a "tapered"
- AirPlay volume-control profile.
-
-1.67 2023-11-30 Add support for Apple-style one-time pin authentication of clients with option "-pin":
- (uses SRP6a authentication protocol and public key persistence). Detection with error message
- of (currently) unsupported H265 video when requesting high resolution over wired ethernet.
- Removed rpi* options (which are not valid with new Raspberry Pi model 5, and can be replaced
- by combinations of other options). Added optional argument "mac" to "-m" option, to
- specify a replacement MAC address/Device ID. Update llhttp to v. 9.1.3. Add -dacp option
- for exporting current client DACP info (for remotes).
-
-1.66 2023-09-05 Fix IPV6 support. Add option to restrict clients to those on a list of allowed deviceIDs,
- or to block connections from clients on a list of blocked deviceIDs. Fix for #207 from
- @thiccaxe (screen lag in vsync mode after client wakes from sleep).
-
-1.65.3 2023-07-23 Add RPM spec file; add warning if required gstreamer libav feature "avdec_aac" is
- missing: (this occurs in RPM-based distributions that ship an incomplete FFmpeg for Patent
- or License reasons, and rely on users installing an externally-supplied complete FFmpeg).
- Mirror-mode airplay will now work without audio if avdec_aac is missing.
-
-1.65 2023-06-03 Eliminate pair_setup part of connection protocol to allow faster connections with clients
- (thanks to @shuax #176 for this discovery); to revert, uncomment a line in lib/dnssdint.h.
- Disconnect from audio device when connection closes, to not block its use by other apps if
- uxplay is running but not connected. Fix for AirMyPC client (broken since 1.60), so its
- older non-NTP timestamp protocol works with -vsync. Corrected parsing of configuration
- file entries that were in quotes.
-
-1.64 2023-04-23 Timestamp-based synchronization of audio and video is now the default in Mirror mode.
- (Use "-vsync no" to restore previous behavior.) A configuration file can now be used
- for startup options. Also some internal cleanups and a minor bugfix that fixes #192.
-
-1.63 2023-02-12 Reworked audio-video synchronization, with new options -vsync (for Mirror mode) and
- -async (for Audio-Only mode, to sync with client video). Option -vsync makes software
- h264 decoding of streamed videos with option -avdec viable on some recent Raspberry Pi models.
- Internal change: all times are now processed in nanoseconds units. Removed -ao option
- introduced in 1.62.
-
-1.62 2023-01-18 Added Audio-only mode time offset -ao x to allow user synchronization of ALAC
- audio playing on the server with video, song lyrics, etc. playing on the client.
- x = 5.0 appears to be optimal in many cases. Quality fixes: cleanup in volume
- changes, timestamps, some bugfixes.
-
-1.61 2022-12-30 Removed -t option (workaround for an Avahi issue, correctly solved by opening network
- port UDP 5353 in firewall). Remove -g debug flag from CMAKE_CFLAGS. Postpend (instead
- of prepend) build environment CFLAGS to CMAKE_CFLAGS. Refactor parts of uxplay.cpp
-
-1.60 2022-12-15 Added exit with error message if DNSServiceRegister fails (instead of just stalling).
- Test for Client's attempt to using unsupported AirPlay 2 "REMOTE CONTROL" protocol
- (with no timing channel), and exit if this occurs. Reworked metadata processing
- to correctly parse DMAP header (previous version worked with DMAP messages currently
- received, but was not correct).
-
-1.59 2022-12-12 remove "ZOOMFIX" compile option and make compilation with X11-dependence the
- default if X11 development libraries are detected (this now also provides
- fullscreen mode with a F11 or Alt+Enter key toggle); ZOOMFIX is now automatically
- applied for GStreamer < 1.20. New cmake option -DNO_X11_DEPS compiles uxplay without
- X11 dependence. Reworked internal metadata handling. Fix segfault with "-vs 0".
-
-1.58 2022-10-29 Add option "-nohold" that will drop existing connections when a new client connects.
- Update llhttp to v8.1.0.
-
-1.57 2022-10-09 Minor fixes: (fix coredump on AUR on "stop mirroring", occurs when compiled with
- AUR CFLAGS -DFORTIFY_SOURCE); graceful exit when required plugins are missing;
- improved support for builds on Windows. Include audioresample in GStreamer
- audio pipeline.
-
-1.56 2022-09-01 Added support for building and running UxPlay-1.56 on Windows (no changes
- to Unix (Linux, *BSD, macOS) codebase.)
-
-1.56 2022-07-30 Remove -bt709 from -rpi, -rpiwl, -rpifb as GStreamer is now fixed.
-
-1.55 2022-07-04 Remove the bt709 fix from -v4l2 and create a new -bt709 option (previous
- "-v4l2" is now "-v4l2 -bt709"). This allows the currently-required -bt709
- option to be used on its own on RPi without -v4l2 (sometimes this give better results).
-
-1.54 2022-06-25 Add support for "Cover Art" display in Audio-only (ALAC) mode. Reverted a change
- that caused VAAPI to crash with AMD POLARIS graphics cards. Minor internal changes to
- plist code and uxplay option parsing.
-
-1.53 2022-06-13 Internal changes to audio sync code, revised documentation,
- Minor bugfix (fix assertion crash when resent audio packets are empty).
-
-1.52 2022-05-05 Cleaned up initial audio sync code, and reformatted
- streaming debug output (readable aligned timestamps with
- decimal points in seconds). Eliminate memory leaks
- (found by valgrind). Support for display of ALAC
- (audio-only) metadata (soundtrack artist names, titles etc.)
- in the uxplay terminal.
-
-1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option -v4l2) and short options -rpi, -rpifb, -rpiwl as
- synonyms for -v4l2, -v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from 1.48 that broke
- reconnection after "Stop Mirroring" is sent by client.
-
-1.50 2022-04-22 Added -fs fullscreen option (for Wayland or VAAPI plugins only), Changed -rpi to be for framebuffer ("lite") RPi
- systems and added -rpigl (OpenGL) and -rpiwl (Wayland) options for RPi Desktop systems.
- Also modified timestamps from "DTS" to "PTS" for latency improvement, plus internal cleanups.
-
-1.49 2022-03-28 Addded options for dumping video and/or audio to file, for debugging, etc. h264 PPS/SPS NALU's are shown with -d.
- Fixed video-not-working for M1 Mac clients.
-
-1.48 2022-03-11 Made the GStreamer video pipeline fully configurable, for use with hardware h264 decoding. Support for Raspberry Pi.
-
-1.47 2022-02-05 Added -FPSdata option to display (in the terminal) regular reports sent by the client about video streaming
- performance. Internal cleanups of processing of video packets received from the client. Added -reset n option
- to reset the connection after n ntp timeouts (also reset after "connection reset by peer" error in video stream).
-
-1.46 2022-01-20 Restore pre-1.44 behavior (1.44 may have broken hardware acceleration): once again use decodebin in the video pipeline;
- introduce new option "-avdec" to force software h264 decoding by libav h264, if needed (to prevent selection of
- vaapisink by autovideosink). Update llhttp to v6.0.6. UxPlay now reports itself as AppleTV3,2. Restrict connections
- to one client at a time (second client must now wait for first client to disconnect).
-
-1.45 2022-01-10 New behavior: close video window when client requests "stop mirroring". (A new "no close" option "-nc" is added
- for users who wish to retain previous behavior that does not close the video window).
-
-1.44 2021-12-13 Omit hash of aeskey with ecdh_secret for an AirMyPC client; make an internal rearrangement of where this hash is
- done. Fully report all initial communications between client and server in -d debug mode. Replace decodebin in GStreamer
- video pipeline by h264-specific elements.
-
-1.43 2021-12-07 Various internal changes, such as tests for successful decryption, uniform treatment
- of informational/debug messages, etc., updated README.
-
-1.42 2021-11-20 Fix MAC detection to work with modern Linux interface naming practices, MacOS and *BSD.
-
-1.41 2021-11-11 Further cleanups of multiple audio format support (internal changes,
- separated RAOP and GStreamer audio/video startup)
-
-1.40 2021-11-09 Cleanup segfault in ALAC support, manpage location fix, show request Plists in debug mode.
-
-1.39 2021-11-06 Added support for Apple Lossless (ALAC) audio streams.
-
-1.38 2021-10-8 Add -as _audiosink_ option to allow user to choose the GStreamer audiosink.
-
-1.37 2021-09-29 Append "@hostname" to AirPlay Server name, where "hostname" is the name of the
- server running uxplay (reworked change in 1.36).
-
-1.36 2021-09-29 Implemented suggestion (by @mrbesen and @PetrusZ) to use hostname of machine
- runing uxplay as the default server name
-
-1.35.1 2021-09-28 Added the -vs 0 option for streaming audio, but not displaying video.
-
-1.35 2021-09-10 now uses a GLib MainLoop, and builds on macOS (tested on Intel Mac, 10.15 ).
- New option -t _timeout_ for relaunching server if no connections were active in
- previous _timeout_ seconds (to renew Bonjour registration).
-
-1.341 2021-09-04 fixed: render logger was not being destroyed by stop_server()
-
-1.34 2021-08-27 Fixed "ZOOMFIX": the X11 window name fix was only being made the
- first time the GStreamer window was created by uxplay, and
- not if the server was relaunched after the GStreamer window
- was closed, with uxplay still running. Corrected in v. 1.34
-
-### Building OpenSSL >= 1.1.1 from source.
-
-If you need to do this, note that you may be able to use a newer version (OpenSSL-3.0.1 is known to work).
-You will need the standard development toolset (autoconf, automake, libtool).
-Download the source code from
-[https://www.openssl.org/source/](https://www.openssl.org/source/).
-Install the downloaded
-openssl by opening a terminal in your Downloads directory, and unpacking the source distribution:
-("tar -xvzf openssl-3.0.1.tar.gz ; cd openssl-3.0.1"). Then build/install with
-"./config ; make ; sudo make install_dev". This will typically install the needed library ```libcrypto.*```,
-either in /usr/local/lib or /usr/local/lib64.
-
-_(Ignore the following for builds on MacOS:)_
-On some systems like
-Debian or Ubuntu, you may also need to add a missing entry ```/usr/local/lib64```
-in /etc/ld.so.conf (or place a file containing "/usr/local/lib64/libcrypto.so" in /etc/ld.so.conf.d)
-and then run "sudo ldconfig".
-
-### Building libplist >= 2.0.0 from source.
-
-_(Note: on Debian 9 "Stretch" or Ubuntu 16.04 LTS editions, you can avoid this step by installing libplist-dev
-and libplist3 from Debian 10 or Ubuntu 18.04.)_
-As well as the usual build tools (autoconf, automake, libtool), you
-may need to also install some libpython\*-dev package. Download the latest source
-with git from [https://github.com/libimobiledevice/libplist](https://github.com/libimobiledevice/libplist), or
-get the source from the Releases section (use the \*.tar.bz2 release, **not** the \*.zip or \*.tar.gz versions):
-download [libplist-2.3.0](https://github.com/libimobiledevice/libplist/releases/download/2.3.0/libplist-2.3.0.tar.bz2),
-then unpack it ("tar -xvjf libplist-2.3.0.tar.bz2 ; cd libplist-2.3.0"), and build/install it:
-("./configure ; make ; sudo make install"). This will probably install libplist-2.0.\* in /usr/local/lib.
-The new libplist-2.3.0 release should be compatible with
-UxPlay; [libplist-2.2.0](https://github.com/libimobiledevice/libplist/releases/download/2.2.0/libplist-2.2.0.tar.bz2) is
-also available if there are any issues.
-
-_(Ignore the following for builds on MacOS:)_ On some systems like
-Debian or Ubuntu, you may also need to add a missing entry ```/usr/local/lib```
-in /etc/ld.so.conf (or place a file containing "/usr/local/lib/libplist-2.0.so" in /etc/ld.so.conf.d)
-and then run "sudo ldconfig".
-
-
+1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
+only for YouTube movies. Fix issue with NTP timeout on Windows.
+
+1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
+2160). Fix issue with GStreamer \>= 1.24 when client sleeps, then wakes.
+
+1.69 2024-08-09 Internal improvements (e.g. in -nohold option,
+identifying GStreamer videosink selected by autovideosink, finding X11
+display) in anticipation of future HLS video support. New -nofreeze
+option to not leave frozen video in place when a network connection is
+reset. Fixes for GStreamer-1.24.x changes.
+
+1.68 2023-12-31 New simpler (default) method for generating a persistent
+public key from the server MAC address (which can now be set with the -m
+option). (The previous method is still available with -key option). New
+option -reg to maintain a register of pin-authenticated clients.
+Corrected volume-control: now interprets AirPlay volume range -30dB:0dB
+as decibel gain attenuation, with new option -db low\[:high\] for "flat"
+rescaling of the dB range. Add -taper option for a "tapered" AirPlay
+volume-control profile.
+
+1.67 2023-11-30 Add support for Apple-style one-time pin authentication
+of clients with option "-pin": (uses SRP6a authentication protocol and
+public key persistence). Detection with error message of (currently)
+unsupported H265 video when requesting high resolution over wired
+ethernet. Removed rpi\* options (which are not valid with new Raspberry
+Pi model 5, and can be replaced by combinations of other options). Added
+optional argument "mac" to "-m" option, to specify a replacement MAC
+address/Device ID. Update llhttp to v. 9.1.3. Add -dacp option for
+exporting current client DACP info (for remotes).
+
+1.66 2023-09-05 Fix IPV6 support. Add option to restrict clients to
+those on a list of allowed deviceIDs, or to block connections from
+clients on a list of blocked deviceIDs. Fix for #207 from @thiccaxe
+(screen lag in vsync mode after client wakes from sleep).
+
+1.65.3 2023-07-23 Add RPM spec file; add warning if required gstreamer
+libav feature "avdec_aac" is missing: (this occurs in RPM-based
+distributions that ship an incomplete FFmpeg for Patent or License
+reasons, and rely on users installing an externally-supplied complete
+FFmpeg). Mirror-mode airplay will now work without audio if avdec_aac is
+missing.
+
+1.65 2023-06-03 Eliminate pair_setup part of connection protocol to
+allow faster connections with clients (thanks to @shuax #176 for this
+discovery); to revert, uncomment a line in lib/dnssdint.h. Disconnect
+from audio device when connection closes, to not block its use by other
+apps if uxplay is running but not connected. Fix for AirMyPC client
+(broken since 1.60), so its older non-NTP timestamp protocol works with
+-vsync. Corrected parsing of configuration file entries that were in
+quotes.
+
+1.64 2023-04-23 Timestamp-based synchronization of audio and video is
+now the default in Mirror mode. (Use "-vsync no" to restore previous
+behavior.) A configuration file can now be used for startup options.
+Also some internal cleanups and a minor bugfix that fixes #192.
+
+1.63 2023-02-12 Reworked audio-video synchronization, with new options
+-vsync (for Mirror mode) and -async (for Audio-Only mode, to sync with
+client video). Option -vsync makes software h264 decoding of streamed
+videos with option -avdec viable on some recent Raspberry Pi models.
+Internal change: all times are now processed in nanoseconds units.
+Removed -ao option introduced in 1.62.
+
+1.62 2023-01-18 Added Audio-only mode time offset -ao x to allow user
+synchronization of ALAC audio playing on the server with video, song
+lyrics, etc. playing on the client. x = 5.0 appears to be optimal in
+many cases. Quality fixes: cleanup in volume changes, timestamps, some
+bugfixes.
+
+1.61 2022-12-30 Removed -t option (workaround for an Avahi issue,
+correctly solved by opening network port UDP 5353 in firewall). Remove
+-g debug flag from CMAKE_CFLAGS. Postpend (instead of prepend) build
+environment CFLAGS to CMAKE_CFLAGS. Refactor parts of uxplay.cpp
+
+1.60 2022-12-15 Added exit with error message if DNSServiceRegister
+fails (instead of just stalling). Test for Client's attempt to using
+unsupported AirPlay 2 "REMOTE CONTROL" protocol (with no timing
+channel), and exit if this occurs. Reworked metadata processing to
+correctly parse DMAP header (previous version worked with DMAP messages
+currently received, but was not correct).
+
+1.59 2022-12-12 remove "ZOOMFIX" compile option and make compilation
+with X11-dependence the default if X11 development libraries are
+detected (this now also provides fullscreen mode with a F11 or Alt+Enter
+key toggle); ZOOMFIX is now automatically applied for GStreamer \< 1.20.
+New cmake option -DNO_X11_DEPS compiles uxplay without X11 dependence.
+Reworked internal metadata handling. Fix segfault with "-vs 0".
+
+1.58 2022-10-29 Add option "-nohold" that will drop existing connections
+when a new client connects. Update llhttp to v8.1.0.
+
+1.57 2022-10-09 Minor fixes: (fix coredump on AUR on "stop mirroring",
+occurs when compiled with AUR CFLAGS -DFORTIFY_SOURCE); graceful exit
+when required plugins are missing; improved support for builds on
+Windows. Include audioresample in GStreamer audio pipeline.
+
+1.56 2022-09-01 Added support for building and running UxPlay-1.56 on
+Windows (no changes to Unix (Linux, \*BSD, macOS) codebase.)
+
+1.56 2022-07-30 Remove -bt709 from -rpi, -rpiwl, -rpifb as GStreamer is
+now fixed.
+
+1.55 2022-07-04 Remove the bt709 fix from -v4l2 and create a new -bt709
+option (previous "-v4l2" is now "-v4l2 -bt709"). This allows the
+currently-required -bt709 option to be used on its own on RPi without
+-v4l2 (sometimes this give better results).
+
+1.54 2022-06-25 Add support for "Cover Art" display in Audio-only (ALAC)
+mode. Reverted a change that caused VAAPI to crash with AMD POLARIS
+graphics cards. Minor internal changes to plist code and uxplay option
+parsing.
+
+1.53 2022-06-13 Internal changes to audio sync code, revised
+documentation, Minor bugfix (fix assertion crash when resent audio
+packets are empty).
+
+1.52 2022-05-05 Cleaned up initial audio sync code, and reformatted
+streaming debug output (readable aligned timestamps with decimal points
+in seconds). Eliminate memory leaks (found by valgrind). Support for
+display of ALAC (audio-only) metadata (soundtrack artist names, titles
+etc.) in the uxplay terminal.
+
+1.51 2022-04-24 Reworked options forVideo4Linux2 support (new option
+-v4l2) and short options -rpi, -rpifb, -rpiwl as synonyms for -v4l2,
+-v4l2 -vs kmssink, and -v4l2 -vs waylandsink. Reverted a change from
+1.48 that broke reconnection after "Stop Mirroring" is sent by client.
+
+1.50 2022-04-22 Added -fs fullscreen option (for Wayland or VAAPI
+plugins only), Changed -rpi to be for framebuffer ("lite") RPi systems
+and added -rpigl (OpenGL) and -rpiwl (Wayland) options for RPi Desktop
+systems. Also modified timestamps from "DTS" to "PTS" for latency
+improvement, plus internal cleanups.
+
+1.49 2022-03-28 Addded options for dumping video and/or audio to file,
+for debugging, etc. h264 PPS/SPS NALU's are shown with -d. Fixed
+video-not-working for M1 Mac clients.
+
+1.48 2022-03-11 Made the GStreamer video pipeline fully configurable,
+for use with hardware h264 decoding. Support for Raspberry Pi.
+
+1.47 2022-02-05 Added -FPSdata option to display (in the terminal)
+regular reports sent by the client about video streaming performance.
+Internal cleanups of processing of video packets received from the
+client. Added -reset n option to reset the connection after n ntp
+timeouts (also reset after "connection reset by peer" error in video
+stream).
+
+1.46 2022-01-20 Restore pre-1.44 behavior (1.44 may have broken hardware
+acceleration): once again use decodebin in the video pipeline; introduce
+new option "-avdec" to force software h264 decoding by libav h264, if
+needed (to prevent selection of vaapisink by autovideosink). Update
+llhttp to v6.0.6. UxPlay now reports itself as AppleTV3,2. Restrict
+connections to one client at a time (second client must now wait for
+first client to disconnect).
+
+1.45 2022-01-10 New behavior: close video window when client requests
+"stop mirroring". (A new "no close" option "-nc" is added for users who
+wish to retain previous behavior that does not close the video window).
+
+1.44 2021-12-13 Omit hash of aeskey with ecdh_secret for an AirMyPC
+client; make an internal rearrangement of where this hash is done. Fully
+report all initial communications between client and server in -d debug
+mode. Replace decodebin in GStreamer video pipeline by h264-specific
+elements.
+
+1.43 2021-12-07 Various internal changes, such as tests for successful
+decryption, uniform treatment of informational/debug messages, etc.,
+updated README.
+
+1.42 2021-11-20 Fix MAC detection to work with modern Linux interface
+naming practices, MacOS and \*BSD.
+
+1.41 2021-11-11 Further cleanups of multiple audio format support
+(internal changes, separated RAOP and GStreamer audio/video startup)
+
+1.40 2021-11-09 Cleanup segfault in ALAC support, manpage location fix,
+show request Plists in debug mode.
+
+1.39 2021-11-06 Added support for Apple Lossless (ALAC) audio streams.
+
+1.38 2021-10-8 Add -as *audiosink* option to allow user to choose the
+GStreamer audiosink.
+
+1.37 2021-09-29 Append "@hostname" to AirPlay Server name, where
+"hostname" is the name of the server running uxplay (reworked change in
+1.36).
+
+1.36 2021-09-29 Implemented suggestion (by @mrbesen and @PetrusZ) to use
+hostname of machine runing uxplay as the default server name
+
+1.35.1 2021-09-28 Added the -vs 0 option for streaming audio, but not
+displaying video.
+
+1.35 2021-09-10 now uses a GLib MainLoop, and builds on macOS (tested on
+Intel Mac, 10.15 ). New option -t *timeout* for relaunching server if no
+connections were active in previous *timeout* seconds (to renew Bonjour
+registration).
+
+1.341 2021-09-04 fixed: render logger was not being destroyed by
+stop_server()
+
+1.34 2021-08-27 Fixed "ZOOMFIX": the X11 window name fix was only being
+made the first time the GStreamer window was created by uxplay, and not
+if the server was relaunched after the GStreamer window was closed, with
+uxplay still running. Corrected in v. 1.34
+
+### Building OpenSSL \>= 1.1.1 from source.
+
+If you need to do this, note that you may be able to use a newer version
+(OpenSSL-3.0.1 is known to work). You will need the standard development
+toolset (autoconf, automake, libtool). Download the source code from
+. Install the downloaded openssl by
+opening a terminal in your Downloads directory, and unpacking the source
+distribution: ("tar -xvzf openssl-3.0.1.tar.gz ; cd openssl-3.0.1").
+Then build/install with "./config ; make ; sudo make install_dev". This
+will typically install the needed library `libcrypto.*`, either in
+/usr/local/lib or /usr/local/lib64.
+
+*(Ignore the following for builds on MacOS:)* On some systems like
+Debian or Ubuntu, you may also need to add a missing entry
+`/usr/local/lib64` in /etc/ld.so.conf (or place a file containing
+"/usr/local/lib64/libcrypto.so" in /etc/ld.so.conf.d) and then run "sudo
+ldconfig".
+
+### Building libplist \>= 2.0.0 from source.
+
+*(Note: on Debian 9 "Stretch" or Ubuntu 16.04 LTS editions, you can
+avoid this step by installing libplist-dev and libplist3 from Debian 10
+or Ubuntu 18.04.)* As well as the usual build tools (autoconf, automake,
+libtool), you may need to also install some libpython\*-dev package.
+Download the latest source with git from
+, or get the source from
+the Releases section (use the \*.tar.bz2 release, **not** the \*.zip or
+\*.tar.gz versions): download
+[libplist-2.3.0](https://github.com/libimobiledevice/libplist/releases/download/2.3.0/libplist-2.3.0.tar.bz2),
+then unpack it ("tar -xvjf libplist-2.3.0.tar.bz2 ; cd libplist-2.3.0"),
+and build/install it: ("./configure ; make ; sudo make install"). This
+will probably install libplist-2.0.\* in /usr/local/lib. The new
+libplist-2.3.0 release should be compatible with UxPlay;
+[libplist-2.2.0](https://github.com/libimobiledevice/libplist/releases/download/2.2.0/libplist-2.2.0.tar.bz2)
+is also available if there are any issues.
+
+*(Ignore the following for builds on MacOS:)* On some systems like
+Debian or Ubuntu, you may also need to add a missing entry
+`/usr/local/lib` in /etc/ld.so.conf (or place a file containing
+"/usr/local/lib/libplist-2.0.so" in /etc/ld.so.conf.d) and then run
+"sudo ldconfig".
# Disclaimer
-All the resources in this repository are written using only freely available information from the internet. The code and related resources are meant for
-educational purposes only. It is the responsibility of the user to make sure all local laws are adhered to.
-
-This project makes use of a third-party GPL library for handling FairPlay. The legal status of that library is unclear. Should you be a representative of
-Apple and have any objections against the legality of the library and its use in this project, please contact the developers and the appropriate steps
-will be taken.
-
-Given the large number of third-party AirPlay receivers (mostly closed-source) available for purchase, it is our understanding that an open source
-implementation of the same functionality wouldn't violate any of Apple's rights either.
+All the resources in this repository are written using only freely
+available information from the internet. The code and related resources
+are meant for educational purposes only. It is the responsibility of the
+user to make sure all local laws are adhered to.
+This project makes use of a third-party GPL library for handling
+FairPlay. The legal status of that library is unclear. Should you be a
+representative of Apple and have any objections against the legality of
+the library and its use in this project, please contact the developers
+and the appropriate steps will be taken.
+Given the large number of third-party AirPlay receivers (mostly
+closed-source) available for purchase, it is our understanding that an
+open source implementation of the same functionality wouldn't violate
+any of Apple's rights either.
# UxPlay authors
-_[adapted from fdraschbacher's notes on RPiPlay antecedents]_
-
-The code in this repository accumulated from various sources over time. Here
-is an attempt at listing the various authors and the components they created:
-
-UxPlay was initially created by **antimof** from RPiPlay, by replacing its Raspberry-Pi-adapted OpenMAX video
-and audio rendering system with GStreamer rendering for
-desktop Linux systems; the antimof work on code in `renderers/` was later backported to RPiPlay, and the antimof project became dormant, but was later
-revived at the [current GitHub site](http://github.com/FDH2/UxPlay) to serve a wider community of users.
-
-The previous authors of code included in UxPlay by inheritance from RPiPlay include:
-
-* **EstebanKubata**: Created a FairPlay library called [PlayFair](https://github.com/EstebanKubata/playfair). Located in the `lib/playfair` folder. License: GNU GPL
-* **Juho Vähä-Herttua** and contributors: Created an AirPlay audio server called [ShairPlay](https://github.com/juhovh/shairplay), including support for Fairplay based on PlayFair. Most of the code in `lib/` originally stems from this project. License: GNU LGPLv2.1+
-* **dsafa22**: Created an AirPlay 2 mirroring server [AirplayServer](https://github.com/dsafa22/AirplayServer) (seems gone now), for Android based on ShairPlay. Code is
- preserved [here](https://github.com/jiangban/AirplayServer), and [see here](https://github.com/FDH2/UxPlay/wiki/AirPlay2) for the description
- of the analysis of the AirPlay 2 mirror protocol that made RPiPlay possible, by the AirplayServer author. All
- code in `lib/` concerning mirroring is dsafa22's work. License: GNU LGPLv2.1+
-* **Florian Draschbacher** (FD-) and contributors: adapted dsafa22's Android project for the Raspberry Pi, with extensive cleanups, debugging and improvements. The
- project [RPiPlay](https://github.com/FD-/RPiPlay) is basically a port of dsafa22's code to the Raspberry Pi, utilizing OpenMAX and OpenSSL for better performance on the Pi. License GPL v3.
- FD- has written an interesting note on the history of [Airplay protocol versions](http://github.com/FD-/RPiPlay#airplay-protocol-versions),
- available at the RPiPlay github repository.
-
+*\[adapted from fdraschbacher's notes on RPiPlay antecedents\]*
+
+The code in this repository accumulated from various sources over time.
+Here is an attempt at listing the various authors and the components
+they created:
+
+UxPlay was initially created by **antimof** from RPiPlay, by replacing
+its Raspberry-Pi-adapted OpenMAX video and audio rendering system with
+GStreamer rendering for desktop Linux systems; the antimof work on code
+in `renderers/` was later backported to RPiPlay, and the antimof project
+became dormant, but was later revived at the [current GitHub
+site](http://github.com/FDH2/UxPlay) to serve a wider community of
+users.
+
+The previous authors of code included in UxPlay by inheritance from
+RPiPlay include:
+
+- **EstebanKubata**: Created a FairPlay library called
+ [PlayFair](https://github.com/EstebanKubata/playfair). Located in
+ the `lib/playfair` folder. License: GNU GPL
+- **Juho Vähä-Herttua** and contributors: Created an AirPlay audio
+ server called [ShairPlay](https://github.com/juhovh/shairplay),
+ including support for Fairplay based on PlayFair. Most of the code
+ in `lib/` originally stems from this project. License: GNU LGPLv2.1+
+- **dsafa22**: Created an AirPlay 2 mirroring server
+ [AirplayServer](https://github.com/dsafa22/AirplayServer) (seems
+ gone now), for Android based on ShairPlay. Code is preserved
+ [here](https://github.com/jiangban/AirplayServer), and [see
+ here](https://github.com/FDH2/UxPlay/wiki/AirPlay2) for the
+ description of the analysis of the AirPlay 2 mirror protocol that
+ made RPiPlay possible, by the AirplayServer author. All code in
+ `lib/` concerning mirroring is dsafa22's work. License: GNU
+ LGPLv2.1+
+- **Florian Draschbacher** (FD-) and contributors: adapted dsafa22's
+ Android project for the Raspberry Pi, with extensive cleanups,
+ debugging and improvements. The project
+ [RPiPlay](https://github.com/FD-/RPiPlay) is basically a port of
+ dsafa22's code to the Raspberry Pi, utilizing OpenMAX and OpenSSL
+ for better performance on the Pi. License GPL v3. FD- has written an
+ interesting note on the history of [Airplay protocol
+ versions](http://github.com/FD-/RPiPlay#airplay-protocol-versions),
+ available at the RPiPlay github repository.
Independent of UxPlay, but used by it and bundled with it:
-* **Fedor Indutny** (of Node.js, and formerly Joyent, Inc) and contributors: Created an http parsing library called [llhttp](https://github.com/nodejs/llhttp). Located at `lib/llhttp/`. License: MIT
-
-
+- **Fedor Indutny** (of Node.js, and formerly Joyent, Inc) and
+ contributors: Created an http parsing library called
+ [llhttp](https://github.com/nodejs/llhttp). Located at
+ `lib/llhttp/`. License: MIT
diff --git a/README.txt b/README.txt
index 581d6d0e..b7945083 100644
--- a/README.txt
+++ b/README.txt
@@ -1623,8 +1623,8 @@ what version UxPlay claims to be.
# Changelog
-1.71 2024-12-10 Add support for HTTP Live Streaming (HLS), initially
-only for YouTube movies
+1.71 2024-12-13 Add support for HTTP Live Streaming (HLS), initially
+only for YouTube movies. Fix issue with NTP timeout on Windows.
1.70 2024-10-04 Add support for 4K (h265) video (resolution 3840 x
2160). Fix issue with GStreamer \>= 1.24 when client sleeps, then wakes.
@@ -1758,7 +1758,7 @@ systems. Also modified timestamps from "DTS" to "PTS" for latency
improvement, plus internal cleanups.
1.49 2022-03-28 Addded options for dumping video and/or audio to file,
-for debugging, etc. h264 PPS/SPS NALU's are shown with -d. Fixed
+for debugging, etc. h264 PPS/SPS NALU's are shown with -d. Fixed
video-not-working for M1 Mac clients.
1.48 2022-03-11 Made the GStreamer video pipeline fully configurable,
From 78ab7efd909af9570bf622344002286a1bf7f2ed Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Fri, 13 Dec 2024 17:01:59 -0500
Subject: [PATCH 17/23] leave timeout for gst_get_state after
gst_set_state(READY)
---
renderers/video_renderer.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 4473901b..889535f4 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -346,7 +346,7 @@ void video_renderer_init(logger_t *render_logger, const char *server_name, vide
#endif
gst_element_set_state (renderer_type[i]->pipeline, GST_STATE_READY);
GstState state;
- if (gst_element_get_state (renderer_type[i]->pipeline, &state, NULL, 0)) {
+ if (gst_element_get_state (renderer_type[i]->pipeline, &state, NULL, 100 * GST_MSECOND)) {
if (state == GST_STATE_READY) {
logger_log(logger, LOGGER_DEBUG, "Initialized GStreamer video renderer %d", i + 1);
if (hls_video && i == 0) {
From aae2a2d3080d6235a3e671734c532dbf735eee33 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sat, 14 Dec 2024 10:51:27 -0500
Subject: [PATCH 18/23] option bt709 needed with R Pi <= model 4B and
GStreamer>=1.22
---
README.html | 19 +++++++++++++++----
README.md | 22 +++++++++++++++++-----
README.txt | 21 +++++++++++++++++----
renderers/video_renderer.c | 2 +-
uxplay.1 | 2 +-
uxplay.cpp | 2 +-
6 files changed, 52 insertions(+), 16 deletions(-)
diff --git a/README.html b/README.html
index 63f0d85d..48a88d47 100644
--- a/README.html
+++ b/README.html
@@ -88,12 +88,14 @@ Packaging status
the file. (Output from terminal commands “ps waux | grep pulse” or
“pactl info” will contain “pipewire” if your Linux/BSD system uses
it).
-
On Raspberry Pi: If you use Ubuntu 22.10 or earlier, GStreamer
-must be On Raspberry Pi: models using hardware h264 video decoding by the
+Broadcom GPU (models 4B and earlier) may require the uxplay option
+-bt709. If you use Ubuntu 22.10 or earlier, GStreamer must be patched
to use hardware video decoding by the Broadcom GPU (also recommended but
-optional for Raspberry Pi OS (Bullseye): use option
-“uxplay -bt709
” if you do not use the patch).
+optional for Raspberry Pi OS (Bullseye): the patched GStreamer does not
+need option ” -bt709`“. The need for -bt709 when hardware video decoding
+is used seems to have reappeared starting with GStreamer-1.22.
To (easily) compile the latest UxPlay from source, see the section Getting UxPlay.
@@ -583,6 +585,12 @@ Starting and running UxPlay
If the server is “headless” (no attached monitor, renders audio
only) use -vs 0
.
+Note that videosink options can set using quoted arguments to -vs:
+e.g., -vs "xvimagesink display=:0"
: ximagesink and
+xvimagesink allow an X11 display name to be specified, and waylandsink
+has a similar option. Videosink options (“properties”) can be found in
+their GStreamer description pages,such as
+https://gstreamer.freedesktop.org/documentation/xvimagesink .
GStreamer also searches for the best “audiosink”; override its choice
with -as <audiosink>
. Choices on Linux include
pulsesink, alsasink, pipewiresink, oss4sink; see what is available with
@@ -626,6 +634,9 @@
Starting and running UxPlay
GStreamer-1.18.4 which needs the uxplay option -bt709 (and don’t use
-v4l2); it is still better to apply the full patch from the UxPlay Wiki
in this case.
+It appears that when hardware h264 video decoding is
+used, the option -bt709 became needed again in GStreamer-1.22 and
+later.
For “double-legacy” Raspberry Pi OS (Buster), there is no patch
for GStreamer-1.14. Instead, first build a complete newer
GStreamer-1.18.6 from source using `. Choices on Linux include pulsesink, alsasink,
pipewiresink, oss4sink; see what is available with
@@ -622,6 +631,9 @@ See [Usage](#usage) for more run-time options.
-v4l2); it is still better to apply the full patch from the UxPlay
Wiki in this case.
+- **It appears that when hardware h264 video decoding is used, the option
+ -bt709 became needed again in GStreamer-1.22 and later.**
+
- For "double-legacy" Raspberry Pi OS (Buster), there is no patch for
GStreamer-1.14. Instead, first build a complete newer
GStreamer-1.18.6 from source using [these
diff --git a/README.txt b/README.txt
index b7945083..0ee38e98 100644
--- a/README.txt
+++ b/README.txt
@@ -81,12 +81,15 @@ After installation:
from terminal commands "ps waux \| grep pulse" or "pactl info" will
contain "pipewire" if your Linux/BSD system uses it).*
-- On Raspberry Pi: If you use Ubuntu 22.10 or earlier, GStreamer must
- be
+- On Raspberry Pi: models using hardware h264 video decoding by the
+ Broadcom GPU (models 4B and earlier) may require the uxplay option
+ -bt709. If you use Ubuntu 22.10 or earlier, GStreamer must be
[patched](https://github.com/FDH2/UxPlay/wiki/Gstreamer-Video4Linux2-plugin-patches)
to use hardware video decoding by the Broadcom GPU (also recommended
- but optional for Raspberry Pi OS (Bullseye): use option
- "`uxplay -bt709`" if you do not use the patch).
+ but optional for Raspberry Pi OS (Bullseye): the patched GStreamer
+ does not need option " -bt709\`". The need for -bt709 when hardware
+ video decoding is used seems to have reappeared starting with
+ GStreamer-1.22.
To (easily) compile the latest UxPlay from source, see the section
[Getting UxPlay](#getting-uxplay).
@@ -574,6 +577,13 @@ what is available. Some possibilites on Linux/\*BSD are:
- If the server is "headless" (no attached monitor, renders audio
only) use `-vs 0`.
+Note that videosink options can set using quoted arguments to -vs:
+*e.g.*, `-vs "xvimagesink display=:0"`: ximagesink and xvimagesink allow
+an X11 display name to be specified, and waylandsink has a similar
+option. Videosink options ("properties") can be found in their GStreamer
+description pages,such as
+https://gstreamer.freedesktop.org/documentation/xvimagesink .
+
GStreamer also searches for the best "audiosink"; override its choice
with `-as `. Choices on Linux include pulsesink, alsasink,
pipewiresink, oss4sink; see what is available with
@@ -622,6 +632,9 @@ See [Usage](#usage) for more run-time options.
-v4l2); it is still better to apply the full patch from the UxPlay
Wiki in this case.
+- **It appears that when hardware h264 video decoding is used, the
+ option -bt709 became needed again in GStreamer-1.22 and later.**
+
- For "double-legacy" Raspberry Pi OS (Buster), there is no patch for
GStreamer-1.14. Instead, first build a complete newer
GStreamer-1.18.6 from source using [these
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 889535f4..15c9c955 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -571,7 +571,7 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void
"*** GStreamer may be trying to use non-functional hardware h264 video decoding.\n"
"*** Try using option -avdec to force software decoding or use -vs \n"
"*** to select a videosink of your choice (see \"man uxplay\").\n\n"
- "*** Raspberry Pi OS with (unpatched) GStreamer-1.18.4 needs \"-bt709\" uxplay option");
+ "*** Raspberry Pi models 4B and earlier using Video4Linux2 may need \"-bt709\" uxplay option");
}
g_error_free (err);
g_free (debug);
diff --git a/uxplay.1 b/uxplay.1
index 4c391785..8b799655 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -85,7 +85,7 @@ UxPlay 1.71: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-v4l2\fR Use Video4Linux2 for GPU hardware h264 video decoding.
.TP
-\fB\-bt709\fR Sometimes needed for Raspberry Pi with GStreamer < 1.22
+\fB\-bt709\fR Sometimes needed for Raspberry Pi models using Video4Linux2.
.TP
\fB\-as\fI sink\fR Choose the GStreamer audiosink; default "autoaudiosink"
.IP
diff --git a/uxplay.cpp b/uxplay.cpp
index 3246ffdb..f7a60746 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -640,7 +640,7 @@ static void print_info (char *name) {
printf(" gtksink,waylandsink,osxvideosink,kmssink,d3d11videosink etc.\n");
printf("-vs 0 Streamed audio only, with no video display window\n");
printf("-v4l2 Use Video4Linux2 for GPU hardware h264 decoding\n");
- printf("-bt709 Sometimes needed for Raspberry Pi with GStreamer < 1.22 \n");
+ printf("-bt709 Sometimes needed for Raspberry Pi models using Video4Linux2 \n");
printf("-as ... Choose the GStreamer audiosink; default \"autoaudiosink\"\n");
printf(" some choices:pulsesink,alsasink,pipewiresink,jackaudiosink,\n");
printf(" osssink,oss4sink,osxaudiosink,wasapisink,directsoundsink.\n");
From 26640d91feaae860e62d589d819b4080eaac1db3 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sat, 14 Dec 2024 11:54:04 -0500
Subject: [PATCH 19/23] video_renderer: fix a regression from update to support
HLS
---
renderers/video_renderer.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 15c9c955..5188914f 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -484,10 +484,10 @@ void video_renderer_stop() {
}
}
-void video_renderer_destroy() {
+static void video_renderer_destroy_h26x(video_renderer_t *renderer) {
if (renderer) {
GstState state;
- gst_element_get_state(renderer->pipeline, &state, NULL, 0);
+ gst_element_get_state(renderer->pipeline, &state, NULL, 100 * GST_MSECOND);
if (state != GST_STATE_NULL) {
if (!hls_video) {
gst_app_src_end_of_stream (GST_APP_SRC(renderer->appsrc));
@@ -510,6 +510,14 @@ void video_renderer_destroy() {
}
}
+void video_renderer_destroy() {
+ for (int i = 0; i < n_renderers; i++) {
+ if (renderer_type[i]) {
+ video_renderer_destroy_h26x(renderer_type[i]);
+ }
+ }
+}
+
gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void *loop) {
/* identify which pipeline sent the message */
@@ -595,10 +603,9 @@ gboolean gstreamer_pipeline_bus_callback(GstBus *bus, GstMessage *message, void
}
break;
case GST_MESSAGE_STATE_CHANGED:
-ESSAGE_STATE_CHANGED:
if (renderer_type[type]->state_pending && strstr(GST_MESSAGE_SRC_NAME(message), "pipeline")) {
GstState state;
- gst_element_get_state(renderer_type[type]->pipeline, &state, NULL,0);
+ gst_element_get_state(renderer_type[type]->pipeline, &state, NULL, 100 * GST_MSECOND);
if (state == GST_STATE_NULL) {
gst_element_set_state(renderer_type[type]->pipeline, GST_STATE_PLAYING);
} else if (state == GST_STATE_PLAYING) {
From 88d4126e0aba6de50f428402dfbfde898d41af2a Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Sun, 15 Dec 2024 08:47:11 -0500
Subject: [PATCH 20/23] httpd: provide name of connection type
---
lib/httpd.c | 7 +++++--
lib/httpd.h | 1 +
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/lib/httpd.c b/lib/httpd.c
index 61479282..28f3d3c6 100644
--- a/lib/httpd.c
+++ b/lib/httpd.c
@@ -29,7 +29,6 @@
#include "logger.h"
#include "utils.h"
-
static const char *typename[] = {
[CONNECTION_TYPE_UNKNOWN] = "Unknown",
[CONNECTION_TYPE_RAOP] = "RAOP",
@@ -38,7 +37,6 @@ static const char *typename[] = {
[CONNECTION_TYPE_HLS] = "HLS"
};
-
struct http_connection_s {
int connected;
@@ -69,6 +67,11 @@ struct httpd_s {
int server_fd6;
};
+const char *
+httpd_get_connection_typename (connection_type_t type) {
+ return typename[type];
+}
+
int
httpd_get_connection_socket (httpd_t *httpd, void *user_data) {
for (int i = 0; i < httpd->max_connections; i++) {
diff --git a/lib/httpd.h b/lib/httpd.h
index e00a950c..1df0e6b9 100644
--- a/lib/httpd.h
+++ b/lib/httpd.h
@@ -44,6 +44,7 @@ int httpd_set_connection_type (httpd_t *http, void *user_data, connection_type_t
int httpd_count_connection_type (httpd_t *http, connection_type_t type);
int httpd_get_connection_socket (httpd_t *httpd, void *user_data);
int httpd_get_connection_socket_by_type (httpd_t *httpd, connection_type_t type, int instance);
+const char *httpd_get_connection_typename (connection_type_t type);
void *httpd_get_connection_by_type (httpd_t *httpd, connection_type_t type, int instance);
httpd_t *httpd_init(logger_t *logger, httpd_callbacks_t *callbacks, int nohold);
From ee831a676b847b8767d25caeadd9a18723f52605 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Mon, 16 Dec 2024 18:52:56 -0500
Subject: [PATCH 21/23] update spec file to 1.71.1
---
uxplay.spec | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/uxplay.spec b/uxplay.spec
index 6e9c7dc1..9dd17df6 100644
--- a/uxplay.spec
+++ b/uxplay.spec
@@ -1,5 +1,5 @@
Name: uxplay
-Version: 1.71
+Version: 1.71.1
Release: 1%{?dist}
%global gittag v%{version}
From 47783de5de462ab34a0ba32464ecd62f3b7f3a18 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Tue, 17 Dec 2024 20:46:40 -0500
Subject: [PATCH 22/23] remove unused variable
---
lib/http_handlers.h | 2 --
1 file changed, 2 deletions(-)
diff --git a/lib/http_handlers.h b/lib/http_handlers.h
index a47f4827..3db7fdca 100644
--- a/lib/http_handlers.h
+++ b/lib/http_handlers.h
@@ -454,9 +454,7 @@ char *adjust_yt_condensed_playlist(const char *media_playlist) {
memcpy(new_pos, old_pos, len);
old_pos += len;
new_pos += len;
- int counter = 0;
while (ptr) {
- counter++;
/* for each chunk */
const char *end = NULL;
char *start = strstr(ptr, prefix);
From b1fb51033375a6146bc954e297c43147b5dde091 Mon Sep 17 00:00:00 2001
From: "F. Duncanh"
Date: Thu, 19 Dec 2024 21:04:51 -0500
Subject: [PATCH 23/23] fix Full vs Restricted color issue by conversion to
sRGB
thanks to @PancakeTAS for this discovery
---
CMakeLists.txt | 4 +++-
README.html | 14 ++++++++++++--
README.md | 15 +++++++++++++--
README.txt | 15 +++++++++++++--
renderers/video_renderer.c | 6 +++---
uxplay.1 | 6 ++++++
uxplay.cpp | 29 +++++++++++++++++++++++++++--
7 files changed, 77 insertions(+), 12 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 03cd02d9..b03bbd6f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -32,7 +32,9 @@ if ( ( UNIX AND NOT APPLE ) OR USE_X11 )
endif()
if( UNIX AND NOT APPLE )
- add_definitions( -DSUPPRESS_AVAHI_COMPAT_WARNING )
+ add_definitions( -DSUPPRESS_AVAHI_COMPAT_WARNING )
+ # convert AirPlay colormap 1:3:7:1 to sRGB (1:1:7:1), needed on Linux and BSD
+ add_definitions( -DFULL_RANGE_RGB_FIX )
else()
set( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE )
endif()
diff --git a/README.html b/README.html
index 48a88d47..a441989d 100644
--- a/README.html
+++ b/README.html
@@ -1107,8 +1107,18 @@ Usage
-bt709 A workaround for the failure of the older
Video4Linux2 plugin to recognize Apple’s use of an uncommon (but
permitted) “full-range color” variant of the bt709 color standard for
-digital TV. This is no longer needed by GStreamer-1.20.4 and backports
-from it.
+digital TV. This was no longer needed by GStreamer-1.20.4 and backports
+from it, but appears to again be required in GStreamer-1.22 and
+later.
+-srgb A workaround for a failure to display
+full-range 8-bit color [0-255], and instead restrict to limited range
+[16-235] “legal BT709” HDTV format. The workaround works on x86_64
+desktop systems, but does not yet work on Raspberry Pi. The issue may be
+fixed in a future GStreamer release: it only occurs in Linux and
+*BSD.
+-srbg no. Disables the -srgb option, which is
+enabled by default in Linux and *BSD, but may be useless on Raspberry
+Pi, and may be unwanted, as it adds extra processing load.
-rpi Equivalent to “-v4l2” (Not valid for Raspberry
Pi model 5, and removed in UxPlay 1.67)
-rpigl Equivalent to “-rpi -vs glimagesink”.
diff --git a/README.md b/README.md
index 2d0fd7a7..ab3f8723 100644
--- a/README.md
+++ b/README.md
@@ -1121,8 +1121,19 @@ Video4Linux2. Equivalent to `-vd v4l2h264dec -vc v4l2convert`.
**-bt709** A workaround for the failure of the older Video4Linux2 plugin
to recognize Apple's use of an uncommon (but permitted) "full-range
-color" variant of the bt709 color standard for digital TV. This is no
-longer needed by GStreamer-1.20.4 and backports from it.
+color" variant of the bt709 color standard for digital TV. This was no
+longer needed by GStreamer-1.20.4 and backports from it, but appears to
+again be required in GStreamer-1.22 and later.
+
+**-srgb** A workaround for a failure to display full-range 8-bit color
+[0-255], and instead restrict to limited range [16-235] "legal BT709"
+HDTV format. The workaround works on x86_64 desktop systems, but
+does not yet work on Raspberry Pi. The issue may be fixed in a future GStreamer
+release: it only occurs in Linux and \*BSD.
+
+**-srbg no**. Disables the -srgb option, which is enabled by default in
+Linux and *BSD, but may be useless on Raspberry Pi, and may be unwanted,
+as it adds extra processing load.
**-rpi** Equivalent to "-v4l2" (Not valid for Raspberry Pi model 5, and
removed in UxPlay 1.67)
diff --git a/README.txt b/README.txt
index 0ee38e98..01ed6877 100644
--- a/README.txt
+++ b/README.txt
@@ -1122,8 +1122,19 @@ Video4Linux2. Equivalent to `-vd v4l2h264dec -vc v4l2convert`.
**-bt709** A workaround for the failure of the older Video4Linux2 plugin
to recognize Apple's use of an uncommon (but permitted) "full-range
-color" variant of the bt709 color standard for digital TV. This is no
-longer needed by GStreamer-1.20.4 and backports from it.
+color" variant of the bt709 color standard for digital TV. This was no
+longer needed by GStreamer-1.20.4 and backports from it, but appears to
+again be required in GStreamer-1.22 and later.
+
+**-srgb** A workaround for a failure to display full-range 8-bit color
+\[0-255\], and instead restrict to limited range \[16-235\] "legal
+BT709" HDTV format. The workaround works on x86_64 desktop systems, but
+does not yet work on Raspberry Pi. The issue may be fixed in a future
+GStreamer release: it only occurs in Linux and \*BSD.
+
+**-srbg no**. Disables the -srgb option, which is enabled by default in
+Linux and \*BSD, but may be useless on Raspberry Pi, and may be
+unwanted, as it adds extra processing load.
**-rpi** Equivalent to "-v4l2" (Not valid for Raspberry Pi model 5, and
removed in UxPlay 1.67)
diff --git a/renderers/video_renderer.c b/renderers/video_renderer.c
index 5188914f..bd7895c4 100644
--- a/renderers/video_renderer.c
+++ b/renderers/video_renderer.c
@@ -128,14 +128,14 @@ static void append_videoflip (GString *launch, const videoflip_t *flip, const vi
}
}
-/* apple uses colorimetry=1:3:5:1 *
+/* apple uses colorimetry that is detected as 1:3:7:1 * //previously 1:3:5:1 was seen
* (not recognized by v4l2 plugin in Gstreamer < 1.20.4) *
* See .../gst-libs/gst/video/video-color.h in gst-plugins-base *
* range = 1 -> GST_VIDEO_COLOR_RANGE_0_255 ("full RGB") *
* matrix = 3 -> GST_VIDEO_COLOR_MATRIX_BT709 *
- * transfer = 5 -> GST_VIDEO_TRANSFER_BT709 *
+ * transfer = 7 -> GST_VIDEO_TRANSFER_SRGB * // previously GST_VIDEO_TRANSFER_BT709
* primaries = 1 -> GST_VIDEO_COLOR_PRIMARIES_BT709 *
- * closest used by GStreamer < 1.20.4 is BT709, 2:3:5:1 with * *
+ * closest used by GStreamer < 1.20.4 is BT709, 2:3:5:1 with * // now use sRGB = 1:1:7:1
* range = 2 -> GST_VIDEO_COLOR_RANGE_16_235 ("limited RGB") */
static const char h264_caps[]="video/x-h264,stream-format=(string)byte-stream,alignment=(string)au";
diff --git a/uxplay.1 b/uxplay.1
index 8b799655..ee8d3f6c 100644
--- a/uxplay.1
+++ b/uxplay.1
@@ -87,6 +87,12 @@ UxPlay 1.71: An open\-source AirPlay mirroring (+ audio streaming) server:
.TP
\fB\-bt709\fR Sometimes needed for Raspberry Pi models using Video4Linux2.
.TP
+\fB\-srgb\fR Display "Full range" [0-255] color, not "Limited Range"[16-235]
+.IP
+ This is a workaround for a GStreamer problem, until it is fixed.
+.PP
+\fB\-srgb\fR no Disable srgb option (use when enabled by default: Linux, *BSD)
+.TP
\fB\-as\fI sink\fR Choose the GStreamer audiosink; default "autoaudiosink"
.IP
choices:pulsesink,alsasink,pipewiresink,osssink,oss4sink,
diff --git a/uxplay.cpp b/uxplay.cpp
index f7a60746..b96053d3 100644
--- a/uxplay.cpp
+++ b/uxplay.cpp
@@ -72,6 +72,12 @@
#define HIGHEST_PORT 65535
#define NTP_TIMEOUT_LIMIT 5
#define BT709_FIX "capssetter caps=\"video/x-h264, colorimetry=bt709\""
+#define SRGB_FIX " ! video/x-raw,colorimetry=sRGB,format=RGB ! "
+#ifdef FULL_RANGE_RGB_FIX
+ #define DEFAULT_SRGB_FIX true
+#else
+ #define DEFAULT_SRGB_FIX false
+#endif
static std::string server_name = DEFAULT_NAME;
static dnssd_t *dnssd = NULL;
@@ -122,6 +128,7 @@ static unsigned short display[5] = {0}, tcp[3] = {0}, udp[3] = {0};
static bool debug_log = DEFAULT_DEBUG_LOG;
static int log_level = LOGGER_INFO;
static bool bt709_fix = false;
+static bool srgb_fix = DEFAULT_SRGB_FIX;
static int nohold = 0;
static bool nofreeze = false;
static unsigned short raop_port;
@@ -640,7 +647,10 @@ static void print_info (char *name) {
printf(" gtksink,waylandsink,osxvideosink,kmssink,d3d11videosink etc.\n");
printf("-vs 0 Streamed audio only, with no video display window\n");
printf("-v4l2 Use Video4Linux2 for GPU hardware h264 decoding\n");
- printf("-bt709 Sometimes needed for Raspberry Pi models using Video4Linux2 \n");
+ printf("-bt709 Sometimes needed for Raspberry Pi models using Video4Linux2 \n");
+ printf("-srgb Display \"Full range\" [0-255] color, not \"Limited Range\"[16-235]\n");
+ printf(" This is a workaround for a GStreamer problem, until it is fixed\n");
+ printf("-srgb no Disable srgb option (use when enabled by default: Linux, *BSD)\n");
printf("-as ... Choose the GStreamer audiosink; default \"autoaudiosink\"\n");
printf(" some choices:pulsesink,alsasink,pipewiresink,jackaudiosink,\n");
printf(" osssink,oss4sink,osxaudiosink,wasapisink,directsoundsink.\n");
@@ -1003,7 +1013,7 @@ static void parse_arguments (int argc, char *argv[]) {
fprintf(stderr," -rpifb was equivalent to \"-v4l2 -vs kmssink\"\n");
fprintf(stderr," -rpigl was equivalent to \"-v4l2 -vs glimagesink\"\n");
fprintf(stderr," -rpiwl was equivalent to \"-v4l2 -vs waylandsink\"\n");
- fprintf(stderr," for GStreamer < 1.22, \"-bt709\" may also be needed\n");
+ fprintf(stderr," Option \"-bt709\" may also be needed for R Pi model 4B and earlier\n");
exit(1);
} else if (arg == "-fs" ) {
fullscreen = true;
@@ -1078,6 +1088,15 @@ static void parse_arguments (int argc, char *argv[]) {
}
} else if (arg == "-bt709") {
bt709_fix = true;
+ } else if (arg == "-srgb") {
+ srgb_fix = true;
+ if (i < argc - 1) {
+ if (strlen(argv[i+1]) == 2 && strncmp(argv[i+1], "no", 2) == 0) {
+ srgb_fix = false;
+ i++;
+ continue;
+ }
+ }
} else if (arg == "-nohold") {
nohold = 1;
} else if (arg == "-al") {
@@ -2170,6 +2189,12 @@ int main (int argc, char *argv[]) {
video_parser.append(BT709_FIX);
}
+ if (srgb_fix && use_video) {
+ std::string option = video_converter;
+ video_converter.append(SRGB_FIX);
+ video_converter.append(option);
+ }
+
if (require_password && registration_list) {
if (pairing_register == "") {
const char * homedir = get_homedir();