support for YouTube app HLS video language choice. also fix #452

This commit is contained in:
F. Duncanh
2025-11-09 17:22:05 -05:00
parent 940d3286eb
commit 1072692172
11 changed files with 311 additions and 21 deletions

View File

@@ -7,6 +7,13 @@ id="now-developed-at-the-github-site-httpsgithub.comfdh2uxplay-where-all-user-is
developed at the GitHub site <a href="https://github.com/FDH2/UxPlay"
class="uri">https://github.com/FDH2/UxPlay</a> (where ALL user issues
should be posted, and latest versions can be found).</strong></h3>
<p> <strong>NEW on github</strong>: some YouTube app HLS videos now
offer alternative language tracks (generated by AI dubbing). Language
choices will be made in order of preferences set with option -lang (or
by environment variable $LANGUAGE, which it overrides). Format is
<code>-lang fr:es:en</code>, where French (“fr”) is the first choice, if
available, then Spanish (“es”), etc. $LANGUAGE has the same format.
<code>-lang</code> by itself suppresses playing of dubbed audio.</p>
<ul>
<li><p><strong>NEW on github</strong>: Support for <strong>service
discovery using a Bluetooth LE “beacon”</strong> for both Linux/*BSD and
@@ -1115,6 +1122,14 @@ default: 3) allows selection of the version of GStreamers "playbin"
video player to use for playing HLS video. <em>(Playbin v3 is the
recommended player, but if some videos fail to play, you can try with
version 2.)</em></p>
<p><strong>-lang [list]</strong> Specify language preferences for
YouTube app HLS videos, which may offer a choice of language (based on
AI dubbing). If this option is not used, preferences will be taken from
environment variable $LANGUAGE, if set. Both methods specify the
preference order as e.g. list = <code>fr:es:en</code>, fot French (first
choice), Spanish (second choice), and English (third choice).<br />
If “list” is not given (or list = 0), $LANGUAGE is ignored and undubbed
audio is played.</p>
<p><strong>-scrsv n</strong>. (since 1.73) (So far, only implemented on
Linux/*BSD systems using D-Bus). Inhibit the screensaver in the absence
of keyboard input (e.g., while watching video), using the
@@ -1911,7 +1926,9 @@ what version UxPlay claims to be.</p>
screensaver while UxPlay is running (Linux/*BSD only). Add support for
Service Discovery using a Bluetooth LE beacon. Add -vrtp option for
forwarding decrypted h264/5 video to an external renderer (e.g., OBS
Studio). Check that option input strings have valid UTF-8 encoding.</p>
Studio). Check that option input strings have valid UTF-8 encoding. New
option <code>-lang fr:es:en</code> to specify language preferences for
YouTube HLS videos when they offer a choice.</p>
<p>1.72.2 2025-07-07 Fix bug (typo) in DNS_SD advertisement introduced
with -pw option. Update llhttp to v 9.3.0</p>
<p>1.72.1 2025-06-06 minor update: fix regression in -reg option; add

View File

@@ -2,6 +2,10 @@
### **Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).**
-- **NEW on github**: some YouTube app HLS videos now offer alternative language tracks (generated by AI dubbing). Language choices will be made in order of
preferences set with option -lang (or by environment variable $LANGUAGE, which it overrides). Format is `-lang fr:es:en`, where French ("fr") is
the first choice, if available, then Spanish ("es"), etc. $LANGUAGE has the same format. `-lang` by itself suppresses playing of dubbed audio.
- **NEW on github**: Support for **service discovery using a Bluetooth LE "beacon"** for both Linux/\*BSD and Windows (as an alternative to Bonjour/Rendezvous DNS-SD
service discovery). The user must set up a Bluetooth LE "beacon", (a USB 4.0 or later "dongle" can be used). See instructions
below. The beacon runs independently of UxPlay and regularly broadcasts a Bluetooth LE ("Low Energy") 46 byte packet informing nearby iOS/macOS devices of
@@ -1103,6 +1107,14 @@ allows selection of the version of GStreamer's
is the recommended player, but if some videos fail to play, you can try
with version 2.)_
**-lang \[list\]** Specify language preferences for YouTube app HLS videos,
which may offer a choice of language (based on AI dubbing). If this option is not
used, preferences will be taken from environment variable $LANGUAGE, if set. Both
methods specify the preference order as e.g. list = `fr:es:en`, fot French (first
choice), Spanish (second choice), and English (third choice).
If "list" is not given (or list = 0), $LANGUAGE is ignored and undubbed audio is played.
**-scrsv n**. (since 1.73) (So far, only implemented
on Linux/*BSD systems using D-Bus). Inhibit the screensaver in the
absence of keyboard input (e.g., while watching video), using the
@@ -1927,7 +1939,8 @@ specified). (D-Bus based) option -scrsv <n> to inhibit screensaver while UxPlay
is running (Linux/*BSD only). Add support for Service Discovery using a
Bluetooth LE beacon. Add -vrtp option for forwarding decrypted h264/5 video
to an external renderer (e.g., OBS Studio). Check that option input strings
have valid UTF-8 encoding.
have valid UTF-8 encoding. New option `-lang fr:es:en` to specify language
preferences for YouTube HLS videos when they offer a choice.
1.72.2 2025-07-07 Fix bug (typo) in DNS_SD advertisement introduced with -pw
option. Update llhttp to v 9.3.0

View File

@@ -2,6 +2,14 @@
### **Now developed at the GitHub site <https://github.com/FDH2/UxPlay> (where ALL user issues should be posted, and latest versions can be found).**
-- **NEW on github**: some YouTube app HLS videos now offer alternative
language tracks (generated by AI dubbing). Language choices will be made
in order of preferences set with option -lang (or by environment
variable \$LANGUAGE, which it overrides). Format is `-lang fr:es:en`,
where French ("fr") is the first choice, if available, then Spanish
("es"), etc. \$LANGUAGE has the same format. `-lang` by itself
suppresses playing of dubbed audio.
- **NEW on github**: Support for **service discovery using a Bluetooth
LE "beacon"** for both Linux/\*BSD and Windows (as an alternative to
Bonjour/Rendezvous DNS-SD service discovery). The user must set up a
@@ -1136,6 +1144,15 @@ allows selection of the version of GStreamer's \"playbin\" video player
to use for playing HLS video. *(Playbin v3 is the recommended player,
but if some videos fail to play, you can try with version 2.)*
**-lang \[list\]** Specify language preferences for YouTube app HLS
videos, which may offer a choice of language (based on AI dubbing). If
this option is not used, preferences will be taken from environment
variable \$LANGUAGE, if set. Both methods specify the preference order
as e.g. list = `fr:es:en`, fot French (first choice), Spanish (second
choice), and English (third choice).\
If "list" is not given (or list = 0), \$LANGUAGE is ignored and undubbed
audio is played.
**-scrsv n**. (since 1.73) (So far, only implemented on Linux/\*BSD
systems using D-Bus). Inhibit the screensaver in the absence of keyboard
input (e.g., while watching video), using the
@@ -1979,7 +1996,9 @@ file specified). (D-Bus based) option -scrsv `<n>`{=html} to inhibit
screensaver while UxPlay is running (Linux/\*BSD only). Add support for
Service Discovery using a Bluetooth LE beacon. Add -vrtp option for
forwarding decrypted h264/5 video to an external renderer (e.g., OBS
Studio). Check that option input strings have valid UTF-8 encoding.
Studio). Check that option input strings have valid UTF-8 encoding. New
option `-lang fr:es:en` to specify language preferences for YouTube HLS
videos when they offer a choice.
1.72.2 2025-07-07 Fix bug (typo) in DNS_SD advertisement introduced with
-pw option. Update llhttp to v 9.3.0

View File

@@ -37,6 +37,7 @@ struct airplay_video_s {
char apple_session_id[37];
char playback_uuid[37];
char *uri_prefix;
const char *lang;
char local_uri_prefix[23];
int next_uri;
int FCUP_RequestID;
@@ -52,7 +53,7 @@ struct airplay_video_s {
// initialize airplay_video service.
int airplay_video_service_init(raop_t *raop, unsigned short http_port,
const char *session_id) {
const char *lang, const char *session_id) {
char uri[] = "http://localhost:xxxxx";
assert(raop);
@@ -68,7 +69,8 @@ int airplay_video_service_init(raop_t *raop, unsigned short http_port,
return -1;
}
/* create local_uri_prefix string */
airplay_video->lang = lang;
/* 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);
@@ -176,11 +178,186 @@ int get_next_media_uri_id(airplay_video_t *airplay_video) {
return airplay_video->next_uri;
}
typedef struct language_s {
char *start;
int len;
char type;
char code[6];
} language_t;
language_t* master_playlist_process_language(char * data, int *slices, int *language_count) {
*language_count = 0;
char *ptr = data;
int count = 0, count1 = 0, count2 = 0, count3 = 0;
while (ptr) {
ptr = strstr(ptr,"#EXT-X-MEDIA:URI=");
if(!ptr) {
break;
}
ptr = strstr(ptr, "LANGUAGE=");
if(!ptr) {
break;
}
ptr = strstr(ptr,"YT-EXT-AUDIO-CONTENT-ID=");
if(!ptr) {
break;
}
count++;
}
if (count == 0) {
return NULL;
}
language_t *languages = (language_t *) calloc(count + 2, sizeof(language_t));
languages[0].start = data;
ptr = data;
for (int i = 1; i <= count; i++) {
char *end;
if (!(ptr = strstr(ptr, "#EXT-X-MEDIA"))) {
break;
}
count1++;
if (i == 1) {
languages[0].len = (int) (ptr - data);
languages[0].type = ' ';
}
languages[i].start = ptr;
if (!(ptr = strstr(ptr, "LANGUAGE="))) {
break;
}
if (!strncmp(ptr - strlen("dubbed-auto") - 2, "dubbed-auto", strlen("dubbed-auto"))) {
languages[i].type = 'd';
} else if (!strncmp(ptr - strlen("original") - 2, "original", strlen("original"))) {
languages[i].type = 'o';
} else {
languages[i].type = 'u';
}
count2++;
if (!(ptr = strchr(ptr,'"'))) {
break;
}
ptr++;
if (!(end = strchr(ptr,'"'))) {
break;
}
strncpy(languages[i].code, ptr, end - ptr);
if (!(ptr = strchr(ptr,'\n'))) {
break;
}
count3++;
languages[i].len = (int) (ptr + 1 - languages[i].start);
}
assert (count1 == count && count2 == count && count3 == count);
languages[count + 1].start = ++ptr;
languages[count + 1].len = strlen(ptr);
languages[count + 1].type = ' ';
*slices = count + 2;
int len = 0;
int copies = 0;
for (int i = 0; i < *slices; i++) {
if (!strcmp(languages[i].code, languages[1].code)) {
copies++;
}
len += languages[i].len;
}
if (copies == count) {
/* only one language is offered, nothing to do */
free (languages);
return NULL;
}
*language_count = count/copies;
assert(count == *language_count * copies);
assert(len == (int) strlen(data));
/* verify expected structure of language choice information */
for (int i = 1; i <= count; i++) {
if (i % *language_count) {
assert(languages[i].type == 'd');
} else {
assert(languages[i].type == 'o');
}
int j = i - *language_count;
if (j > 0) {
assert (!strcmp(languages[i].code, languages[j].code));
}
}
return languages;
}
void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist) {
int language_count, slices;
if (airplay_video->master_playlist) {
free (airplay_video->master_playlist);
}
airplay_video->master_playlist = master_playlist;
language_t *languages;
if (!(languages = master_playlist_process_language(airplay_video->master_playlist,
&slices, &language_count))) {
return;
}
/* audio is offered in multiple languages */
char *str = calloc(6 * language_count, sizeof(char));
int i;
char *ptr = str;
for (i = 0; i < language_count; i++) {
sprintf(ptr,"%s ", languages[i + 1].code);
ptr += strlen(languages[i + 1].code);
ptr++;
if ( i % 16 == 15) {
sprintf(ptr++,"\n");
}
}
if (i % 16 != 15) {
sprintf(ptr++,"\n");
}
printf("%d available languages: %s", language_count, str);
free(str);
const char *ptrc = airplay_video->lang;
char *lang = NULL;
while (ptrc) {
for (int i = 1; i <= language_count; i++) {
if (!strncmp(languages[i].code, ptrc, 2)) {
lang = languages[i].code;
break;
}
}
if (lang) {
break;
}
ptrc = strchr(ptrc,':');
if(ptrc) {
ptrc++;
if (strlen(ptrc) < 2) {
break;
}
}
}
if (lang) {
printf("language choice: %s (based on prefered languages list %s)\n\n",
lang, airplay_video->lang);
} else {
if (airplay_video->lang) {
printf("no match with prefered language list %s\n", airplay_video->lang);
}
lang = languages[language_count].code;
printf("default language choice: %s\n\n", lang);
}
int len = 0;
for (int i = 0; i < slices; i++) {
if (strlen(languages[i].code) == 0 || !strcmp(languages[i].code, lang)) {
len += languages[i].len;
}
}
airplay_video->master_playlist = (char *) calloc(len + 1, sizeof(char));
ptr = airplay_video->master_playlist;
for (int i = 0; i < slices; i++) {
if (strlen(languages[i].code) == 0 || !strcmp(languages[i].code, lang)) {
strncpy(ptr, languages[i].start, languages[i].len);
ptr += languages[i].len;
}
}
free (languages);
free(master_playlist);
}
char *get_master_playlist(airplay_video_t *airplay_video) {

View File

@@ -84,7 +84,7 @@ http_handler_server_info(raop_conn_t *conn, http_request_t *request, http_respon
/* 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);
airplay_video_service_init(conn->raop, conn->raop->port, conn->raop->lang, session_id);
}
@@ -289,7 +289,7 @@ http_handler_playback_info(raop_conn_t *conn, http_request_t *request, http_resp
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);
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls, true);
return;
} else if (playback_info.position == -1.0) {
logger_log(conn->raop->logger, LOGGER_DEBUG, "playback_info not available");
@@ -442,9 +442,9 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t
}
plist_mem_free (remove_uuid);
}
logger_log(conn->raop->logger, LOGGER_ERR, "FIXME: playlist removal not yet implemented");
goto finish;
} else if (playlist_insert) {
logger_log(conn->raop->logger, LOGGER_ERR, "FIXME: playlist insertion not yet implemented");
logger_log(conn->raop->logger, LOGGER_INFO, "unhandled action type playlistInsert (add new playback)");
printf("\n***************FIXME************************\nPlaylist insertion needs more information for it to be implemented:\n"
"please report following output as an \"Issue\" at http://github.com/FDH2/UxPlay:\n");

View File

@@ -82,6 +82,9 @@ struct raop_s {
char *nonce;
char *random_pw;
unsigned char auth_fail_count;
/* used for setting HLS video language choices */
char *lang;
};
struct raop_conn_s {
@@ -233,7 +236,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) {
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);
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls, false);
}
httpd_remove_known_connections(conn->raop->httpd);
} else {
@@ -594,6 +597,8 @@ raop_init(raop_callbacks_t *callbacks) {
raop->hls_support = false;
raop->nonce = NULL;
raop->lang = NULL;
return raop;
}
@@ -660,6 +665,10 @@ raop_destroy(raop_t *raop) {
free(raop->random_pw);
}
if (raop->lang) {
free(raop->lang);
}
free(raop);
/* Cleanup the network */
@@ -767,6 +776,22 @@ raop_set_dnssd(raop_t *raop, dnssd_t *dnssd) {
raop->dnssd = dnssd;
}
void
raop_set_lang(raop_t *raop, const char *lang) {
if (raop->lang) {
free (raop->lang);
raop->lang = NULL;
}
if (lang && strlen(lang)) {
raop->lang = (char *) calloc(strlen(lang) + 1, sizeof(char));
memcpy(raop->lang, lang, strlen(lang) * sizeof(char));
}
}
char *
raop_get_lang(raop_t *raop) {
return raop->lang;
}
int
raop_start_httpd(raop_t *raop, unsigned short *port) {
@@ -785,6 +810,12 @@ void raop_remove_known_connections(raop_t * raop) {
httpd_remove_known_connections(raop->httpd);
}
void raop_remove_hls_connections(raop_t * raop) {
httpd_remove_connections_by_type(raop->httpd, CONNECTION_TYPE_HLS);
httpd_remove_connections_by_type(raop->httpd, CONNECTION_TYPE_PTTH);
httpd_remove_connections_by_type(raop->httpd, CONNECTION_TYPE_AIRPLAY);
}
airplay_video_t *deregister_airplay_video(raop_t *raop) {
airplay_video_t *airplay_video = raop->airplay_video;
raop->airplay_video = NULL;

View File

@@ -69,7 +69,7 @@ struct raop_callbacks_s {
void (*video_resume)(void *cls);
void (*conn_feedback) (void *cls);
void (*conn_reset) (void *cls, int reason);
void (*video_reset) (void *cls);
void (*video_reset) (void *cls, bool hls_shutdown);
/* Optional but recommended callback functions (probably not optional, check this)*/
@@ -108,9 +108,9 @@ raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const c
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);
int airplay_video_service_init(raop_t *raop, unsigned short port, const char *lang, const char *session_id);
bool register_airplay_video(raop_t *raop, airplay_video_t *airplay_video);
char *raop_get_lang(raop_t *raop);
airplay_video_t *get_airplay_video(raop_t *raop);
airplay_video_t *deregister_airplay_video(raop_t *raop);
uint64_t get_local_time();
@@ -121,6 +121,7 @@ RAOP_API void raop_set_log_level(raop_t *raop, int level);
RAOP_API void raop_set_log_callback(raop_t *raop, raop_log_callback_t callback, void *cls);
RAOP_API int raop_set_plist(raop_t *raop, const char *plist_item, const int value);
RAOP_API void raop_set_port(raop_t *raop, unsigned short port);
RAOP_API void raop_set_lang(raop_t *raop, const char *lang);
RAOP_API void raop_set_udp_ports(raop_t *raop, unsigned short port[3]);
RAOP_API void raop_set_tcp_ports(raop_t *raop, unsigned short port[2]);
RAOP_API unsigned short raop_get_port(raop_t *raop);
@@ -131,6 +132,7 @@ RAOP_API void raop_stop_httpd(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_remove_hls_connections(raop_t * raop);
RAOP_API void raop_destroy_airplay_video(raop_t *raop);
#ifdef __cplusplus

View File

@@ -1268,7 +1268,7 @@ raop_handler_teardown(raop_conn_t *conn,
}
}
} else if (teardown_110) {
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls);
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls, false);
if (conn->raop_rtp_mirror) {
/* Stop our video RTP session */
raop_rtp_mirror_stop(conn->raop_rtp_mirror);
@@ -1284,7 +1284,10 @@ raop_handler_teardown(raop_conn_t *conn,
conn->raop_rtp_mirror = NULL;
}
/* shut down any HLS connections */
httpd_remove_connections_by_type(conn->raop->httpd, CONNECTION_TYPE_HLS);
int hls_count = httpd_count_connection_type(conn->raop->httpd, CONNECTION_TYPE_HLS);
if (hls_count) {
conn->raop->callbacks.video_reset(conn->raop->callbacks.cls, true);
}
}
if (conn->raop->callbacks.conn_teardown) {
conn->raop->callbacks.conn_teardown(conn->raop->callbacks.cls, &teardown_96, &teardown_110);

View File

@@ -863,7 +863,7 @@ raop_rtp_mirror_thread(void *arg)
if (unsupported_codec) {
closesocket(raop_rtp_mirror->mirror_data_sock);
raop_rtp_mirror_stop(raop_rtp_mirror);
raop_rtp_mirror->callbacks.video_reset(raop_rtp_mirror->callbacks.cls);
raop_rtp_mirror->callbacks.video_reset(raop_rtp_mirror->callbacks.cls, false);
}
return 0;

View File

@@ -15,10 +15,14 @@ UxPlay 1.72: 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)
\fB\-hls\fR [v] Support HTTP Live Streaming (HLS), YouTube app video only:
.IP
v = 2 or 3 (default 3) optionally selects video player version
.TP
\fB\-lang\fR xx HLS language preferences ("fr:es:..", overrides $LANGUAGE)
.TP
\fB\-lang\fR (or -lang 0): play undubbed HLS version (overrides $LANGUAGE)
.TP
\fB\-scrsv\fI n\fR Screensaver override \fIn\fR:0=off 1=on during activity 2=always on.
.TP
\fB\-pin\fI[xxxx]\fRUse a 4-digit pin code to control client access (default: no)

View File

@@ -175,6 +175,7 @@ static bool h265_support = false;
static int n_video_renderers = 0;
static int n_audio_renderers = 0;
static bool hls_support = false;
static std::string lang = "";
static std::string url = "";
static guint gst_x11_window_id = 0;
static guint progress_id = 0;
@@ -880,8 +881,10 @@ static void print_info (char *name) {
printf("-n name Specify network name of the AirPlay server (UTF-8/ascii)\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 [v] Support HTTP Live Streaming (currently Youtube video only) \n");
printf("-hls [v] Support HTTP Live Streaming (HLS), Youtube app video only: \n");
printf(" v = 2 or 3 (default 3) optionally selects video player version\n");
printf("-lang xx HLS language preferences (\"fr:es:..\", overrides $LANGUAGE)\n");
printf("-lang (or -lang 0): play undubbed HLS version (overrides $LANGUAGE)\n");
printf("-scrsv n Screensaver override n: 0=off 1=on during activity 2=always on\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");
@@ -1666,7 +1669,12 @@ static void parse_arguments (int argc, char *argv[]) {
exit(1);
}
playbin_version = (guint) n;
}
}
} else if (arg == "-lang") {
lang.erase();
if (i < argc - 1 && *argv[i+1] != '-') {
lang = argv[++i];
}
} else if (arg == "-h265") {
h265_support = true;
} else if (arg == "-nofreeze") {
@@ -2035,10 +2043,15 @@ static bool check_blocked_client(char *deviceid) {
// Server callbacks
extern "C" void video_reset(void *cls) {
extern "C" void video_reset(void *cls, bool hls_shutdown) {
LOGD("video_reset");
video_renderer_stop();
url.erase();
if (hls_shutdown) {
url.erase();
raop_destroy_airplay_video(raop);
raop_remove_hls_connections(raop);
preserve_connections = true;
}
remote_clock_offset = 0;
relaunch_video = true;
reset_loop = true;
@@ -2356,7 +2369,7 @@ extern "C" void audio_set_coverart(void *cls, const void *buffer, int buflen) {
extern "C" void audio_stop_coverart_rendering(void *cls) {
if (render_coverart) {
video_reset(cls);
video_reset(cls, false);
}
}
@@ -2731,6 +2744,13 @@ int main (int argc, char *argv[]) {
if (!getenv("AVAHI_COMPAT_NOWARN")) putenv(avahi_compat_nowarn);
#endif
/* for HLS video language preferences */
char *lang_env = getenv("LANGUAGE");
if (lang_env && strlen(lang_env)) {
lang.erase();
lang = lang_env;
}
char *rcfile = NULL;
/* see if option -rc was given */
for (int i = 1; i < argc ; i++) {
@@ -3018,6 +3038,10 @@ int main (int argc, char *argv[]) {
cleanup();
}
if (lang.length() > 1) {
raop_set_lang(raop, lang.c_str());
}
#define PID_MAX 4194304 // 2^22
if (ble_filename.length()) {
#ifdef _WIN32