aboutsummaryrefslogtreecommitdiff
path: root/video_player
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2022-03-11 07:07:04 +0100
committerdec05eba <dec05eba@protonmail.com>2022-03-11 07:09:29 +0100
commit80696a506afcee0cca48c22448adacb4aea6eece (patch)
treee1b642940ced3bb01460ef06308d4f86c887b88d /video_player
parent8c142359fd27d73fc6c77dc5d1bd4df831f7c0c1 (diff)
youtube: use mpv stream_cb instead of proxy server
Diffstat (limited to 'video_player')
-rw-r--r--video_player/src/main.cpp253
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;