rework airplay_video to store multiple playlists

This commit is contained in:
F. Duncanh
2025-11-12 09:40:24 -05:00
parent 94535dd7aa
commit dae1ed108f
4 changed files with 76 additions and 75 deletions

View File

@@ -52,21 +52,17 @@ struct airplay_video_s {
}; };
// initialize airplay_video service. // initialize airplay_video service.
int airplay_video_service_init(raop_t *raop, unsigned short http_port, airplay_video_t *airplay_video_service_init(raop_t *raop, unsigned short http_port,
const char *lang, const char *session_id) { const char *lang, const char *session_id) {
char uri[] = "http://localhost:xxxxx"; char uri[] = "http://localhost:xxxxx";
assert(raop); assert(raop);
airplay_video_t *airplay_video = deregister_airplay_video(raop);
if (airplay_video) {
airplay_video_service_destroy(airplay_video);
}
/* calloc guarantees that the 36-character strings apple_session_id and /* calloc guarantees that the 36-character strings apple_session_id and
playback_uuid are null-terminated */ playback_uuid are null-terminated */
airplay_video = (airplay_video_t *) calloc(1, sizeof(airplay_video_t)); airplay_video_t *airplay_video = (airplay_video_t *) calloc(1, sizeof(airplay_video_t));
if (!airplay_video) { if (!airplay_video) {
return -1; return NULL;
} }
airplay_video->lang = lang; airplay_video->lang = lang;
@@ -79,10 +75,6 @@ int airplay_video_service_init(raop_t *raop, unsigned short http_port,
*ptr = '\0'; *ptr = '\0';
} }
if (!register_airplay_video(raop, airplay_video)) {
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; airplay_video->raop = raop;
@@ -99,7 +91,7 @@ int airplay_video_service_init(raop_t *raop, unsigned short http_port,
airplay_video->master_playlist = NULL; airplay_video->master_playlist = NULL;
airplay_video->num_uri = 0; airplay_video->num_uri = 0;
airplay_video->next_uri = 0; airplay_video->next_uri = 0;
return 0; return airplay_video;
} }
// destroy the airplay_video service // destroy the airplay_video service

View File

@@ -84,8 +84,28 @@ http_handler_server_info(raop_conn_t *conn, http_request_t *request, http_respon
/* initialize the airplay video service */ /* initialize the airplay video service */
const char *session_id = http_request_get_header(request, "X-Apple-Session-ID"); const char *session_id = http_request_get_header(request, "X-Apple-Session-ID");
airplay_video_service_init(conn->raop, conn->raop->port, conn->raop->lang, session_id); int id = -1;
for (int i = 0; i < MAX_AIRPLAY_VIDEO; i++) {
if (conn->raop->airplay_video[i]) {
continue;
}
id = i;
break;
}
if (id == -1) {
logger_log(conn->raop->logger, LOGGER_ERR, "no unused airplay_video structures are available"
" MAX_AIRPLAY_VIDEO = %d\n", MAX_AIRPLAY_VIDEO);
exit(1);
}
airplay_video_t *airplay_video = airplay_video_service_init(conn->raop, conn->raop->port, conn->raop->lang, session_id);
if (airplay_video) {
conn->raop->current_video = id;
conn->raop->airplay_video[id] = airplay_video;
} else {
logger_log(conn->raop->logger, LOGGER_ERR, "failed to allocate airplay_video[%d]\n", id);
exit(-1);
}
} }
static void static void
@@ -123,7 +143,7 @@ http_handler_rate(raop_conn_t *conn, http_request_t *request, http_response_t *r
float value = strtof(rate, &end); float value = strtof(rate, &end);
if (end && end != rate) { if (end && end != rate) {
rate_value = value; rate_value = value;
logger_log(conn->raop->logger, LOGGER_DEBUG, "http_handler_rate: got rate = %.6f", rate_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); conn->raop->callbacks.on_video_rate(conn->raop->callbacks.cls, rate_value);
@@ -360,6 +380,7 @@ static void
http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t *response, http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen) { char **response_data, int *response_datalen) {
airplay_video_t *airplay_video = conn->raop->airplay_video[conn->raop->current_video];
bool data_is_plist = false; bool data_is_plist = false;
plist_t req_root_node = NULL; plist_t req_root_node = NULL;
uint64_t uint_val; uint64_t uint_val;
@@ -372,7 +393,7 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t
logger_log(conn->raop->logger, LOGGER_ERR, "Play request had no X-Apple-Session-ID"); logger_log(conn->raop->logger, LOGGER_ERR, "Play request had no X-Apple-Session-ID");
goto post_action_error; goto post_action_error;
} }
const char *apple_session_id = get_apple_session_id(conn->raop->airplay_video); const char *apple_session_id = get_apple_session_id(airplay_video);
if (strcmp(session_id, apple_session_id)){ 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\"", logger_log(conn->raop->logger, LOGGER_ERR, "X-Apple-Session-ID has changed:\n was:\"%s\"\n now:\"%s\"",
apple_session_id, session_id); apple_session_id, session_id);
@@ -433,7 +454,7 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t
plist_t req_params_item_uuid_node = plist_dict_get_item(req_params_item_node, "uuid"); plist_t req_params_item_uuid_node = plist_dict_get_item(req_params_item_node, "uuid");
char* remove_uuid = NULL; char* remove_uuid = NULL;
plist_get_string_val(req_params_item_uuid_node, &remove_uuid); plist_get_string_val(req_params_item_uuid_node, &remove_uuid);
const char *playback_uuid = get_playback_uuid(conn->raop->airplay_video); const char *playback_uuid = get_playback_uuid(airplay_video);
if (strcmp(remove_uuid, playback_uuid)) { if (strcmp(remove_uuid, playback_uuid)) {
logger_log(conn->raop->logger, LOGGER_ERR, "uuid of playlist removal action request did not match current playlist:\n" logger_log(conn->raop->logger, LOGGER_ERR, "uuid of playlist removal action request did not match current playlist:\n"
" current: %s\n remove: %s", playback_uuid, remove_uuid); " current: %s\n remove: %s", playback_uuid, remove_uuid);
@@ -557,25 +578,25 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t
char *ptr = strstr(fcup_response_url, "/master.m3u8"); char *ptr = strstr(fcup_response_url, "/master.m3u8");
if (ptr) { if (ptr) {
/* this is a master playlist */ /* this is a master playlist */
char *uri_prefix = get_uri_prefix(conn->raop->airplay_video); char *uri_prefix = get_uri_prefix(airplay_video);
char ** media_data_store = NULL; char ** media_data_store = NULL;
int num_uri = 0; int num_uri = 0;
char *uri_local_prefix = get_uri_local_prefix(conn->raop->airplay_video); char *uri_local_prefix = get_uri_local_prefix(airplay_video);
char *new_master = adjust_master_playlist (fcup_response_data, fcup_response_datalen, uri_prefix, uri_local_prefix); 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); store_master_playlist(airplay_video, new_master);
create_media_uri_table(uri_prefix, fcup_response_data, fcup_response_datalen, &media_data_store, &num_uri); 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); create_media_data_store(airplay_video, media_data_store, num_uri);
num_uri = get_num_media_uri(conn->raop->airplay_video); num_uri = get_num_media_uri(airplay_video);
set_next_media_uri_id(conn->raop->airplay_video, 0); set_next_media_uri_id(airplay_video, 0);
} else { } else {
/* this is a media playlist */ /* this is a media playlist */
assert(fcup_response_data); assert(fcup_response_data);
char *playlist = (char *) calloc(fcup_response_datalen + 1, sizeof(char)); char *playlist = (char *) calloc(fcup_response_datalen + 1, sizeof(char));
memcpy(playlist, fcup_response_data, fcup_response_datalen); memcpy(playlist, fcup_response_data, fcup_response_datalen);
int uri_num = get_next_media_uri_id(conn->raop->airplay_video); int uri_num = get_next_media_uri_id(airplay_video);
--uri_num; // (next num is current num + 1) --uri_num; // (next num is current num + 1)
store_media_playlist(conn->raop->airplay_video, playlist, uri_num); store_media_playlist(airplay_video, playlist, uri_num);
float duration = 0.0f; float duration = 0.0f;
int count = analyze_media_playlist(playlist, &duration); int count = analyze_media_playlist(playlist, &duration);
if (count) { if (count) {
@@ -590,18 +611,18 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t
} }
plist_mem_free(fcup_response_url); plist_mem_free(fcup_response_url);
int num_uri = get_num_media_uri(conn->raop->airplay_video); int num_uri = get_num_media_uri(airplay_video);
int uri_num = get_next_media_uri_id(conn->raop->airplay_video); int uri_num = get_next_media_uri_id(airplay_video);
if (uri_num < num_uri) { if (uri_num < num_uri) {
fcup_request((void *) conn, get_media_uri_by_num(conn->raop->airplay_video, uri_num), fcup_request((void *) conn, get_media_uri_by_num(airplay_video, uri_num),
apple_session_id, apple_session_id,
get_next_FCUP_RequestID(conn->raop->airplay_video)); get_next_FCUP_RequestID(airplay_video));
set_next_media_uri_id(conn->raop->airplay_video, ++uri_num); set_next_media_uri_id(airplay_video, ++uri_num);
} else { } else {
char * uri_local_prefix = get_uri_local_prefix(conn->raop->airplay_video); char * uri_local_prefix = get_uri_local_prefix(airplay_video);
conn->raop->callbacks.on_video_play(conn->raop->callbacks.cls, conn->raop->callbacks.on_video_play(conn->raop->callbacks.cls,
strcat(uri_local_prefix, "/master.m3u8"), strcat(uri_local_prefix, "/master.m3u8"),
get_start_position_seconds(conn->raop->airplay_video)); get_start_position_seconds(airplay_video));
} }
finish: finish:
@@ -626,6 +647,7 @@ static void
http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *response, http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen) { char **response_data, int *response_datalen) {
airplay_video_t *airplay_video = conn->raop->airplay_video[conn->raop->current_video];
char* playback_location = NULL; char* playback_location = NULL;
char* client_proc_name = NULL; char* client_proc_name = NULL;
plist_t req_root_node = NULL; plist_t req_root_node = NULL;
@@ -642,7 +664,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r
logger_log(conn->raop->logger, LOGGER_ERR, "Play request had no X-Apple-Session-ID"); logger_log(conn->raop->logger, LOGGER_ERR, "Play request had no X-Apple-Session-ID");
goto play_error; goto play_error;
} }
const char *apple_session_id = get_apple_session_id(conn->raop->airplay_video); const char *apple_session_id = get_apple_session_id(airplay_video);
if (strcmp(session_id, apple_session_id)){ 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\"", logger_log(conn->raop->logger, LOGGER_ERR, "X-Apple-Session-ID has changed:\n was:\"%s\"\n now:\"%s\"",
apple_session_id, session_id); apple_session_id, session_id);
@@ -684,7 +706,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r
} else { } else {
char* playback_uuid = NULL; char* playback_uuid = NULL;
plist_get_string_val(req_uuid_node, &playback_uuid); plist_get_string_val(req_uuid_node, &playback_uuid);
set_playback_uuid(conn->raop->airplay_video, playback_uuid); set_playback_uuid(airplay_video, playback_uuid);
plist_mem_free (playback_uuid); plist_mem_free (playback_uuid);
} }
@@ -715,7 +737,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r
plist_get_real_val(req_start_position_seconds_node, &start_position); plist_get_real_val(req_start_position_seconds_node, &start_position);
start_position_seconds = (float) start_position; start_position_seconds = (float) start_position;
} }
set_start_position_seconds(conn->raop->airplay_video, (float) start_position_seconds); set_start_position_seconds(airplay_video, (float) start_position_seconds);
} }
char *ptr = strstr(playback_location, "/master.m3u8"); char *ptr = strstr(playback_location, "/master.m3u8");
@@ -724,9 +746,9 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r
goto play_error; goto play_error;
} }
int prefix_len = (int) (ptr - playback_location); int prefix_len = (int) (ptr - playback_location);
set_uri_prefix(conn->raop->airplay_video, playback_location, prefix_len); set_uri_prefix(airplay_video, playback_location, prefix_len);
set_next_media_uri_id(conn->raop->airplay_video, 0); set_next_media_uri_id(airplay_video, 0);
fcup_request((void *) conn, playback_location, apple_session_id, get_next_FCUP_RequestID(conn->raop->airplay_video)); fcup_request((void *) conn, playback_location, apple_session_id, get_next_FCUP_RequestID(airplay_video));
plist_mem_free(playback_location); plist_mem_free(playback_location);
@@ -757,6 +779,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r
static void static void
http_handler_hls(raop_conn_t *conn, http_request_t *request, http_response_t *response, http_handler_hls(raop_conn_t *conn, http_request_t *request, http_response_t *response,
char **response_data, int *response_datalen) { char **response_data, int *response_datalen) {
airplay_video_t *airplay_video = conn->raop->airplay_video[conn->raop->current_video];
const char *method = http_request_get_method(request); const char *method = http_request_get_method(request);
assert (!strcmp(method, "GET")); assert (!strcmp(method, "GET"));
const char *url = http_request_get_url(request); const char *url = http_request_get_url(request);
@@ -772,7 +795,7 @@ http_handler_hls(raop_conn_t *conn, http_request_t *request, http_response_t *r
} }
if (!strcmp(url, "/master.m3u8")){ if (!strcmp(url, "/master.m3u8")){
char * master_playlist = get_master_playlist(conn->raop->airplay_video); char * master_playlist = get_master_playlist(airplay_video);
if (master_playlist) { if (master_playlist) {
size_t len = strlen(master_playlist); size_t len = strlen(master_playlist);
char * data = (char *) malloc(len + 1); char * data = (char *) malloc(len + 1);
@@ -786,7 +809,7 @@ http_handler_hls(raop_conn_t *conn, http_request_t *request, http_response_t *r
} }
} else { } else {
char *media_playlist = get_media_playlist(conn->raop->airplay_video, url); char *media_playlist = get_media_playlist(airplay_video, url);
if (media_playlist) { if (media_playlist) {
char *data = adjust_yt_condensed_playlist(media_playlist); char *data = adjust_yt_condensed_playlist(media_playlist);
*response_data = data; *response_data = data;

View File

@@ -73,8 +73,10 @@ struct raop_s {
char pk_str[2*ED25519_KEY_SIZE + 1]; char pk_str[2*ED25519_KEY_SIZE + 1];
/* place to store media_data_store */ /* place to store media_data_store */
airplay_video_t *airplay_video; airplay_video_t *airplay_video[MAX_AIRPLAY_VIDEO];
int current_video;
int removed_video;
/* activate support for HLS live streaming */ /* activate support for HLS live streaming */
bool hls_support; bool hls_support;
@@ -94,7 +96,6 @@ struct raop_conn_s {
raop_rtp_mirror_t *raop_rtp_mirror; raop_rtp_mirror_t *raop_rtp_mirror;
fairplay_t *fairplay; fairplay_t *fairplay;
pairing_session_t *session; pairing_session_t *session;
airplay_video_t *airplay_video;
unsigned char *local; unsigned char *local;
int locallen; int locallen;
@@ -165,7 +166,6 @@ conn_init(void *opaque, unsigned char *local, int locallen, unsigned char *remot
conn->connection_type = CONNECTION_TYPE_UNKNOWN; conn->connection_type = CONNECTION_TYPE_UNKNOWN;
conn->client_session_id = NULL; conn->client_session_id = NULL;
conn->airplay_video = NULL;
conn->authenticated = false; conn->authenticated = false;
@@ -535,9 +535,6 @@ conn_destroy(void *ptr) {
if (conn->client_session_id) { if (conn->client_session_id) {
free(conn->client_session_id); free(conn->client_session_id);
} }
if (conn->airplay_video) {
airplay_video_service_destroy(conn->airplay_video);
}
free(conn); free(conn);
} }
@@ -592,6 +589,13 @@ raop_init(raop_callbacks_t *callbacks) {
/* initialize switch for display of client's streaming data records */ /* initialize switch for display of client's streaming data records */
raop->clientFPSdata = 0; raop->clientFPSdata = 0;
/* initialize airplay_video */
raop->current_video = -1;
raop->removed_video = -1;
for (int i= 0; i < MAX_AIRPLAY_VIDEO; i++) {
raop->airplay_video[i] = NULL;
}
raop->audio_delay_micros = 250000; raop->audio_delay_micros = 250000;
raop->hls_support = false; raop->hls_support = false;
@@ -816,28 +820,12 @@ void raop_remove_hls_connections(raop_t * raop) {
httpd_remove_connections_by_type(raop->httpd, CONNECTION_TYPE_AIRPLAY); 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;
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) { void raop_destroy_airplay_video(raop_t *raop) {
if (raop->airplay_video) { for (int i = 0; i < MAX_AIRPLAY_VIDEO; i++) {
airplay_video_service_destroy(raop->airplay_video); if (raop->airplay_video[i]) {
raop->airplay_video = NULL; airplay_video_service_destroy(raop->airplay_video[i]);
raop->airplay_video[i] = NULL;
}
} }
} }

View File

@@ -23,7 +23,8 @@
#include "raop_ntp.h" #include "raop_ntp.h"
#include "airplay_video.h" #include "airplay_video.h"
# define RAOP_API #define RAOP_API
#define MAX_AIRPLAY_VIDEO 2
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@@ -104,11 +105,8 @@ raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const c
int remote_addr_len, unsigned short timing_rport, int remote_addr_len, unsigned short timing_rport,
timing_protocol_t *time_protocol); timing_protocol_t *time_protocol);
int airplay_video_service_init(raop_t *raop, unsigned short port, const char *lang, const char *session_id); airplay_video_t *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); 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(); uint64_t get_local_time();
RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks); RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks);