diff --git a/lib/http_handlers.h b/lib/http_handlers.h index 3f46af7..4b1e49c 100644 --- a/lib/http_handlers.h +++ b/lib/http_handlers.h @@ -31,8 +31,8 @@ static void static int get_playlist_by_uuid(raop_t *raop, const char *uuid) { - for (int i = 0 ;i < MAX_AIRPLAY_VIDEO && raop->airplay_video[i]; i++) { - if (!strcmp(uuid, get_playback_uuid(raop->airplay_video[i]))) { + for (int i = 0 ;i < MAX_AIRPLAY_VIDEO; i++) { + if (raop->airplay_video[i] && !strcmp(uuid, get_playback_uuid(raop->airplay_video[i]))) { return i; } } @@ -385,7 +385,7 @@ http_handler_playback_info(raop_conn_t *conn, http_request_t *request, http_resp logger_log(raop->logger, LOGGER_DEBUG, "playback_info not available (finishing)"); //httpd_remove_known_connections(raop->httpd); http_response_set_disconnect(response,1); - raop->callbacks.video_reset(raop->callbacks.cls, true, false); + raop->callbacks.video_reset(raop->callbacks.cls, RESET_TYPE_HLS_SHUTDOWN); return; } else if (playback_info.position == -1.0) { logger_log(raop->logger, LOGGER_DEBUG, "playback_info not available"); @@ -536,14 +536,9 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t if (id == raop->current_video) { raop->current_video = -1; float position = raop->callbacks.on_video_playlist_remove(raop->callbacks.cls); - float duration = get_duration(airplay_video); - if (duration < (float) MIN_STORED_AIRPLAY_VIDEO_DURATION_SECONDS) { - airplay_video_destroy(airplay_video); /* short duration == probably advertisements */ - raop->airplay_video[id] = NULL; - } else { - set_resume_position_seconds(airplay_video, position); - raop->interrupted_video = id; - } + /* keep the playlist (until space is needed for another one) in case its playback_uuid is re-requested + the video will then be restarted at its previous position */ + set_resume_position_seconds(airplay_video, position); } else { logger_log(raop->logger, LOGGER_WARNING, "playlistRemove uuid %s does not match current_video\n", remove_uuid); } @@ -575,7 +570,7 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t logger_log(raop->logger, LOGGER_ERR, "FIXME: playlistInsert is not yet implemented"); } else if (!strcmp(type, "unhandledURLResponse")) { - /* handling type "unhandledURLResponse" (case 1)*/ + /* handling type "unhandledURLResponse" */ uint_val = 0; int fcup_response_datalen = 0; @@ -709,21 +704,6 @@ http_handler_action(raop_conn_t *conn, http_request_t *request, http_response_t "start position in seconds". Once this request is received by the Sever, the Server sends a POST /event "FCUP Request" request to the Client on the reverse http channel, to request the HLS Master Playlist */ -static void -delete_short_playlist(raop_t *raop, int id) { - if (!raop->airplay_video[id]) { - return; - } - float duration = get_duration(raop->airplay_video[id]); - if (duration < (float) MIN_STORED_AIRPLAY_VIDEO_DURATION_SECONDS ) { //likely to be an advertisement - logger_log(raop->logger, LOGGER_INFO, - "deleting playlist playback_uuid %s duration (seconds) %f", - get_playback_uuid(raop->airplay_video[id]), duration); - airplay_video_destroy(raop->airplay_video[id]); - raop->airplay_video[id] = NULL; - } -} - static void http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *response, char **response_data, int *response_datalen) { @@ -782,15 +762,11 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r int id = -1; id = get_playlist_by_uuid(raop, playback_uuid); - /* check if playlist is already downloaded and stored (may have been interruoted by advertisements ) */ + /* check if playlist is already downloaded and stored (may have been interrupted by advertisements ) */ if (id >= 0) { - //printf("====use EXISTING airplay_video[%d] %p %s %s\n", id, raop->airplay_video[id], playback_uuid, get_playback_uuid(raop->airplay_video[id])); + //printf("====use EXISTING airplay_video[%d] %p %s %s\n", id, raop->airplay_video[id], playback_uuid, get_playback_uuid(raop->airplay_video[id])); plist_mem_free(playback_uuid); plist_free(req_root_node); - int current_video = raop->current_video; - if (current_video >= 0 && current_video != id) { - delete_short_playlist(raop, current_video); - } raop->current_video = id; airplay_video = hls_get_current_video(raop); assert(airplay_video); @@ -803,18 +779,26 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r resume_pos > start_pos ? resume_pos : start_pos); return; } - - /* remove short stort playlists (probably advertisements */ + + /* initialize a new playlist (airplay_video structure) */ + /* first delete any short stored playlists (probably advertisements */ int count = 0; for (int i = 0; i < MAX_AIRPLAY_VIDEO; i++) { if (raop->airplay_video[i]) { - delete_short_playlist(raop, i); - } - if (raop->airplay_video[i]) { - count++; + float duration = get_duration(raop->airplay_video[i]); + if (duration < (float) MIN_STORED_AIRPLAY_VIDEO_DURATION_SECONDS ) { //likely to be an advertisement + logger_log(raop->logger, LOGGER_INFO, + "deleting playlist playback_uuid %s duration (seconds) %f", + get_playback_uuid(raop->airplay_video[i]), duration); + raop_destroy_airplay_video(raop, i); + } else { + count++; + } } } + assert (count < MAX_AIRPLAY_VIDEO); + assert(id == -1); /* initialize new airplay_video structure to hold playlist */ for (int i = 0; i < MAX_AIRPLAY_VIDEO; i++) { if (raop->airplay_video[i]) { @@ -823,23 +807,16 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r id = i; break; } - if (id == -1) { - logger_log(raop->logger, LOGGER_ERR, "no unused airplay_video structures are available" - " MAX_AIRPLAY_VIDEO = %d\n", MAX_AIRPLAY_VIDEO); - exit(1); - } + assert(id >= 0); raop->current_video = id; raop->airplay_video[id] = airplay_video_init(raop, raop->port, raop->lang); airplay_video = hls_get_current_video(raop); - if (airplay_video) { - set_playback_uuid(airplay_video, playback_uuid, strlen(playback_uuid)); - plist_mem_free (playback_uuid); - count++; - } else { - logger_log(raop->logger, LOGGER_ERR, "failed to allocate airplay_video[%d]\n", id); - exit(-1); - } + assert(airplay_video); + set_apple_session_id(airplay_video, apple_session_id, strlen(apple_session_id)); + set_playback_uuid(airplay_video, playback_uuid, strlen(playback_uuid)); + plist_mem_free (playback_uuid); + count++; /* ensure that space will always be available for adding future playlists */ @@ -859,7 +836,6 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r get_duration(raop->airplay_video[i])); } #endif - set_apple_session_id(airplay_video, apple_session_id, strlen(apple_session_id)); plist_t req_content_location_node = plist_dict_get_item(req_root_node, "Content-Location"); if (!req_content_location_node) { @@ -890,6 +866,7 @@ http_handler_play(raop_conn_t *conn, http_request_t *request, http_response_t *r } set_start_position_seconds(airplay_video, (float) start_position_seconds); + /* we only support HLS if the playback location is terminated by "/master.m3u8" */ const char *uri_suffix = strstr(playback_location, "/master.m3u8"); if (!uri_suffix) { logger_log(raop->logger, LOGGER_ERR, "Content-Location has unsupported form:\n%s\n", playback_location); diff --git a/lib/raop.c b/lib/raop.c index f24410b..a470bf5 100644 --- a/lib/raop.c +++ b/lib/raop.c @@ -1,5 +1,4 @@ -/** - * Copyright (C) 2011-2012 Juho Vähä-Herttua +/** Copyright (C) 2011-2012 Juho Vähä-Herttua * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -85,7 +84,6 @@ struct raop_s { /* place to store media_data_store */ airplay_video_t *airplay_video[MAX_AIRPLAY_VIDEO]; int current_video; - int interrupted_video; /* activate support for HLS live streaming */ bool hls_support; @@ -248,7 +246,7 @@ conn_request(void *ptr, http_request_t *request, http_response_t **response) { logger_log(raop->logger, LOGGER_INFO, "*****\"nohold\" feature: switch to new connection request from %s", ipaddr); httpd_remove_known_connections(raop->httpd); if (raop->callbacks.video_reset) { - raop->callbacks.video_reset(raop->callbacks.cls, false, true); + raop->callbacks.video_reset(raop->callbacks.cls, RESET_TYPE_NOHOLD); } } else { logger_log(raop->logger, LOGGER_WARNING, "rejecting new connection request from %s", ipaddr); @@ -601,7 +599,6 @@ raop_init(raop_callbacks_t *callbacks) { /* initialize airplay_video */ raop->current_video = -1; - raop->interrupted_video = -1; for (int i= 0; i < MAX_AIRPLAY_VIDEO; i++) { raop->airplay_video[i] = NULL; } @@ -839,28 +836,20 @@ void raop_destroy_airplay_video(raop_t *raop, int id) { if (raop->airplay_video[i]) { airplay_video_destroy(raop->airplay_video[i]); raop->airplay_video[i] = NULL; + if (i == raop->current_video) { + raop->current_video = -1; + } } } } -void raop_playlist_remove(raop_t *raop, void *opaque, float position_seconds) { - airplay_video_t *airplay_video = (airplay_video_t *) opaque; - int id = -1; - for (int i = 0; i < MAX_AIRPLAY_VIDEO; i++) { - if (airplay_video == raop->airplay_video[i]) { - id = i; - break; - } - } - if (id >= 0) { - set_resume_position_seconds(airplay_video, position_seconds); - raop->current_video = -1; - float pos = get_resume_position_seconds(airplay_video); - assert(pos == position_seconds); - } else { - logger_log(raop->logger, LOGGER_ERR, "raop_playlist_remove: failed to identify removed_video"); - exit(0); - } +void raop_handle_eos(raop_t *raop) { + int id = raop->current_video; + assert (id >= 0); + raop_destroy_airplay_video(raop, id); + raop->current_video = -1; + /* reset video without deleting raop->airplay_video */ + raop->callbacks.video_reset(raop->callbacks.cls, RESET_TYPE_HLS_EOS); } uint64_t get_local_time() { diff --git a/lib/raop.h b/lib/raop.h index 0facd36..6a3bca3 100644 --- a/lib/raop.h +++ b/lib/raop.h @@ -60,6 +60,13 @@ typedef enum video_codec_e { VIDEO_CODEC_H265 } video_codec_t; +typedef enum reset_type_e { + RESET_TYPE_NOHOLD, + RESET_TYPE_RTP_SHUTDOWN, + RESET_TYPE_HLS_SHUTDOWN, + RESET_TYPE_HLS_EOS +} reset_type_t; + struct raop_callbacks_s { void* cls; @@ -69,7 +76,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, bool hls_shutdown, bool nohold); + void (*video_reset) (void *cls, reset_type_t reset_type); /* Optional but recommended callback functions (probably not optional, check this)*/ @@ -112,6 +119,7 @@ raop_ntp_t *raop_ntp_init(logger_t *logger, raop_callbacks_t *callbacks, const c airplay_video_t *airplay_video_init(raop_t *raop, unsigned short port, const char *lang); char *raop_get_lang(raop_t *raop); uint64_t get_local_time(); +void raop_handle_eos(raop_t *raop); RAOP_API raop_t *raop_init(raop_callbacks_t *callbacks); RAOP_API int raop_init2(raop_t *raop, int nohold, const char *device_id, const char *keyfile); diff --git a/lib/raop_handlers.h b/lib/raop_handlers.h index 1944760..74f6633 100644 --- a/lib/raop_handlers.h +++ b/lib/raop_handlers.h @@ -1278,7 +1278,7 @@ raop_handler_teardown(raop_conn_t *conn, } } } else if (teardown_110) { - raop->callbacks.video_reset(raop->callbacks.cls, false, false); + raop->callbacks.video_reset(raop->callbacks.cls, RESET_TYPE_RTP_SHUTDOWN); if (conn->raop_rtp_mirror) { /* Stop our video RTP session */ raop_rtp_mirror_stop(conn->raop_rtp_mirror); @@ -1296,7 +1296,7 @@ raop_handler_teardown(raop_conn_t *conn, /* shut down any HLS connections */ int hls_count = httpd_count_connection_type(raop->httpd, CONNECTION_TYPE_HLS); if (hls_count) { - raop->callbacks.video_reset(raop->callbacks.cls, true, false); + raop->callbacks.video_reset(raop->callbacks.cls, RESET_TYPE_HLS_SHUTDOWN); } } if (raop->callbacks.conn_teardown) { diff --git a/lib/raop_rtp_mirror.c b/lib/raop_rtp_mirror.c index 02a4f60..bfa82b7 100644 --- a/lib/raop_rtp_mirror.c +++ b/lib/raop_rtp_mirror.c @@ -852,7 +852,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, false, false); + raop_rtp_mirror->callbacks.video_reset(raop_rtp_mirror->callbacks.cls, RESET_TYPE_RTP_SHUTDOWN); } return 0; diff --git a/uxplay.cpp b/uxplay.cpp index a2f7764..73c45dc 100644 --- a/uxplay.cpp +++ b/uxplay.cpp @@ -639,8 +639,7 @@ static gboolean video_eos_watch_callback (gpointer loop) { /* HLS video has sent EOS */ LOGI("hls video has sent EOS"); video_renderer_hls_ready(); - video_renderer_start(); - /* if raop->interrupted_video exists, we should reset video without deleting raop->airplay_video */ + raop_handle_eos(raop); } return TRUE; } @@ -2056,33 +2055,33 @@ static bool check_blocked_client(char *deviceid) { // Server callbacks -extern "C" void video_reset(void *cls, bool hls_shutdown, bool nohold) { +extern "C" void video_reset(void *cls, reset_type_t type) { LOGD("video_reset"); if (use_video) { video_renderer_stop(); } - if (hls_shutdown) { + if (hls_support && (type == RESET_TYPE_HLS_SHUTDOWN || type == RESET_TYPE_NOHOLD)) { url.erase(); raop_destroy_airplay_video(raop, -1); + } + if (type == RESET_TYPE_HLS_SHUTDOWN) { raop_remove_hls_connections(raop); preserve_connections = true; - } else if (nohold) { - if (use_video) { - /* reset the video renderer immediately to avoid a timing issue if we wait for main_loop to reset */ - video_renderer_destroy(); - video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(), rtp_pipeline.c_str(), - video_decoder.c_str(), video_converter.c_str(), videosink.c_str(), - videosink_options.c_str(), fullscreen, video_sync, h265_support, - render_coverart, playbin_version, NULL); - video_renderer_start(); - } - if (hls_support && !url.empty()) { - url.erase(); - raop_destroy_airplay_video(raop, -1); - } + } + if (use_video && (type == RESET_TYPE_NOHOLD || type == RESET_TYPE_HLS_EOS)) { + /* reset the video renderer immediately to avoid a timing issue if we wait for main_loop to reset */ + video_renderer_destroy(); + video_renderer_init(render_logger, server_name.c_str(), videoflip, video_parser.c_str(), rtp_pipeline.c_str(), + video_decoder.c_str(), video_converter.c_str(), videosink.c_str(), + videosink_options.c_str(), fullscreen, video_sync, h265_support, + render_coverart, playbin_version, NULL); + video_renderer_start(); close_window = false; // we already closed the window + } + if (type == RESET_TYPE_NOHOLD) { preserve_connections = false; //we already closed all other connections } + remote_clock_offset = 0; relaunch_video = true; reset_loop = true; @@ -2400,7 +2399,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, false, false); + video_reset(cls, RESET_TYPE_RTP_SHUTDOWN); } }