mirror of
https://github.com/morgan9e/UxPlay
synced 2026-04-14 00:04:13 +09:00
support for YouTube app HLS video language choice. also fix #452
This commit is contained in:
19
README.html
19
README.html
@@ -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 GStreamer’s "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
|
||||
|
||||
15
README.md
15
README.md
@@ -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
|
||||
|
||||
21
README.txt
21
README.txt
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
33
lib/raop.c
33
lib/raop.c
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
6
uxplay.1
6
uxplay.1
@@ -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)
|
||||
|
||||
34
uxplay.cpp
34
uxplay.cpp
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user