diff --git a/lib/airplay_video.c b/lib/airplay_video.c index 1b1c761..a97106f 100644 --- a/lib/airplay_video.c +++ b/lib/airplay_video.c @@ -30,6 +30,8 @@ struct media_item_s { char *uri; char *playlist; int num; + int count; + float duration; }; struct airplay_video_s { @@ -170,6 +172,13 @@ int get_next_media_uri_id(airplay_video_t *airplay_video) { return airplay_video->next_uri; } +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; +} + typedef struct language_s { char *start; int len; @@ -277,16 +286,12 @@ language_t* master_playlist_process_language(char * data, int *slices, int *lang return languages; } -void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist) { +char * select_master_playlist_language(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, + if (!(languages = master_playlist_process_language(master_playlist, &slices, &language_count))) { - return; + return master_playlist; } /* audio is offered in multiple languages */ char *str = calloc(6 * language_count, sizeof(char)); @@ -342,8 +347,8 @@ void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist len += languages[i].len; } } - airplay_video->master_playlist = (char *) calloc(len + 1, sizeof(char)); - ptr = airplay_video->master_playlist; + char *new_master_playlist = (char *) calloc(len + 1, sizeof(char)); + ptr = new_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); @@ -352,6 +357,7 @@ void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist } free (languages); free(master_playlist); + return new_master_playlist; } char *get_master_playlist(airplay_video_t *airplay_video) { @@ -392,13 +398,14 @@ void create_media_data_store(airplay_video_t * airplay_video, char ** uri_list, airplay_video->num_uri = num_uri; } -int store_media_playlist(airplay_video_t *airplay_video, char * media_playlist, int num) { +int store_media_playlist(airplay_video_t *airplay_video, char * media_playlist, int *count, float *duration, 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; } + /* dont store duplicate media paylists */ for (int i = 0; i < num ; i++) { if (strcmp(media_data_store[i].uri, media_data_store[num].uri) == 0) { assert(strcmp(media_data_store[i].playlist, media_playlist) == 0); @@ -408,16 +415,20 @@ int store_media_playlist(airplay_video_t *airplay_video, char * media_playlist, } } media_data_store[num].playlist = media_playlist; + media_data_store[num].count = *count; + media_data_store[num].duration = *duration; return 0; } -char * get_media_playlist(airplay_video_t *airplay_video, const char *uri) { +char * get_media_playlist(airplay_video_t *airplay_video, int *count, float *duration, const char *uri) { media_item_t *media_data_store = airplay_video->media_data_store; if (media_data_store == NULL) { return NULL; } for (int i = 0; i < airplay_video->num_uri; i++) { if (strstr(media_data_store[i].uri, uri)) { + *count = media_data_store[media_data_store[i].num].count; + *duration = media_data_store[media_data_store[i].num].duration; return media_data_store[media_data_store[i].num].playlist; } } @@ -432,7 +443,8 @@ char * get_media_uri_by_num(airplay_video_t *airplay_video, int num) { return NULL; } -int analyze_media_playlist(char *playlist, float *duration) { +#if 0 +int analyze_media_playlist_old(char *playlist, float *duration) { float next; int count = 0; char *ptr = strstr(playlist, "#EXTINF:"); @@ -447,6 +459,34 @@ int analyze_media_playlist(char *playlist, float *duration) { } return count; } +#endif + +int analyze_media_playlist(char *playlist, float *duration) { + *duration = 0.0f;; + int count = 0; + int len_playlist = strlen(playlist); + char target[] = "#EXTINF:"; + int len_target = strlen(target); + int len = len_playlist - len_target; + char *ptr = playlist; + int i = 0; + while (i < len) { + char *endptr; + float next; + if (strncmp(ptr, target, len_target)) { + i++; + ptr++; + continue; + } + i += len_target; + ptr += len_target; + next = strtof(ptr, &endptr); + assert(endptr > ptr); + *duration += next; + count++; + } + return count; +} /* parse Master Playlist, make table of Media Playlist uri's that it lists */ int create_media_uri_table(const char *url_prefix, const char *master_playlist_data, diff --git a/lib/airplay_video.h b/lib/airplay_video.h index 868819d..5e6e5ae 100644 --- a/lib/airplay_video.h +++ b/lib/airplay_video.h @@ -45,9 +45,10 @@ int analyze_media_playlist(char *playlist, float *duration); int create_media_uri_table(const char *url_prefix, const char *master_playlist_data, int datalen, char ***media_uri_table, int *num_uri); void store_master_playlist(airplay_video_t *airplay_video, char *master_playlist); -int store_media_playlist(airplay_video_t *airplay_video, char *media_playlist, int num); +char *select_master_playlist_language(airplay_video_t *airplay_video, char *master_playlist); +int store_media_playlist(airplay_video_t *airplay_video, char *media_playlist, int *count, float *duration, int num); char *get_master_playlist(airplay_video_t *airplay_video); -char *get_media_playlist(airplay_video_t *airplay_video, const char *uri); +char *get_media_playlist(airplay_video_t *airplay_video, int *count, float *duration, const char *uri); 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); diff --git a/lib/http_handlers.h b/lib/http_handlers.h index 546888f..3801c2a 100644 --- a/lib/http_handlers.h +++ b/lib/http_handlers.h @@ -376,19 +376,6 @@ http_handler_reverse(raop_conn_t *conn, http_request_t *request, http_response_t 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 -plist_mem_free_wrapper(char * plist_ptr) { - /* wrapper for plist_mem_free, only available since libplist 2.3.0 */ - if (plist_ptr) { -#ifdef PLIST_230 - plist_mem_free (plst_ptr); -#else - free (plist_ptr); -#endif - } -} - static void http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t *response, char **response_data, int *response_datalen) { @@ -504,7 +491,7 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t } if (plist_xml) { #ifdef PLIST_230 - plist_mem_free(plist_xml); + plist_mem_free_wrapper(plist_xml); #else plist_to_xml_free(plist_xml); #endif @@ -570,52 +557,55 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t plist_get_data_val(req_params_fcup_response_data_node, &fcup_response_data, &uint_val); fcup_response_datalen = (int) uint_val; + char *playlist = NULL; if (!fcup_response_data) { goto post_action_error; - } - - if (logger_debug) { - logger_log(conn->raop->logger, LOGGER_DEBUG, "FCUP_Response datalen = %d", fcup_response_datalen); - 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); + } else { + playlist = (char *) malloc(fcup_response_datalen + 1); + playlist[fcup_response_datalen] = '\0'; + memcpy(playlist, fcup_response_data, fcup_response_datalen); + plist_mem_free_wrapper(fcup_response_data); + } + assert(playlist); + int playlist_len = strlen(playlist); + + if (logger_debug) { + logger_log(conn->raop->logger, LOGGER_DEBUG, "begin FCUP Response data:\n%s\nend FCUP Response data", playlist); } - char *ptr = strstr(fcup_response_url, "/master.m3u8"); if (ptr) { /* this is a master playlist */ char *uri_prefix = get_uri_prefix(airplay_video); - char ** media_data_store = NULL; + char ** uri_list = NULL; int num_uri = 0; - 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); + playlist = select_master_playlist_language(airplay_video, playlist); + playlist_len = strlen(playlist); + create_media_uri_table(uri_prefix, playlist, playlist_len, &uri_list, &num_uri); + char *new_master = adjust_master_playlist (playlist, playlist_len, uri_prefix, uri_local_prefix); + free(playlist); 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_data_store(airplay_video, media_data_store, num_uri); + create_media_data_store(airplay_video, uri_list, num_uri); + free (uri_list); num_uri = get_num_media_uri(airplay_video); set_next_media_uri_id(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(airplay_video); - --uri_num; // (next num is current num + 1) - store_media_playlist(airplay_video, playlist, uri_num); float duration = 0.0f; int count = analyze_media_playlist(playlist, &duration); - if (count) { - 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); + int uri_num = get_next_media_uri_id(airplay_video); + --uri_num; // (next num is current num + 1) + int ret = store_media_playlist(airplay_video, playlist, &count, &duration, uri_num); + if (ret == 1) { + logger_log(conn->raop->logger, LOGGER_DEBUG,"media_playlist is a duplicate: do not store"); + } else if (count) { + 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); } } - plist_mem_free_wrapper(fcup_response_data); plist_mem_free_wrapper(fcup_response_url); int num_uri = get_num_media_uri(airplay_video); @@ -715,7 +705,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r char* playback_uuid = NULL; plist_get_string_val(req_uuid_node, &playback_uuid); set_playback_uuid(airplay_video, playback_uuid); - plist_mem_free (playback_uuid); + plist_mem_free_wrapper (playback_uuid); } plist_t req_content_location_node = plist_dict_get_item(req_root_node, "Content-Location"); @@ -734,7 +724,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r logger_log(conn->raop->logger, LOGGER_WARNING, "Unsupported HLS streaming format: clientProcName %s not found in supported list: %s", client_proc_name, supported_hls_proc_names); } - plist_mem_free(client_proc_name); + plist_mem_free_wrapper(client_proc_name); } plist_t req_start_position_seconds_node = plist_dict_get_item(req_root_node, "Start-Position-Seconds"); @@ -758,7 +748,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r set_next_media_uri_id(airplay_video, 0); fcup_request((void *) conn, playback_location, apple_session_id, get_next_FCUP_RequestID(airplay_video)); - plist_mem_free(playback_location); + plist_mem_free_wrapper(playback_location); if (req_root_node) { plist_free(req_root_node); @@ -766,7 +756,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r return; play_error:; - plist_mem_free(playback_location); + plist_mem_free_wrapper(playback_location); if (req_root_node) { plist_free(req_root_node); } @@ -817,19 +807,20 @@ http_handler_hls(raop_conn_t *conn, http_request_t *request, http_response_t *r } } else { - char *media_playlist = get_media_playlist(airplay_video, url); + int chunks; + float duration; + char *media_playlist = get_media_playlist(airplay_video, &chunks, &duration, url); if (media_playlist) { char *data = adjust_yt_condensed_playlist(media_playlist); *response_data = data; *response_datalen = strlen(data); - 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); } else { logger_log(conn->raop->logger, LOGGER_ERR,"requested media playlist %s not found", url); *response_datalen = 0; } + } http_response_add_header(response, "Access-Control-Allow-Headers", "Content-type"); diff --git a/lib/raop.c b/lib/raop.c index 1fbaa21..f43d236 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -33,6 +33,18 @@ #include "raop_rtp_mirror.h" #include "raop_ntp.h" +static void +plist_mem_free_wrapper(char * plist_ptr) { + /* wrapper for plist_mem_free, only available since libplist 2.3.0 */ + if (plist_ptr) { +#ifdef PLIST_230 + plist_mem_free (plst_ptr); +#else + free (plist_ptr); +#endif + } +} + struct raop_s { /* Callbacks for audio and video */ raop_callbacks_t callbacks; diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 67b0c49..9a0143f 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -32,13 +32,6 @@ typedef void (*raop_handler_t)(raop_conn_t *, http_request_t *, http_response_t *, char **, int *); -#ifndef PLIST_230 -void plist_mem_free(void* ptr) { - if (ptr) { - free(ptr); - } -} -#endif static void raop_handler_info(raop_conn_t *conn, @@ -87,8 +80,9 @@ raop_handler_info(raop_conn_t *conn, } else if (!strcmp(qualifier_string, txtRAOP)) { add_txt_raop = true; } - plist_mem_free(qualifier_string); + plist_mem_free_wrapper(qualifier_string); } + plist_free(req_root_node); } @@ -327,7 +321,7 @@ raop_handler_pairsetup_pin(raop_conn_t *conn, plist_free (req_root_node); return; } - plist_mem_free(method); + plist_mem_free_wrapper(method); method = NULL; plist_get_string_val(req_user_node, &user); logger_log(conn->raop->logger, LOGGER_INFO, "pair-setup-pin: device_id = %s", user); @@ -337,7 +331,7 @@ raop_handler_pairsetup_pin(raop_conn_t *conn, } int ret = srp_new_user(conn->session, conn->raop->pairing, (const char *) user, (const char *) pin, &salt, &len_salt, &pk, &len_pk); - plist_mem_free(user); + plist_mem_free_wrapper(user); user = NULL; plist_free(req_root_node); if (ret < 0) { @@ -760,11 +754,11 @@ raop_handler_setup(raop_conn_t *conn, free (client_pk); } } - plist_mem_free(deviceID); + plist_mem_free_wrapper(deviceID); deviceID = NULL; - plist_mem_free(model); + plist_mem_free_wrapper(model); model = NULL; - plist_mem_free(name); + plist_mem_free_wrapper(name); name = NULL; if (admit_client == false) { /* client is not authorized to connect */ @@ -877,7 +871,7 @@ raop_handler_setup(raop_conn_t *conn, logger_log(conn->raop->logger, LOGGER_ERR, "Client specified timingProtocol=%s," " but timingProtocol= NTP is required here", timing_protocol); } - plist_mem_free (timing_protocol); + plist_mem_free_wrapper (timing_protocol); timing_protocol = NULL; } else { logger_log(conn->raop->logger, LOGGER_DEBUG, "Client did not specify timingProtocol," @@ -1176,7 +1170,7 @@ raop_handler_audiomode(raop_conn_t *conn, /* not sure what should be done with this request: usually audioMode requested is "default" */ int log_level = (strstr(audiomode, "default") ? LOGGER_DEBUG : LOGGER_INFO); logger_log(conn->raop->logger, log_level, "Unhandled RTSP request \"audioMode: %s\"", audiomode); - plist_mem_free(audiomode); + plist_mem_free_wrapper(audiomode); plist_free(req_root_node); }