From 21c50903a68c253fa5fcb9ed5ac8ba5abb1142b9 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 7 Mar 2022 19:02:28 +0100 Subject: Attempt to fix youtube video issue where video stops playing because audio finishes download Remove youtube livestream code --- plugins/youtube/YoutubeMediaProxy.hpp | 32 +--- src/Downloader.cpp | 8 +- src/plugins/youtube/YoutubeMediaProxy.cpp | 267 ++++++------------------------ 3 files changed, 54 insertions(+), 253 deletions(-) diff --git a/plugins/youtube/YoutubeMediaProxy.hpp b/plugins/youtube/YoutubeMediaProxy.hpp index eaef898..0fb2d6a 100644 --- a/plugins/youtube/YoutubeMediaProxy.hpp +++ b/plugins/youtube/YoutubeMediaProxy.hpp @@ -22,7 +22,7 @@ namespace QuickMedia { virtual Error update() = 0; virtual bool get_address(std::string &address) = 0; - bool start_download(const std::string &media_url, ReadProgram &read_program, int64_t range_start, bool include_header, bool is_livestream = false, int livestream_sequence = -1); + bool start_download(const std::string &media_url, ReadProgram &read_program, int64_t range_start, int64_t end_range, bool include_header); }; class YoutubeStaticMediaProxy : public YoutubeMediaProxy { @@ -57,7 +57,9 @@ namespace QuickMedia { ssize_t downloader_num_read_bytes = 0; int64_t download_range_start = 0; int64_t current_download_range = 0; + int64_t next_range_length = 0; std::string download_header; + bool end_of_file = false; bool download_header_finished = false; bool download_header_sent = false; bool download_header_remaining_sent = false; @@ -70,32 +72,4 @@ namespace QuickMedia { std::string client_request_buffer; char download_read_buffer[16384]; }; - - class YoutubeLiveStreamMediaProxy : public YoutubeMediaProxy { - public: - YoutubeLiveStreamMediaProxy() = default; - YoutubeLiveStreamMediaProxy(YoutubeLiveStreamMediaProxy&) = delete; - YoutubeLiveStreamMediaProxy&operator=(YoutubeLiveStreamMediaProxy&) = delete; - ~YoutubeLiveStreamMediaProxy(); - - bool start(const std::string &youtube_media_url, int64_t content_length) override; - void stop() override; - Error update() override; - bool get_address(std::string &address) override; - private: - Error update_download_program_status(); - Error continue_send(const char *buffer_start, size_t total_bytes_to_write, int &buffer_offset); - private: - ReadProgram downloader_read_program; - std::string youtube_media_url; - int fd[2]; - int64_t livestream_sequence_num = -1; - std::string download_header; - bool download_header_finished = false; - bool download_header_remaining_sent = false; - int download_header_written_offset = 0; - int download_read_buffer_offset = 0; - ssize_t downloader_num_read_bytes = 0; - char download_read_buffer[16384]; - }; } \ No newline at end of file diff --git a/src/Downloader.cpp b/src/Downloader.cpp index e5b1a3c..89d83cb 100644 --- a/src/Downloader.cpp +++ b/src/Downloader.cpp @@ -307,14 +307,10 @@ namespace QuickMedia { int num_proxied_media = 0; for(int i = 0; i < 2; ++i) { media_proxies[i].output_filepath->clear(); - if(media_proxies[i].media_metadata->url.empty()) + if(media_proxies[i].media_metadata->url.empty() || youtube_url_is_live_stream(media_proxies[i].media_metadata->url)) continue; - if(youtube_url_is_live_stream(media_proxies[i].media_metadata->url)) - *media_proxies[i].media_proxy = std::make_unique(); - else - *media_proxies[i].media_proxy = std::make_unique(); - + *media_proxies[i].media_proxy = std::make_unique(); if(!(*media_proxies[i].media_proxy)->start(media_proxies[i].media_metadata->url, media_proxies[i].media_metadata->content_length)) { fprintf(stderr, "Failed to load start youtube media proxy\n"); return false; diff --git a/src/plugins/youtube/YoutubeMediaProxy.cpp b/src/plugins/youtube/YoutubeMediaProxy.cpp index 9efd7ac..4d84674 100644 --- a/src/plugins/youtube/YoutubeMediaProxy.cpp +++ b/src/plugins/youtube/YoutubeMediaProxy.cpp @@ -30,12 +30,12 @@ static ssize_t read_eintr(int fd, void *buffer, size_t size) { } } -static ssize_t write_all(int fd, const void *buffer, size_t size) { +static ssize_t write_all_blocking(int fd, const void *buffer, size_t size) { ssize_t bytes_written = 0; while((size_t)bytes_written < size) { ssize_t written = write(fd, (char*)buffer + bytes_written, size - bytes_written); if(written == -1) { - if(errno != EINTR) + if(errno != EINTR && errno != EWOULDBLOCK) return -1; } else { bytes_written += written; @@ -50,6 +50,9 @@ namespace QuickMedia { static const char download_error_response_msg[] = "HTTP/1.1 500 Internal Server Error\r\n" "Content-Length: 0\r\n\r\n"; + static const char download_finished_response_msg[] = + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n\r\n"; static bool set_non_blocking(int fd) { const int flags = fcntl(fd, F_GETFL, 0); @@ -60,22 +63,15 @@ namespace QuickMedia { // TODO: Restrict range end to remote file size (content-length which we have saved). // TODO: Check if custom youtube redirect code is needed - bool YoutubeMediaProxy::start_download(const std::string &media_url, ReadProgram &read_program, int64_t range_start, bool include_header, bool is_livestream, int livestream_sequence) { - std::string r = std::to_string(range_start) + "-" + std::to_string(range_start + RANGE); + bool YoutubeMediaProxy::start_download(const std::string &media_url, ReadProgram &read_program, int64_t range_start, int64_t end_range, bool include_header) { + std::string r = std::to_string(range_start) + "-" + std::to_string(end_range); std::string url = media_url;; std::vector args = { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive", "--no-buffer", "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", - "-g", "-s", "-L", "-f" }; - - if(is_livestream) { - if(livestream_sequence != -1) - url += "&sq=" + std::to_string(livestream_sequence); - } else { - args.insert(args.end(), { "-r", r.c_str() }); - } + "-g", "-s", "-L", "-f", "-r", r.c_str() }; if(include_header) args.push_back("-i"); @@ -124,6 +120,8 @@ namespace QuickMedia { server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); server_addr.sin_port = htons(0); + next_range_length = RANGE; + if(bind(socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { perror("YoutubeStaticMediaProxy::start: bind failed"); goto err; @@ -249,6 +247,20 @@ namespace QuickMedia { return start_range; } + static int64_t header_extract_content_length(const std::string &header) { + std::string content_length_str = header_extract_value(header, "content-length"); + if(content_length_str.empty()) + return 0; + + errno = 0; + char *endptr; + int64_t content_length = strtoll(content_length_str.c_str(), &endptr, 10); + if(endptr == content_length_str.c_str() || errno != 0) + return 0; + + return content_length; + } + // TODO: What about hls (live streams)? need to test with that. There may not be a need to use YoutubeMediaProxy for that case (in that case document it). YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::read_client_data() { if(client_request_finished) @@ -307,6 +319,8 @@ namespace QuickMedia { download_header_remaining_sent = false; download_header_written_offset = 0; download_read_buffer_offset = 0; + next_range_length = RANGE; + end_of_file = false; } YoutubeStaticMediaProxy::Error YoutubeStaticMediaProxy::update_download_program_status(bool client_disconnected, int64_t new_range_start, bool restart_download) { @@ -326,11 +340,10 @@ namespace QuickMedia { downloader_read_program.read_fd = -1; } - // TODO: Why is this not 0 when download finishes? if(program_status != 0) { //fprintf(stderr, "YoutubeStaticMediaProxy::update_download_program_status: download failed, exit status: %d\n", program_status); if(client_fd != -1) { - write_all(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1); + write_all_blocking(client_fd, download_finished_response_msg, sizeof(download_finished_response_msg) - 1); close(client_fd); client_fd = -1; client_request_buffer.clear(); @@ -342,7 +355,7 @@ namespace QuickMedia { if(client_disconnected) { current_download_range = 0; } else { - current_download_range += RANGE + 1; + current_download_range += next_range_length; } if(new_range_start != -1) { @@ -372,11 +385,17 @@ namespace QuickMedia { download_header_remaining_sent = true; } - const bool start_download_success = start_download(youtube_media_url, downloader_read_program, current_download_range, new_range_start != -1); + int64_t end_range = current_download_range + RANGE; + if(end_range > content_length) { + end_range = content_length; + end_of_file = true; + } + + const bool start_download_success = start_download(youtube_media_url, downloader_read_program, current_download_range, end_range, new_range_start != -1); if(!start_download_success) { fprintf(stderr, "YoutubeStaticMediaProxy::update_download_program_status: failed to start download\n"); if(client_fd != -1) { - write_all(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1); + write_all_blocking(client_fd, download_error_response_msg, sizeof(download_error_response_msg) - 1); close(client_fd); client_fd = -1; client_request_buffer.clear(); @@ -414,7 +433,7 @@ namespace QuickMedia { return Error::OK; } - const ssize_t num_bytes_written = write_all(client_fd, buffer_start + buffer_offset, num_bytes_to_write); + const ssize_t num_bytes_written = write_all_blocking(client_fd, buffer_start + buffer_offset, num_bytes_to_write); if(num_bytes_written == -1) { const int err = errno; if(err == EAGAIN || err == EWOULDBLOCK) { @@ -478,6 +497,17 @@ namespace QuickMedia { return Error::ERROR; } } else if(downloader_num_read_bytes == 0) { + if(end_of_file) { + if(client_fd != -1) { + write_all_blocking(client_fd, download_finished_response_msg, sizeof(download_finished_response_msg) - 1); + close(client_fd); + client_fd = -1; + client_request_buffer.clear(); + client_request_finished = false; + } + return Error::OK; + } + Error err = update_download_program_status(false, -1, true); if(err != Error::OK) return err; @@ -507,6 +537,7 @@ namespace QuickMedia { download_header_offset_to_end_of_header = header_end; download_read_buffer_offset = -1; + next_range_length = header_extract_content_length(download_header.substr(0, header_end)); header_replace_content_length(download_header, header_end, content_length - download_range_start); } else { if(download_header.size() > MAX_BUFFER_SIZE) { @@ -575,204 +606,4 @@ namespace QuickMedia { //fprintf(stderr, "YoutubeStaticMediaProxy::accept_client: client connected!\n"); return new_client_fd; } - - YoutubeLiveStreamMediaProxy::~YoutubeLiveStreamMediaProxy() { - stop(); - } - - bool YoutubeLiveStreamMediaProxy::start(const std::string &youtube_media_url, int64_t) { - fd[0] = -1; - fd[1] = -1; - if(pipe(fd) == -1) { - perror("YoutubeLiveStreamMediaProxy::start: failed to open pipe"); - return false; - } - - //if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) { - // perror("YoutubeLiveStreamMediaProxy::start: failed to open pipe"); - // return false; - //} - - for(int i = 0; i < 2; ++i) { - if(!set_non_blocking(fd[i])) { - stop(); - return false; - } - } - - if(!start_download(youtube_media_url, downloader_read_program, 0, true, true)) { - stop(); - return false; - } - - this->youtube_media_url = youtube_media_url; - return true; - } - - void YoutubeLiveStreamMediaProxy::stop() { - for(int i = 0; i < 2; ++i) { - if(fd[i] != -1) { - close(fd[i]); - fd[i] = -1; - } - } - - if(downloader_read_program.read_fd != -1) { - close(downloader_read_program.read_fd); - downloader_read_program.read_fd = -1; - } - - if(downloader_read_program.pid != -1) { - kill(downloader_read_program.pid, SIGTERM); - wait_program(downloader_read_program.pid); - downloader_read_program.pid = -1; - } - } - - YoutubeMediaProxy::Error YoutubeLiveStreamMediaProxy::update_download_program_status() { - int program_status = 0; - if(wait_program_non_blocking(downloader_read_program.pid, &program_status) == 0 && program_status == 0) - return Error::OK; - - downloader_read_program.pid = -1; - if(downloader_read_program.read_fd != -1) { - close(downloader_read_program.read_fd); - downloader_read_program.read_fd = -1; - } - - // TODO: Why is this not 0 when download finishes? - if(program_status != 0) { - //fprintf(stderr, "YoutubeLiveStreamMediaProxy::update_download_program_status: download failed, exit status: %d\n", program_status); - stop(); - return Error::ERROR; - } - - ++livestream_sequence_num; - const bool start_download_success = start_download(youtube_media_url, downloader_read_program, 0, false, true, livestream_sequence_num); - if(!start_download_success) { - fprintf(stderr, "YoutubeLiveStreamMediaProxy::update_download_program_status: failed to start download\n"); - stop(); - return Error::ERROR; - } - - return Error::OK; - } - - YoutubeMediaProxy::Error YoutubeLiveStreamMediaProxy::continue_send(const char *buffer_start, size_t total_bytes_to_write, int &buffer_offset) { - int num_bytes_to_write = total_bytes_to_write - buffer_offset; - //assert(num_bytes_to_write >= 0); - if(num_bytes_to_write < 0) num_bytes_to_write = 0; - if(num_bytes_to_write == 0) { - buffer_offset = 0; - return Error::OK; - } - - const ssize_t num_bytes_written = write_all(fd[1], buffer_start + buffer_offset, num_bytes_to_write); - if(num_bytes_written == -1) { - const int err = errno; - if(err == EAGAIN || err == EWOULDBLOCK) { - return Error::OK; - } else if(err == EPIPE || err == ECONNRESET) { - //fprintf(stderr, "YoutubeLiveStreamMediaProxy::continue_send: client disconnected\n"); - stop(); - return Error::ERROR; - } else { - perror("YoutubeLiveStreamMediaProxy::continue_send: write failed"); - return Error::ERROR; - } - } else if(num_bytes_written == 0) { - //fprintf(stderr, "YoutubeLiveStreamMediaProxy::continue_send: client disconnected\n"); - stop(); - return Error::ERROR; - } else if(num_bytes_written != num_bytes_to_write) { - buffer_offset += num_bytes_written; - } else { - buffer_offset = 0; - } - return Error::OK; - } - - YoutubeMediaProxy::Error YoutubeLiveStreamMediaProxy::update() { - if(fd[1] == -1 || downloader_read_program.read_fd == -1) - return Error::OK; - - if(download_read_buffer_offset == 0) { - downloader_num_read_bytes = read_eintr(downloader_read_program.read_fd, download_read_buffer, sizeof(download_read_buffer)); - if(downloader_num_read_bytes == -1) { - const int err = errno; - if(err == EAGAIN || err == EWOULDBLOCK) { - return Error::OK; - } else { - perror("YoutubeLiveStreamMediaProxy::update: curl read failed"); - return Error::ERROR; - } - } else if(downloader_num_read_bytes == 0) { - Error err = update_download_program_status(); - if(err != Error::OK) - return err; - } - } - - if(!download_header_finished) { - download_header.append(download_read_buffer, downloader_num_read_bytes); - size_t header_end = std::string::npos; - const size_t offset_to_start_of_header = find_start_of_first_non_redirect_header(download_header.c_str(), download_header.size(), header_end); - if(header_end != std::string::npos) { - download_header.erase(0, offset_to_start_of_header); - header_end -= offset_to_start_of_header; - - download_header_finished = true; - download_header_remaining_sent = false; - download_header_written_offset = header_end; - download_read_buffer_offset = -1; - fprintf(stderr, "header: |%.*s|\n", download_header_written_offset, download_header.c_str()); - - if(livestream_sequence_num == -1) { - // TODO: What about |header_end|? - std::string sequence_num = header_extract_value(download_header, "x-sequence-num"); - fprintf(stderr, "server sequence num: |%s|\n", sequence_num.c_str()); - if(sequence_num.empty()) - fprintf(stderr, "YoutubeLiveStreamMediaProxy::update: missing sequence num from server\n"); - else - livestream_sequence_num = strtoll(sequence_num.c_str(), nullptr, 10); - } - } else { - if(download_header.size() > MAX_BUFFER_SIZE) { - fprintf(stderr, "YoutubeLiveStreamMediaProxy::update: buffer is full (malicious server?)\n"); - if(downloader_read_program.pid != -1) { - kill(downloader_read_program.pid, SIGTERM); - wait_program(downloader_read_program.pid); - downloader_read_program.pid = -1; - } - download_header_finished = true; - return Error::ERROR; - } - } - } - - if(download_header_finished && !download_header_remaining_sent) { - Error err = continue_send(download_header.data(), download_header.size(), download_header_written_offset); - if(err != Error::OK) - return err; - - if(download_header_written_offset == 0) { - download_header_remaining_sent = true; - download_read_buffer_offset = 0; - return Error::OK; - } - } - - if(download_header_remaining_sent) - return continue_send(download_read_buffer, downloader_num_read_bytes, download_read_buffer_offset); - - return Error::OK; - } - - bool YoutubeLiveStreamMediaProxy::get_address(std::string &address) { - if(fd[0] == -1) - return false; - - address = "fd://" + std::to_string(fd[0]); - return true; - } } -- cgit v1.2.3