diff options
author | dec05eba <dec05eba@protonmail.com> | 2022-03-11 07:07:04 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2022-03-11 07:09:29 +0100 |
commit | 80696a506afcee0cca48c22448adacb4aea6eece (patch) | |
tree | e1b642940ced3bb01460ef06308d4f86c887b88d /video_player | |
parent | 8c142359fd27d73fc6c77dc5d1bd4df831f7c0c1 (diff) |
youtube: use mpv stream_cb instead of proxy server
Diffstat (limited to 'video_player')
-rw-r--r-- | video_player/src/main.cpp | 253 |
1 files changed, 250 insertions, 3 deletions
diff --git a/video_player/src/main.cpp b/video_player/src/main.cpp index 91f2d4b..42a9b4f 100644 --- a/video_player/src/main.cpp +++ b/video_player/src/main.cpp @@ -11,8 +11,13 @@ #include <mutex> #include <optional> #include <set> +#include <algorithm> #include <mpv/client.h> +#include <mpv/stream_cb.h> +#include <sys/stat.h> +#include <signal.h> +#include <sys/wait.h> #include <json/json.h> #define COMMAND_BUFFER_MAX_SIZE 2048 @@ -313,6 +318,247 @@ static void mpv_set_before_init_options(mpv_handle *mpv_ctx, const Args &args) { } } +#define READ_END 0 +#define WRITE_END 1 + +struct ReadProgram { + pid_t pid = -1; + int read_fd = -1; + int64_t offset = 0; + int64_t content_length = -1; + char *url = nullptr; +}; + +static int exec_program_pipe(const char **args, ReadProgram *read_program) { + read_program->pid = -1; + read_program->read_fd = -1; + + /* 1 arguments */ + if(args[0] == NULL) + return -1; + + int fd[2]; + if(pipe(fd) == -1) { + perror("Failed to open pipe"); + return -2; + } + + pid_t pid = vfork(); + if(pid == -1) { + perror("Failed to vfork"); + close(fd[READ_END]); + close(fd[WRITE_END]); + return -3; + } else if(pid == 0) { /* child */ + dup2(fd[WRITE_END], STDOUT_FILENO); + close(fd[READ_END]); + close(fd[WRITE_END]); + + execvp(args[0], (char* const*)args); + perror("execvp"); + _exit(127); + } else { /* parent */ + close(fd[WRITE_END]); + read_program->pid = pid; + read_program->read_fd = fd[READ_END]; + return 0; + } +} + +static char to_upper(char c) { + if(c >= 'a' && c <= 'z') + return c - 32; + else + return c; +} + +size_t str_find_case_insensitive(const std::string &str, size_t start_index, const char *substr, size_t substr_len) { + if(substr_len == 0) + return 0; + + auto it = std::search(str.begin() + start_index, str.end(), substr, substr + substr_len, + [](char c1, char c2) { + return to_upper(c1) == to_upper(c2); + }); + + if(it == str.end()) + return std::string::npos; + + return it - str.begin(); +} + +static int64_t size_fn(void *cookie) { + ReadProgram *program = (ReadProgram*)cookie; + if(program->content_length != -1) + return program->content_length; + + const char *args[] = { "curl", + "-I", + "-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", + "--", program->url, nullptr }; + + ReadProgram header_program; + int res = exec_program_pipe(args, &header_program); + if(res != 0) + return MPV_ERROR_UNSUPPORTED; + + std::string buffer; + buffer.resize(8192); + size_t read_offset = 0; + for(;;) { + ssize_t bytes_read = read(header_program.read_fd, &buffer[read_offset], buffer.size() - read_offset); + if(bytes_read == 0) { + buffer.resize(read_offset); + break; + } else if(bytes_read == -1) { + res = MPV_ERROR_UNSUPPORTED; + break; + } + + read_offset += bytes_read; + if(read_offset >= buffer.size()) { + fprintf(stderr, "Error: youtube header too large\n"); + res = MPV_ERROR_UNSUPPORTED; + break; + } + } + + if(res != 0) + kill(header_program.pid, SIGTERM); + + int status = 0; + if(waitpid(header_program.pid, &status, 0) == -1) { + perror("waitpid failed"); + res = MPV_ERROR_UNSUPPORTED; + goto cleanup; + } + + if(!WIFEXITED(status)) { + res = MPV_ERROR_UNSUPPORTED; + goto cleanup; + } + + if(WEXITSTATUS(status) != 0) + res = MPV_ERROR_UNSUPPORTED; + + cleanup: + close(header_program.read_fd); + + if(res == 0) { + size_t content_length_index = str_find_case_insensitive(buffer, 0, "content-length:", 15); + if(content_length_index == std::string::npos) + return res; + + content_length_index += 15; + size_t content_length_end = buffer.find('\r', content_length_index); + if(content_length_end == std::string::npos) + return res; + + buffer[content_length_end] = '\0'; + errno = 0; + char *endptr; + int64_t content_length = strtoll(&buffer[content_length_index], &endptr, 10); + if(endptr == &buffer[content_length_index] || errno != 0) + return res; + + res = content_length; + } + + program->content_length = res; + return res; +} + +static int64_t seek_fn(void *cookie, int64_t offset) { + ReadProgram *program = (ReadProgram*)cookie; + + if(program->pid != -1) { + kill(program->pid, SIGTERM); + int status; + waitpid(program->pid, &status, 0); + program->pid = -1; + } + + if(program->read_fd != -1) { + close(program->read_fd); + program->read_fd = -1; + } + + program->offset = offset; + + char range[64]; + snprintf(range, sizeof(range), "%lld-%lld", (long long)program->offset, (long long)program->offset + 5242870LL); + const char *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", + "-r", range, "--", program->url, nullptr }; + int res = exec_program_pipe(args, program); + if(res != 0) + return MPV_ERROR_GENERIC; + + return offset; +} + +static int64_t read_fn(void *cookie, char *buf, uint64_t nbytes) { + ReadProgram *program = (ReadProgram*)cookie; + ssize_t bytes_read = read(program->read_fd, buf, nbytes); + if(bytes_read > 0) { + program->offset += bytes_read; + } else if(bytes_read == 0) { + // End of current range, progress to next range + bytes_read = seek_fn(program, program->offset); + if(bytes_read >= 0) { + bytes_read = read(program->read_fd, buf, nbytes); + if(bytes_read > 0) + program->offset += bytes_read; + } + } + return bytes_read; +} + +static void close_fn(void *cookie) { + ReadProgram *program = (ReadProgram*)cookie; + + if(program->pid != -1) { + kill(program->pid, SIGTERM); + int status; + waitpid(program->pid, &status, 0); + program->pid = -1; + } + + if(program->read_fd != -1) { + close(program->read_fd); + program->read_fd = -1; + } + + if(program->url) { + free(program->url); + program->url = nullptr; + } + + delete program; +} + +static int open_fn(void*, char *uri, mpv_stream_cb_info *info) { + ReadProgram *read_program = new ReadProgram(); + read_program->read_fd = -1; + read_program->pid = -1; + read_program->url = strdup((const char*)uri + 8); + read_program->offset = 0; + int64_t res = seek_fn(read_program, read_program->offset); + + info->cookie = read_program; + info->size_fn = size_fn; + info->read_fn = read_fn; + info->seek_fn = seek_fn; + info->close_fn = close_fn; + return res >= 0 ? 0 : MPV_ERROR_LOADING_FAILED; +} + int main(int argc, char **argv) { // This is needed for mpv_create or it will fail setlocale(LC_ALL, "C"); @@ -328,12 +574,13 @@ int main(int argc, char **argv) { mpv_set_before_init_options(mpv_ctx, args); check_error(mpv_initialize(mpv_ctx), "mpv_initialize"); - const char *cmd[] = { "loadfile", args.file_to_play, NULL }; - check_error(mpv_command(mpv_ctx, cmd), "loadfile"); - + check_error(mpv_stream_cb_add_ro(mpv_ctx, "qm-yt", nullptr, open_fn), "mpv_stream_cb_add_ro"); check_error(mpv_observe_property(mpv_ctx, 0, "idle-active", MPV_FORMAT_FLAG), "observe idle-active"); check_error(mpv_observe_property(mpv_ctx, 0, "fullscreen", MPV_FORMAT_FLAG), "observe fullscreen"); + const char *cmd[] = { "loadfile", args.file_to_play, NULL }; + check_error(mpv_command(mpv_ctx, cmd), "loadfile"); + char command_buffer[COMMAND_BUFFER_MAX_SIZE]; size_t command_buffer_size = 0; std::mutex command_mutex; |