diff --git a/lib/airplay_video.c b/lib/airplay_video.c index f6e07e8..28543a9 100644 --- a/lib/airplay_video.c +++ b/lib/airplay_video.c @@ -39,6 +39,8 @@ struct airplay_video_s { char apple_session_id[37]; char playback_uuid[37]; char *uri_prefix; + char *language_name; + char *language_code; const char *lang; char local_uri_prefix[23]; int next_uri; @@ -88,6 +90,10 @@ airplay_video_t *airplay_video_init(raop_t *raop, unsigned short http_port, airplay_video->start_position_seconds = 0.0f; + airplay_video->uri_prefix = NULL; + airplay_video->language_code = NULL; + airplay_video->language_name = NULL; + airplay_video->media_data_store = NULL; airplay_video->master_playlist = NULL; airplay_video->num_uri = 0; @@ -103,6 +109,12 @@ airplay_video_destroy(airplay_video_t *airplay_video) if (airplay_video->uri_prefix) { free(airplay_video->uri_prefix); } + if (airplay_video->language_name) { + free(airplay_video->language_name); + } + if (airplay_video->language_code) { + free(airplay_video->language_code); + } if (airplay_video->media_data_store) { destroy_media_data_store(airplay_video); } @@ -152,7 +164,29 @@ void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix) { } const char *get_uri_prefix(airplay_video_t *airplay_video) { - return airplay_video->uri_prefix; + return (const char *) airplay_video->uri_prefix; +} + +void set_language_name(airplay_video_t *airplay_video, char *language_name) { + if (airplay_video->language_name) { + free (airplay_video->language_name); + } + airplay_video->language_name = language_name; +} + +const char *get_language_name(airplay_video_t *airplay_video) { + return (const char *)airplay_video->language_name; +} + +void set_language_code(airplay_video_t *airplay_video, char *language_code) { + if (airplay_video->language_code) { + free (airplay_video->language_code); + } + airplay_video->language_code = language_code; +} + +const char *get_language_code(airplay_video_t *airplay_video) { + return (const char *) airplay_video->language_code; } char *get_uri_local_prefix(airplay_video_t *airplay_video) { diff --git a/lib/airplay_video.h b/lib/airplay_video.h index 864bbff..060ce22 100644 --- a/lib/airplay_video.h +++ b/lib/airplay_video.h @@ -36,6 +36,10 @@ const char *get_playback_uuid(airplay_video_t *airplay_video); void set_uri_prefix(airplay_video_t *airplay_video, char *uri_prefix); const char *get_uri_prefix(airplay_video_t *airplay_video); char *get_uri_local_prefix(airplay_video_t *airplay_video); +void set_language_code(airplay_video_t *airplay_video, char *language_code); +const char *get_language_code(airplay_video_t *airplay_video); +void set_language_name(airplay_video_t *airplay_video, char *language_name); +const char *get_language_name(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); diff --git a/lib/http_handlers.h b/lib/http_handlers.h index e470b19..15e0bbf 100644 --- a/lib/http_handlers.h +++ b/lib/http_handlers.h @@ -168,6 +168,7 @@ http_handler_set_property(raop_conn_t *conn, 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) @@ -175,9 +176,82 @@ http_handler_set_property(raop_conn_t *conn, reverseEndTime (only used when rate < 0) time at which reverse playback ends forwardEndTime (only used when rate > 0) time at which reverse playback ends + selectedMediaArray contains plist with language choice: */ - if (!strcmp(property, "reverseEndTime") || + airplay_video_t *airplay_video = conn->raop->airplay_video[conn->raop->current_video]; + if (!strcmp(property, "selectedMediaArray")) { + /* verify that this request contains a binary plist*/ + char *header_str = NULL; + int request_datalen = 0; + http_request_get_header_string(request, &header_str); + bool is_plist = strstr(header_str,"apple-binary-plist"); + free(header_str); + if (!is_plist) { + logger_log(conn->raop->logger, LOGGER_DEBUG, "POST /setProperty?selectedMediaArray" + "does not provide an apple-binary-plist"); + goto post_error; + } + + const char *request_data = http_request_get_data(request, &request_datalen); + plist_t req_root_node = NULL; + plist_from_bin(request_data, request_datalen, &req_root_node); + plist_t req_value_node = plist_dict_get_item(req_root_node, "value"); + + if (!req_value_node || !PLIST_IS_ARRAY(req_value_node)) { + logger_log(conn->raop->logger, LOGGER_INFO, "POST /setProperty?selectedMediaArray" + " did not provide expected plist from client"); + goto post_error; + } + + int count = plist_array_get_size(req_value_node); + char *name = NULL; + char *code = NULL; + char *language_name = NULL; + char *language_code = NULL; + for (int i = 0; i < count; i++) { + plist_t req_value_array_node = plist_array_get_item(req_value_node,i); + if (!language_name) { + plist_t req_value_options_name_node = plist_dict_get_item(req_value_array_node,"MediaSelectionOptionsName"); + if (PLIST_IS_STRING(req_value_options_name_node)) { + plist_get_string_val(req_value_options_name_node, &name); + if (name) { + language_name = (char *) calloc(strlen(name) + 1, sizeof(char)); + memcpy(language_name, name, strlen(name)); + plist_mem_free(name); + } + } + } + if (!language_code) { + plist_t req_value_options_code_node = plist_dict_get_item(req_value_array_node,"MediaSelectionOptionsUnicodeLanguageIdentifier"); + if (PLIST_IS_STRING(req_value_options_code_node)) { + plist_get_string_val(req_value_options_code_node, &code); + if (code) { + language_code = (char *) calloc(strlen(code) + 1, sizeof(char)); + memcpy(language_code, code, strlen(code)); + plist_mem_free(code); + } + } + } + if (language_code && language_name) { + break; + } else { + plist_free (req_value_array_node); + continue; + } + } + plist_free (req_root_node); + const char *lname = NULL, *lcode = NULL; + if (language_code) { + set_language_code(airplay_video, language_code); + lcode = get_language_code(airplay_video); + } + if (language_name) { + set_language_name(airplay_video, language_name); + lname = get_language_name(airplay_video); + } + logger_log(conn->raop->logger, LOGGER_INFO, "stored language from MediaSelectionOptions: %s \"%s\"", lcode, lname); + } else if (!strcmp(property, "reverseEndTime") || !strcmp(property, "forwardEndTime") || !strcmp(property, "actionAtItemEnd")) { logger_log(conn->raop->logger, LOGGER_DEBUG, "property %s is known but unhandled", property); @@ -190,8 +264,11 @@ http_handler_set_property(raop_conn_t *conn, 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"); + goto post_error; } + return; + post_error: + http_response_add_header(response, "Content-Length", "0"); } /* handles GET /getProperty http requests from Client to Server. (not implemented) */