diff options
-rw-r--r-- | TODO | 4 | ||||
-rwxr-xr-x | build.sh | 6 | ||||
-rw-r--r-- | include/VideoPlayer.hpp | 13 | ||||
-rw-r--r-- | plugins/youtube/YoutubeMediaProxy.hpp | 9 | ||||
-rwxr-xr-x | run.sh | 12 | ||||
-rw-r--r-- | src/QuickMedia.cpp | 27 | ||||
-rw-r--r-- | src/VideoPlayer.cpp | 234 | ||||
-rw-r--r-- | src/plugins/LocalManga.cpp | 48 | ||||
-rw-r--r-- | src/plugins/Youtube.cpp | 2 | ||||
-rw-r--r-- | src/plugins/youtube/YoutubeMediaProxy.cpp | 34 | ||||
-rw-r--r-- | video_player/README.md | 20 | ||||
-rw-r--r-- | video_player/include/Args.hpp | 136 | ||||
-rw-r--r-- | video_player/include/Utils.hpp | 30 | ||||
-rw-r--r-- | video_player/project.conf | 3 | ||||
-rw-r--r-- | video_player/src/main.cpp | 281 |
15 files changed, 432 insertions, 427 deletions
@@ -172,15 +172,12 @@ Synapse is gay and mentions do not actually include the whole mxid. It only incl Make it possible to redact invites. Reapply filter when changing body item text. When fetching previous messages in matrix, ignore user info updates. -Add throttling to youtube live stream (MediaProxy). Use event timestamp to sort display name/room name (etc) events to apply them in order when fetching previous messages or additional messages. -Restart and resume youtube media download in downloader when media is throttled. Try to reconnect media proxy on disconnect. The internet may be unstable (for example on mobile internet). Opening a media url should display it directly in quickmedia. Automatically resize body item thumbnail if body is small, or move thumbnail above the text. Support  ?. Add option to navigate studios/producers/author in AniList. -Renable throttle detection after fixing it (it doesn't detect throttling well and it breaks for very long videos, such as 8 hour long videos). Show who deleted a message in matrix. Sync should replace all messages in the room (except for the selected room?) to reduce ram usage when in many rooms and when quickmedia has been running for a long time doing sync. Show youtube annotations. @@ -199,7 +196,6 @@ ffmpeg (and mpv) is very slow at playing streams (mostly affects lbry and certai Allow specifying start/end range for video/music downloads. Limit text input length for 4chan posts to the server limit. Allow creating a new thread on 4chan. -Add flag to quickmedia to use svp, by making svp use the same input ipc socket as quickmedia (and load the svp script manually). Support directly going to a youtube channel for a url. This is helpful for opening channel urls directly with quickmedia and also going to another channel from a youtube description. Support downloading soundcloud/youtube playlists. Such downloads should also have a different download gui as you would select a folder instead of an output file. Support downloading .m3u8 files, such as soundcloud music without using youtube-dl. @@ -1,8 +1,10 @@ #!/bin/sh -e +[ $# -ne 1 ] && echo "usage: build.sh debug|release" && exit 1 + script_dir=$(dirname "$0") cd "$script_dir" -sibs build --release video_player -sibs build --release +sibs build --"$1" video_player +sibs build --"$1" echo "Successfully built the video player and QuickMedia" diff --git a/include/VideoPlayer.hpp b/include/VideoPlayer.hpp index 2ccd280..f75d76f 100644 --- a/include/VideoPlayer.hpp +++ b/include/VideoPlayer.hpp @@ -5,7 +5,6 @@ #include <mglpp/system/Clock.hpp> #include <functional> #include <json/value.h> -#include <sys/un.h> #include <X11/Xlib.h> namespace QuickMedia { @@ -47,15 +46,12 @@ namespace QuickMedia { // Returns time in seconds Error get_time_in_file(double *result); - - Error set_property(const std::string &property_name, const Json::Value &value); - Error get_property(const std::string &property_name, Json::Value *result, Json::ValueType result_type); - Error add_subtitle(const std::string &url, const std::string &title, const std::string &lang); int exit_status; private: - Error send_command(const char *cmd, size_t size); + uint32_t get_next_request_id(); + Error send_command(Json::Value &json_root, Json::Value *result, Json::ValueType result_type); Error launch_video_process(const char *path, const char *audio_path, mgl::WindowHandle parent_window, const std::string &title, const std::string &start_time); VideoPlayer::Error read_ipc_func(); private: @@ -65,20 +61,17 @@ namespace QuickMedia { bool keep_open; bool use_youtube_dl; pid_t video_process_id; - bool connected_to_ipc; mgl::Clock retry_timer; int connect_tries; int find_window_tries; int monitor_height; int ipc_socket = -1; - struct sockaddr_un ipc_addr; - char ipc_server_path[L_tmpnam]; EventCallbackFunc event_callback; VideoPlayerWindowCreateCallback window_create_callback; mgl::WindowHandle window_handle; mgl::WindowHandle parent_window; Display *display; - unsigned int request_id; + unsigned int request_id_counter; unsigned int expected_request_id; Json::Value request_response_data; diff --git a/plugins/youtube/YoutubeMediaProxy.hpp b/plugins/youtube/YoutubeMediaProxy.hpp index e2c4bf7..eaef898 100644 --- a/plugins/youtube/YoutubeMediaProxy.hpp +++ b/plugins/youtube/YoutubeMediaProxy.hpp @@ -7,8 +7,6 @@ // TODO: Sync sequence for video and audio (for live stream). namespace QuickMedia { - using ThrottleHandler = std::function<void()>; - class YoutubeMediaProxy { public: enum Error { @@ -29,7 +27,7 @@ namespace QuickMedia { class YoutubeStaticMediaProxy : public YoutubeMediaProxy { public: - YoutubeStaticMediaProxy(ThrottleHandler throttle_handler = nullptr); + YoutubeStaticMediaProxy(); YoutubeStaticMediaProxy(YoutubeStaticMediaProxy&) = delete; YoutubeStaticMediaProxy&operator=(YoutubeStaticMediaProxy&) = delete; ~YoutubeStaticMediaProxy(); @@ -66,18 +64,13 @@ namespace QuickMedia { int download_header_written_offset = 0; int download_header_offset_to_end_of_header = 0; int64_t download_start_time = 0; - int64_t throttle_start_time = 0; int64_t total_downloaded_bytes = 0; bool download_started = false; - bool throttle_started = false; - bool throttle_callback_called = false; - ThrottleHandler throttle_handler = nullptr; bool client_request_finished = false; std::string client_request_buffer; char download_read_buffer[16384]; }; - // TODO: Add throttle detection for live streams class YoutubeLiveStreamMediaProxy : public YoutubeMediaProxy { public: YoutubeLiveStreamMediaProxy() = default; @@ -0,0 +1,12 @@ +#!/bin/sh -e + +[ $# -ne 1 ] && echo "usage: run.sh debug|release" && exit 1 + +script_dir=$(dirname "$0") +cd "$script_dir" + +sibs build --"$1" video_player +sibs build --"$1" +echo "Successfully built the video player and QuickMedia" + +"./sibs-build/$(sibs platform)/$1/quickmedia" diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index aa86bf8..35efd8d 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -2700,18 +2700,11 @@ namespace QuickMedia { } mgl::WindowHandle video_player_window = None; - auto on_window_create = [this, &video_player_window, &video_loaded, &video_page](mgl::WindowHandle _video_player_window) mutable { + auto on_window_create = [this, &video_player_window, &video_page](mgl::WindowHandle _video_player_window) mutable { video_player_window = _video_player_window; XSelectInput(disp, video_player_window, KeyPressMask | PointerMotionMask); XSync(disp, False); - // Hack to detect video playing if the video starts playing before we have connected to the ipc. - // TODO: This is an issue just because of ubuntu shit that uses old mpv that doesn't support ipc over file descriptors. - double time_in_file = 0.0; - video_player->get_time_in_file(&time_in_file); - if(time_in_file > 0.00001) - video_loaded = true; - SubtitleData subtitle_data; video_page->get_subtitles(subtitle_data); if(!subtitle_data.url.empty()) @@ -2736,11 +2729,6 @@ namespace QuickMedia { std::string prev_start_time; std::vector<MediaChapter> media_chapters; - bool throttled = false; - auto throttle_handler = [&throttled] { - throttled = true; - }; - auto load_video_error_check = [&](std::string start_time = "", bool reuse_media_source = false) mutable { video_player.reset(); channel_url.clear(); @@ -2852,7 +2840,6 @@ namespace QuickMedia { youtube_downloader_task.cancel(); youtube_video_media_proxy.reset(); youtube_audio_media_proxy.reset(); - throttled = false; struct MediaProxyMetadata { std::unique_ptr<YoutubeMediaProxy> *media_proxy; @@ -2869,7 +2856,7 @@ namespace QuickMedia { if(media_proxies[i].url->empty() || youtube_url_is_live_stream(*media_proxies[i].url)) continue; - *media_proxies[i].media_proxy = std::make_unique<YoutubeStaticMediaProxy>(throttle_handler); + *media_proxies[i].media_proxy = std::make_unique<YoutubeStaticMediaProxy>(); if(!(*media_proxies[i].media_proxy)->start(*media_proxies[i].url, media_proxies[i].content_length)) { show_notification("QuickMedia", "Failed to load start youtube media proxy", Urgency::CRITICAL); current_page = previous_page; @@ -2963,6 +2950,8 @@ namespace QuickMedia { // end_of_file = true; } else if(strcmp(event_name, "playback-restart") == 0) { //video_player->set_paused(false); + } else if(strcmp(event_name, "start-file") == 0) { + video_loaded = true; } else if(strcmp(event_name, "file-loaded") == 0) { video_loaded = true; } else if(strcmp(event_name, "video-reconfig") == 0 || strcmp(event_name, "audio-reconfig") == 0) { @@ -3129,14 +3118,6 @@ namespace QuickMedia { cursor_visible = true; } - if(is_youtube && video_player && throttled) { - throttled = false; - fprintf(stderr, "Throttled media download detected, reconnecting...\n"); - double resume_start_time = 0.0; - video_player->get_time_in_file(&resume_start_time); - load_video_error_check(std::to_string((int)resume_start_time)); - } - VideoPlayer::Error update_err = video_player ? video_player->update() : VideoPlayer::Error::OK; if(update_err == VideoPlayer::Error::FAIL_TO_CONNECT_TIMEOUT) { show_notification("QuickMedia", "Failed to connect to mpv ipc after 10 seconds", Urgency::CRITICAL); diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp index 75871da..76fa82a 100644 --- a/src/VideoPlayer.cpp +++ b/src/VideoPlayer.cpp @@ -45,7 +45,7 @@ static ssize_t write_all(int fd, const void *buffer, size_t size) { namespace QuickMedia { static const double RETRY_TIME_SEC = 0.5; static const int MAX_RETRIES_CONNECT = 1000; - static const double READ_TIMEOUT_SEC = 0.2; + static const double READ_TIMEOUT_SEC = 3.0; static std::string media_chapters_to_ffmetadata_chapters(const std::vector<MediaChapter> &chapters) { std::string result = ";FFMETADATA1\n\n"; @@ -98,7 +98,6 @@ namespace QuickMedia { keep_open(keep_open), use_youtube_dl(true), video_process_id(-1), - connected_to_ipc(false), connect_tries(0), find_window_tries(0), monitor_height(monitor_height), @@ -107,7 +106,7 @@ namespace QuickMedia { window_handle(0), parent_window(0), display(nullptr), - request_id(1), + request_id_counter(1), expected_request_id(0), request_response_data(Json::nullValue), response_data_status(ResponseDataStatus::NONE), @@ -120,7 +119,6 @@ namespace QuickMedia { abort(); } fprintf(stderr, "Video max height: %d\n", monitor_height); - ipc_server_path[0] = '\0'; } VideoPlayer::~VideoPlayer() { @@ -131,9 +129,6 @@ namespace QuickMedia { if(ipc_socket != -1) close(ipc_socket); - - if(video_process_id != -1 && ipc_server_path[0] != '\0') - remove(ipc_server_path); if(display) XCloseDisplay(display); @@ -155,31 +150,43 @@ namespace QuickMedia { VideoPlayer::Error VideoPlayer::launch_video_process(const char *path, const char *audio_path, mgl::WindowHandle _parent_window, const std::string &title, const std::string &start_time) { parent_window = _parent_window; - if(!tmpnam(ipc_server_path)) { - perror("Failed to generate ipc file name"); - return Error::FAIL_TO_GENERATE_IPC_FILENAME; + int fd[2]; + if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { + perror("Failed to create socketpair for video player"); + return Error::FAIL_TO_CREATE_SOCKET; } + ipc_socket = fd[0]; - const std::string parent_window_str = std::to_string(parent_window); - std::vector<const char*> args; - - std::string input_ipc_server_arg = "--input-ipc-server="; - input_ipc_server_arg += ipc_server_path; - std::string wid_arg = "--wid="; - wid_arg += parent_window_str; + int flags = fcntl(ipc_socket, F_GETFL, 0); + if(flags != -1) + fcntl(ipc_socket, F_SETFL, flags | O_NONBLOCK); + const std::string ipc_fd = std::to_string(fd[1]); std::string input_conf = "--input-conf=" + resource_root + "input.conf"; std::string cache_dir = "--cache-dir=" + std::move(get_cache_dir().join("media").data); + std::string wid_arg = "--wid="; + wid_arg += std::to_string(parent_window); + + std::string video_player_filepath = resource_root + "/video_player/sibs-build/linux_x86_64/" +#ifdef NDEBUG + "release/" +#else + "debug/" +#endif + "quickmedia-video-player"; + + if(get_file_type(video_player_filepath.c_str()) != FileType::REGULAR) + video_player_filepath = "/usr/bin/quickmedia-video-player"; + + std::vector<const char*> args; // TODO: Resume playback if the last video played matches the first video played next time QuickMedia is launched args.insert(args.end(), { - "mpv", - input_ipc_server_arg.c_str(), - "--cursor-autohide=no", /* "--no-input-default-bindings", "--input-vo-keyboard=no", "--no-input-cursor", */ - "--no-terminal", + video_player_filepath.c_str(), + "--cursor-autohide=no", "--save-position-on-quit=no", "--profile=pseudo-gui", // For gui when playing audio, requires a version of mpv that isn't ancient - "--no-resume-playback", + "--resume-playback=no", // TODO: Disable hr seek on low power devices? "--hr-seek=yes", "--force-seekable=yes", @@ -189,14 +196,19 @@ namespace QuickMedia { "--cache-on-disk=yes", "--cache-secs=86400", // 24 hours "--sub-font-size=60", - "--sub-margin-y=45", + "--sub-margin-y=60", "--sub-border-size=1.95", + "--input-default-bindings=yes", + "--input-vo-keyboard=yes", + "--osc=yes", //"--force_all_formats=no", cache_dir.c_str(), input_conf.c_str(), - wid_arg.c_str() + wid_arg.c_str(), + "--ipc-fd", + ipc_fd.c_str() }); - + if(is_running_wayland()) { args.push_back("--gpu-context=x11egl"); fprintf(stderr, "Wayland detected. Launching mpv in x11egl mode\n"); @@ -212,18 +224,18 @@ namespace QuickMedia { ytdl_format = "--ytdl-format=bestvideo[height<=?" + std::to_string(monitor_height) + "]+bestaudio/best"; if(!use_youtube_dl) - args.push_back("--no-ytdl"); + args.push_back("--ytdl=no"); else args.push_back(ytdl_format.c_str()); std::string mpris_arg; Path mpris_path = get_config_dir_xdg().join("mpv").join("scripts").join("mpris.so"); if(get_file_type(mpris_path) == FileType::REGULAR) - mpris_arg = "--script=" + mpris_path.data; + mpris_arg = "--scripts=" + mpris_path.data; if(!use_system_mpv_config) { args.insert(args.end(), { - "--no-config", + "--config=no", "--profile=gpu-hq", "--vo=gpu,vdpau,x11", "--hwdec=auto" @@ -240,7 +252,7 @@ namespace QuickMedia { } if(no_video) - args.push_back("--no-video"); + args.push_back("--video=no"); std::string chapters_file_arg; if(tmp_chapters_filepath[0] != '\0') { @@ -248,10 +260,9 @@ namespace QuickMedia { args.push_back(chapters_file_arg.c_str()); } - std::string audio_file_arg; if(audio_path && audio_path[0] != '\0') { - audio_file_arg = std::string("--audio-file=") + audio_path; - args.push_back(audio_file_arg.c_str()); + args.push_back("--audio-file"); + args.push_back(audio_path); } std::string start_time_arg; @@ -262,26 +273,14 @@ namespace QuickMedia { args.insert(args.end(), { "--", path, nullptr }); - fprintf(stderr, "mpv input ipc server: %s\n", ipc_server_path); - - if((ipc_socket = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { - perror("Failed to create socket for video player"); - return Error::FAIL_TO_CREATE_SOCKET; - } - - ipc_addr.sun_family = AF_UNIX; - strcpy(ipc_addr.sun_path, ipc_server_path); - - int flags = fcntl(ipc_socket, F_GETFL, 0); - if(flags != -1) - fcntl(ipc_socket, F_SETFL, flags | O_NONBLOCK); - if(exec_program_async(args.data(), &video_process_id) != 0) { + close(fd[1]); close(ipc_socket); ipc_socket = -1; return Error::FAIL_TO_LAUNCH_PROCESS; } + close(fd[1]); return Error::OK; } @@ -298,18 +297,9 @@ namespace QuickMedia { if(video_process_id == -1) return launch_video_process(path, audio_path, _parent_window, title, start_time); - // TODO: When these are used, add audio_path, title and start_time. Also handle is_youtube - Json::Value command_data(Json::arrayValue); - command_data.append("loadfile"); - command_data.append(path); - Json::Value command(Json::objectValue); - command["command"] = command_data; - - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; - const std::string cmd_str = Json::writeString(builder, command) + "\n"; - return send_command(cmd_str.c_str(), cmd_str.size()); + fprintf(stderr, "TODO: Implement VideoPlayer::load_video without restarting the video player\n"); + abort(); + return VideoPlayer::Error::INIT_FAILED; } static std::vector<Window> get_child_window(Display *display, Window window) { @@ -333,12 +323,9 @@ namespace QuickMedia { if(wait_program_non_blocking(video_process_id, &exit_status)) { fprintf(stderr, "The video player exited!, status: %d\n", exit_status); close(ipc_socket); - remove(ipc_server_path); - ipc_server_path[0] = '\0'; video_process_id = -1; ipc_socket = -1; window_handle = None; - connected_to_ipc = false; return Error::EXITED; } } @@ -352,20 +339,7 @@ namespace QuickMedia { if(find_window_tries == max_retries_find_window) return Error::FAIL_TO_FIND_WINDOW; - if(!connected_to_ipc && retry_timer.get_elapsed_time_seconds() >= RETRY_TIME_SEC) { - retry_timer.restart(); - if(connect(ipc_socket, (struct sockaddr*)&ipc_addr, sizeof(ipc_addr)) == -1) { - ++connect_tries; - if(connect_tries == MAX_RETRIES_CONNECT) { - fprintf(stderr, "Failed to connect to mpv ipc after %d seconds, last error: %s\n", (int)(RETRY_TIME_SEC * MAX_RETRIES_CONNECT), strerror(errno)); - return Error::FAIL_TO_CONNECT_TIMEOUT; - } - } else { - connected_to_ipc = true; - } - } - - if(connected_to_ipc && window_handle == 0 && retry_timer.get_elapsed_time_seconds() >= RETRY_TIME_SEC) { + if(window_handle == 0 && retry_timer.get_elapsed_time_seconds() >= RETRY_TIME_SEC) { retry_timer.restart(); std::vector<Window> child_windows = get_child_window(display, parent_window); size_t num_children = child_windows.size(); @@ -385,7 +359,7 @@ namespace QuickMedia { } } - if(connected_to_ipc && window_handle && event_callback) { + if(window_handle && event_callback) { Error err = read_ipc_func(); if(err != Error::OK) return err; @@ -395,7 +369,6 @@ namespace QuickMedia { } VideoPlayer::Error VideoPlayer::read_ipc_func() { - assert(connected_to_ipc); Json::Value json_root; Json::CharReaderBuilder json_builder; std::unique_ptr<Json::CharReader> json_reader(json_builder.newCharReader()); @@ -424,7 +397,8 @@ namespace QuickMedia { } if(expected_request_id != 0 && request_id_json.isNumeric() && request_id_json.asUInt() == expected_request_id) { - if(json_root["error"].isNull()) + const Json::Value &status_json = json_root["status"]; + if(!status_json.isString() || strcmp(status_json.asCString(), "error") == 0) response_data_status = ResponseDataStatus::ERROR; else response_data_status = ResponseDataStatus::OK; @@ -441,8 +415,11 @@ namespace QuickMedia { } VideoPlayer::Error VideoPlayer::get_time_in_file(double *result) { + Json::Value json_root(Json::objectValue); + json_root["command"] = "time-pos"; + Json::Value time_pos_json; - Error err = get_property("time-pos", &time_pos_json, Json::realValue); + Error err = send_command(json_root, &time_pos_json, Json::ValueType::realValue); if(err != Error::OK) return err; @@ -450,46 +427,51 @@ namespace QuickMedia { return err; } - VideoPlayer::Error VideoPlayer::set_property(const std::string &property_name, const Json::Value &value) { - Json::Value command_data(Json::arrayValue); - command_data.append("set_property"); - command_data.append(property_name); - command_data.append(value); - Json::Value command(Json::objectValue); - command["command"] = std::move(command_data); + VideoPlayer::Error VideoPlayer::add_subtitle(const std::string &url, const std::string &title, const std::string &lang) { + Json::Value data_json(Json::objectValue); + data_json["file"] = url; + if(!title.empty()) { + data_json["title"] = title; + if(!lang.empty()) + data_json["language"] = title; + } - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; - const std::string cmd_str = Json::writeString(builder, command) + "\n"; - return send_command(cmd_str.c_str(), cmd_str.size()); + Json::Value json_root(Json::objectValue); + json_root["command"] = "sub-add"; + json_root["data"] = std::move(data_json); + + Json::Value result; + return send_command(json_root, &result, Json::ValueType::nullValue); } - VideoPlayer::Error VideoPlayer::get_property(const std::string &property_name, Json::Value *result, Json::ValueType result_type) { - unsigned int cmd_request_id = request_id; - ++request_id; + uint32_t VideoPlayer::get_next_request_id() { + unsigned int cmd_request_id = request_id_counter; + ++request_id_counter; + // Overflow check. 0 is defined as no request, 1 is the first valid one - if(request_id == 0) - request_id = 1; + if(request_id_counter == 0) + request_id_counter = 1; + + return cmd_request_id; + } - Json::Value command_data(Json::arrayValue); - command_data.append("get_property"); - command_data.append(property_name); - Json::Value command(Json::objectValue); - command["command"] = std::move(command_data); - command["request_id"] = cmd_request_id; + VideoPlayer::Error VideoPlayer::send_command(Json::Value &json_root, Json::Value *result, Json::ValueType result_type) { + const uint32_t request_id = get_next_request_id(); + json_root["request_id"] = request_id; Json::StreamWriterBuilder builder; builder["commentStyle"] = "None"; builder["indentation"] = ""; - const std::string cmd_str = Json::writeString(builder, command) + "\n"; + const std::string cmd_str = Json::writeString(builder, json_root) + "\n"; - Error err = send_command(cmd_str.c_str(), cmd_str.size()); - if(err != Error::OK) - return err; + if(write_all(ipc_socket, cmd_str.data(), cmd_str.size()) == -1) { + fprintf(stderr, "Failed to send to ipc socket, error: %s, command: %.*s\n", strerror(errno), (int)cmd_str.size(), cmd_str.c_str()); + return Error::FAIL_TO_SEND; + } + VideoPlayer::Error err; mgl::Clock read_timer; - expected_request_id = cmd_request_id; + expected_request_id = request_id; do { err = read_ipc_func(); if(err != Error::OK) @@ -505,6 +487,12 @@ namespace QuickMedia { else err = Error::READ_INCORRECT_TYPE; } else if(response_data_status == ResponseDataStatus::ERROR) { + const char *err_msg = "Unknown"; + const Json::Value &message_json = request_response_data["message"]; + if(message_json.isString()) + err_msg = message_json.asCString(); + + fprintf(stderr, "VideoPlayer::send_command failed, error from video player: %s\n", err_msg); err = Error::READ_RESPONSE_ERROR; goto cleanup; } else { @@ -518,36 +506,4 @@ namespace QuickMedia { request_response_data = Json::Value(Json::nullValue); return err; } - - VideoPlayer::Error VideoPlayer::add_subtitle(const std::string &url, const std::string &title, const std::string &lang) { - Json::Value command_data(Json::arrayValue); - command_data.append("sub-add"); - command_data.append(url); - command_data.append("auto"); - if(!title.empty()) { - command_data.append(title); - if(!lang.empty()) - command_data.append(lang); - } - Json::Value command(Json::objectValue); - command["command"] = std::move(command_data); - - Json::StreamWriterBuilder builder; - builder["commentStyle"] = "None"; - builder["indentation"] = ""; - const std::string cmd_str = Json::writeString(builder, command) + "\n"; - return send_command(cmd_str.c_str(), cmd_str.size()); - } - - VideoPlayer::Error VideoPlayer::send_command(const char *cmd, size_t size) { - if(!connected_to_ipc) - return Error::FAIL_NOT_CONNECTED; - - if(write_all(ipc_socket, cmd, size) == -1) { - fprintf(stderr, "Failed to send to ipc socket, error: %s, command: %.*s\n", strerror(errno), (int)size, cmd); - return Error::FAIL_TO_SEND; - } - - return Error::OK; - } } diff --git a/src/plugins/LocalManga.cpp b/src/plugins/LocalManga.cpp index 83dd983..8ab8cd1 100644 --- a/src/plugins/LocalManga.cpp +++ b/src/plugins/LocalManga.cpp @@ -163,13 +163,7 @@ namespace QuickMedia { return manga_list; } - enum class ReadStatus { - READ, - UNREAD - }; - - // Returns the new read status - static bool toggle_read_save_to_file(Program *program, const std::string &manga_name, const std::string &thumbnail_url, ReadStatus &read_status) { + static bool validate_local_manga_dir_config_is_set() { if(get_config().local_manga_directory.empty()) { show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL); return false; @@ -180,6 +174,19 @@ namespace QuickMedia { return false; } + return true; + } + + enum class ReadStatus { + READ, + UNREAD + }; + + // Returns the new read status + static bool toggle_read_save_to_file(Program *program, const std::string &manga_name, const std::string &thumbnail_url, ReadStatus &read_status) { + if(!validate_local_manga_dir_config_is_set()) + return false; + Path manga_url = Path(get_config().local_manga_directory).join(manga_name); std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(manga_name, manga_url, true, true); if(chapters.empty() || chapters.front().pages.empty()) @@ -327,15 +334,8 @@ namespace QuickMedia { } PluginResult LocalMangaSearchPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { - if(get_config().local_manga_directory.empty()) { - show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL); + if(!validate_local_manga_dir_config_is_set()) return PluginResult::OK; - } - - if(get_file_type(get_config().local_manga_directory) != FileType::DIRECTORY) { - show_notification("QuickMedia", "local_manga_directory config is not set to a valid directory", Urgency::CRITICAL); - return PluginResult::OK; - } Path manga_url = Path(get_config().local_manga_directory).join(args.url); std::vector<LocalMangaChapter> chapters = get_chapters_in_manga(args.url, manga_url, false, false); @@ -371,15 +371,8 @@ namespace QuickMedia { manga_list.clear(); finished_reading_manga.clear(); - if(get_config().local_manga_directory.empty()) { - show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL); + if(!validate_local_manga_dir_config_is_set()) return PluginResult::OK; - } - - if(get_file_type(get_config().local_manga_directory) != FileType::DIRECTORY) { - show_notification("QuickMedia", "local_manga_directory config is not set to a valid directory", Urgency::CRITICAL); - return PluginResult::OK; - } manga_list = get_manga_in_directory(get_config().local_manga_directory, true); @@ -467,15 +460,8 @@ namespace QuickMedia { } PluginResult LocalMangaChaptersPage::submit(const SubmitArgs &args, std::vector<Tab> &result_tabs) { - if(get_config().local_manga_directory.empty()) { - show_notification("QuickMedia", "local_manga_directory config is not set", Urgency::CRITICAL); - return PluginResult::OK; - } - - if(get_file_type(get_config().local_manga_directory) != FileType::DIRECTORY) { - show_notification("QuickMedia", "local_manga_directory config is not set to a valid directory", Urgency::CRITICAL); + if(!validate_local_manga_dir_config_is_set()) return PluginResult::OK; - } result_tabs.push_back(Tab{nullptr, std::make_unique<LocalMangaImagesPage>(program, content_title, args.url, args.url, thumbnail_url), nullptr}); return PluginResult::OK; diff --git a/src/plugins/Youtube.cpp b/src/plugins/Youtube.cpp index d6035f2..a4212ae 100644 --- a/src/plugins/Youtube.cpp +++ b/src/plugins/Youtube.cpp @@ -2606,7 +2606,7 @@ R"END( std::string response; DownloadResult download_result = download_to_string(playback_url + "&ver=2&cpn=" + cpn + "&gl=US&hl=en", response, std::move(additional_args), true); if(download_result != DownloadResult::OK) { - fprintf(stderr, "Failed to mark video as watched because http request failed\n"); + fprintf(stderr, "Failed to mark video as watched because the http request failed\n"); return; } } diff --git a/src/plugins/youtube/YoutubeMediaProxy.cpp b/src/plugins/youtube/YoutubeMediaProxy.cpp index b33c122..9efd7ac 100644 --- a/src/plugins/youtube/YoutubeMediaProxy.cpp +++ b/src/plugins/youtube/YoutubeMediaProxy.cpp @@ -47,8 +47,6 @@ static ssize_t write_all(int fd, const void *buffer, size_t size) { namespace QuickMedia { static const int MAX_BUFFER_SIZE = 65536; static const int64_t RANGE = 5242870; - static const int64_t THROTTLED_DOWNLOAD_LIMIT_KB = 80; // TODO: What about people with really slow internet? What if the video player cache is not working and download is stuck, leading to false download speed calculation? - static const int64_t THROTTLED_DURATION_SECS = 3; static const char download_error_response_msg[] = "HTTP/1.1 500 Internal Server Error\r\n" "Content-Length: 0\r\n\r\n"; @@ -101,7 +99,7 @@ namespace QuickMedia { return true; } - YoutubeStaticMediaProxy::YoutubeStaticMediaProxy(ThrottleHandler throttle_handler) : throttle_handler(std::move(throttle_handler)) { + YoutubeStaticMediaProxy::YoutubeStaticMediaProxy() { } @@ -226,7 +224,6 @@ namespace QuickMedia { client_request_buffer.clear(); client_request_finished = false; download_started = false; - throttle_started = false; if(client_fd != -1) { close(client_fd); @@ -283,7 +280,6 @@ namespace QuickMedia { client_request_buffer.erase(header_end + 4); client_request_finished = true; download_started = false; - throttle_started = false; download_read_buffer_offset = 0; const int64_t new_start_range = header_extract_start_range(client_request_buffer); @@ -489,40 +485,12 @@ namespace QuickMedia { if(!download_started) { total_downloaded_bytes = 0; download_started = true; - throttle_started = false; download_start_time = get_boottime_milliseconds(); } total_downloaded_bytes += downloader_num_read_bytes; } } -#if 0 - if(download_started) { - const int64_t time_elapsed_sec = (get_boottime_milliseconds() - download_start_time) / 1000; - int64_t download_speed_kb_sec = 0; - if(time_elapsed > 0) - download_speed_kb_sec = (total_downloaded_bytes / time_elapsed) / 1024; - - if(download_speed_kb_sec < THROTTLED_DOWNLOAD_LIMIT_KB) { - if(throttle_started) { - if(tp.tv_sec - throttle_start_time >= THROTTLED_DURATION_SECS && !throttle_callback_called) { - total_downloaded_bytes = 0; - download_started = false; - throttle_started = false; - throttle_callback_called = true; - if(throttle_handler) - throttle_handler(); - } - } else { - throttle_started = true; - throttle_start_time = tp.tv_sec; - } - } else { - throttle_started = false; - } - } -#endif - // TODO: Remove this code and instead create the header ourselves and send it to the client. Then pipe the curl output directly to the client input. if(!download_header_finished) { download_header.append(download_read_buffer, downloader_num_read_bytes); diff --git a/video_player/README.md b/video_player/README.md index 68697d9..9a74c9c 100644 --- a/video_player/README.md +++ b/video_player/README.md @@ -7,21 +7,24 @@ Return seeking position in file in seconds ### request ``` { - "command": "time-pos" + "command": "time-pos", + "request_id": 232 // Optional } ``` ### response on success ``` { "status": "success", - "data": 112.432 + "data": 112.432, + "request_id": 232, // Optional. Its provided if request_id was provided in the request } ``` ### response on error ``` { "status": "error", - "message": "error message" + "message": "error message", + "request_id": 233 // Optional. Its provided if request_id was provided in the request } ``` ## sub-add @@ -34,25 +37,28 @@ Add a subtitle file/url that is loaded asynchronously "file": "path/to/file/or/url", "title": "title", // Optional "language": "en_us" // Optional - } + }, + "request_id": 233 // Optional } ``` ### response on success ``` { - "status": "success" + "status": "success", + "request_id": 233 // Optional. Its provided if request_id was provided in the request } ``` ### response on error ``` { "status": "error", - "message": "error message" + "message": "error message", + "request_id": 233 // Optional. Its provided if request_id was provided in the request } ``` # IPC event ``` { - "name": "file-loaded" + "event": "file-loaded" } ```
\ No newline at end of file diff --git a/video_player/include/Args.hpp b/video_player/include/Args.hpp new file mode 100644 index 0000000..610f0b0 --- /dev/null +++ b/video_player/include/Args.hpp @@ -0,0 +1,136 @@ +#pragma once + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include <string> +#include <vector> + +struct MpvProperty { + std::string key; + std::string value; +}; + +struct Args { + long wid_num = 0; + long ipc_fd_num = 0; + + const char *ipc_fd = nullptr; + const char *audio_file = nullptr; + const char *file_to_play = nullptr; + + std::vector<MpvProperty> mpv_properties; +}; + +static void usage() { + fprintf(stderr, "usage: quickmedia-video-player [--ipc-fd <fd>] [--audio-file <audio_file>] [--key=value...] <file>\n"); + fprintf(stderr, " --ipc-fd <fd> A bi-directional (socketpair) file descriptor to receive commands from. Optional\n"); + fprintf(stderr, " --audio-file <audio_file> Load the given audio file. Optional\n"); + fprintf(stderr, " --key=value Additional options in the format --key=value are passed directly to mpv as string properties. Optional\n"); + fprintf(stderr, "examples:\n"); + fprintf(stderr, " quickmedia-video-player video.mp4\n"); + fprintf(stderr, " quickmedia-video-player --wid 30481231 -- video.mp4\n"); + exit(1); +} + +static bool string_to_long(const char *str, long &result) { + errno = 0; + char *endptr = NULL; + result = strtol(str, &endptr, 0); + return endptr != str && errno == 0; +} + +static bool string_to_double(const char *str, double &result) { + errno = 0; + char *endptr = NULL; + result = strtod(str, &endptr); + return endptr != str && errno == 0; +} + +static bool fd_is_valid(int fd) { + errno = 0; + return fcntl(fd, F_GETFD) != -1 && errno != EBADF; +} + +static Args parse_args(int argc, char **argv) { + Args args; + + for(int i = 1; i < argc; ++i) { + const char *arg = argv[i]; + if(strcmp(arg, "--audio-file") == 0) { + if(args.audio_file) { + fprintf(stderr, "Error: option --audio-file was specified multiple times\n"); + usage(); + } + + if(i + 1 == argc) { + fprintf(stderr, "Error: missing audio file after option --audio-file\n"); + usage(); + } + + args.audio_file = argv[i + 1]; + ++i; + } else if(strcmp(arg, "--ipc-fd") == 0) { + if(args.ipc_fd) { + fprintf(stderr, "Error: option --ipc-fd was specified multiple times\n"); + usage(); + } + + if(i + 1 == argc) { + fprintf(stderr, "Error: missing fd after option --ipc-fd\n"); + usage(); + } + + args.ipc_fd = argv[i + 1]; + ++i; + } else if(strcmp(arg, "--") == 0) { + if(i + 1 == argc) { + fprintf(stderr, "Error: missing file option after --\n"); + usage(); + } else if(i + 1 != argc - 1) { + fprintf(stderr, "Error: more than one option was specified after --\n"); + usage(); + } + + args.file_to_play = argv[i + 1]; + ++i; + } else if(strncmp(arg, "--", 2) == 0) { + const char *equal_p = strchr(arg, '='); + if(!equal_p) { + fprintf(stderr, "Error: mpv option %s is missing \"=\"\n", arg); + usage(); + } + + args.mpv_properties.push_back({ std::string(arg + 2, equal_p - (arg + 2)), equal_p + 1 }); + } else { + if(args.file_to_play) { + fprintf(stderr, "Error: file option was specified multiple times\n"); + usage(); + } + + args.file_to_play = arg; + } + } + + if(!args.file_to_play) { + fprintf(stderr, "Error: missing file option\n"); + usage(); + } + + if(args.ipc_fd) { + if(!string_to_long(args.ipc_fd, args.ipc_fd_num)) { + fprintf(stderr, "Error: invalid number %s was specified for option --ipc-fd\n", args.ipc_fd); + usage(); + } + + if(!fd_is_valid(args.ipc_fd_num)) { + fprintf(stderr, "Error: invalid fd %s was specified for option --ipc-fd\n", args.ipc_fd); + usage(); + } + } + + return args; +}
\ No newline at end of file diff --git a/video_player/include/Utils.hpp b/video_player/include/Utils.hpp new file mode 100644 index 0000000..a5219f4 --- /dev/null +++ b/video_player/include/Utils.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include <unistd.h> +#include <errno.h> + +static ssize_t read_eintr(int fd, void *buffer, size_t size) { + while(true) { + ssize_t bytes_read = read(fd, buffer, size); + if(bytes_read == -1) { + if(errno != EINTR) + return -1; + } else { + return bytes_read; + } + } +} + +static ssize_t write_all(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) + return -1; + } else { + bytes_written += written; + } + } + return bytes_written; +}
\ No newline at end of file diff --git a/video_player/project.conf b/video_player/project.conf index b71b204..41d6bd3 100644 --- a/video_player/project.conf +++ b/video_player/project.conf @@ -4,5 +4,8 @@ type = "executable" version = "0.1.0" platforms = ["posix"] +[lang.cpp] +version = "c++17" + [dependencies] mpv = "2" diff --git a/video_player/src/main.cpp b/video_player/src/main.cpp index 42ff936..7489873 100644 --- a/video_player/src/main.cpp +++ b/video_player/src/main.cpp @@ -1,69 +1,22 @@ +#include "../include/Args.hpp" +#include "../include/Utils.hpp" #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <errno.h> #include <locale.h> -#include <fcntl.h> #include <unistd.h> #include <thread> #include <mutex> +#include <optional> +#include <set> #include <mpv/client.h> #include <json/json.h> #define COMMAND_BUFFER_MAX_SIZE 2048 -static void usage() { - fprintf(stderr, "usage: quickmedia-video-player [--wid <window_id>] [--ipc-fd <fd>] [--no-config] <file>\n"); - fprintf(stderr, " --wid <window_id> The window to embed the video player into. Optional\n"); - fprintf(stderr, " --ipc-fd <fd> A bi-directional (socketpair) file descriptor to receive commands from. Optional\n"); - fprintf(stderr, " --no-config Do not load the users mpv config (~/.config/mpv/mpv.conf). Optional, the users mpv config is loaded by default\n"); - fprintf(stderr, "examples:\n"); - fprintf(stderr, " quickmedia-video-player video.mp4\n"); - fprintf(stderr, " quickmedia-video-player --wid 30481231 -- video.mp4\n"); - exit(1); -} - -static ssize_t read_eintr(int fd, void *buffer, size_t size) { - while(true) { - ssize_t bytes_read = read(fd, buffer, size); - if(bytes_read == -1) { - if(errno != EINTR) - return -1; - } else { - return bytes_read; - } - } -} - -static ssize_t write_all(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) - return -1; - } else { - bytes_written += written; - } - } - return bytes_written; -} - -static bool string_to_long(const char *str, long &result) { - errno = 0; - char *endptr = NULL; - result = strtol(str, &endptr, 0); - return endptr != str && errno == 0; -} - -static bool fd_is_valid(int fd) { - errno = 0; - return fcntl(fd, F_GETFD) != -1 && errno != EBADF; -} - static size_t ipc_handler(mpv_handle *mpv_ctx, int fd, char *buffer, size_t buffer_size, bool &disconnected) { ssize_t bytes_read = read_eintr(fd, buffer, buffer_size); if(bytes_read < 0) { @@ -148,29 +101,65 @@ static Json::Value handle_json_command_sub_add(mpv_handle *mpv_ctx, const Json:: return response_json; } +static void send_error(const std::string &err_msg, std::optional<int64_t> request_id, int fd) { + fprintf(stderr, "Error: %s\n", err_msg.c_str()); + + Json::Value json_root(Json::objectValue); + json_root["status"] = "error"; + json_root["message"] = err_msg; + if(request_id) + json_root["request_id"] = *request_id; + + Json::StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = ""; + const std::string response_str = Json::writeString(builder, json_root) + "\n"; + + ssize_t bytes_written = write_all(fd, response_str.data(), response_str.size()); + if(bytes_written < 0) { + fprintf(stderr, "Error: ipc write failed, error: %s\n", strerror(errno)); + //exit(3); + return; + } +} + static void handle_json_command(mpv_handle *mpv_ctx, const Json::Value &json_root, int fd) { if(!json_root.isObject()) { - fprintf(stderr, "Error: expected command json root to be an object\n"); + send_error("expected command json root to be an object", std::nullopt, fd); return; } const Json::Value &command_json = json_root["command"]; if(!command_json.isString()) { - fprintf(stderr, "Error: command json is missing field \"command\" or it's not a string\n"); + send_error("command json is missing field \"command\" or it's not a string", std::nullopt, fd); return; } + std::optional<int64_t> request_id = std::nullopt; + const Json::Value &request_id_json = json_root["request_id"]; + if(!request_id_json.isNull()) { + if(request_id_json.isInt64()) { + request_id = request_id_json.asInt64(); + } else { + send_error("request_id was provided but its not an integer", std::nullopt, fd); + return; + } + } + Json::Value response_json; if(strcmp(command_json.asCString(), "time-pos") == 0) { response_json = handle_json_command_time_pos(mpv_ctx); } else if(strcmp(command_json.asCString(), "sub-add") == 0) { response_json = handle_json_command_sub_add(mpv_ctx, json_root); } else { - Json::Value response_json(Json::objectValue); + response_json = Json::Value(Json::objectValue); response_json["status"] = "error"; response_json["message"] = "invalid command " + command_json.asString() + ", expected time-pos or sub-add"; } + if(request_id) + response_json["request_id"] = *request_id; + Json::StreamWriterBuilder builder; builder["commentStyle"] = "None"; builder["indentation"] = ""; @@ -197,7 +186,7 @@ static void handle_request_commands_line_by_line(mpv_handle *mpv_ctx, int fd, ch if(json_reader->parse(command_buffer + command_offset, (const char*)space_p, &json_root, &json_errors)) { handle_json_command(mpv_ctx, json_root, fd); } else { - fprintf(stderr, "Error: failed to parse command as json, error: %s\n", json_errors.c_str()); + send_error("failed to parse command as json, error: " + json_errors, std::nullopt, fd); } command_offset = ((const char*)space_p + 1) - command_buffer; } @@ -206,7 +195,7 @@ static void handle_request_commands_line_by_line(mpv_handle *mpv_ctx, int fd, ch static void send_event(const char *event_name, int fd) { Json::Value json_root(Json::objectValue); - json_root["name"] = event_name; + json_root["event"] = event_name; Json::StreamWriterBuilder builder; builder["commentStyle"] = "None"; @@ -221,106 +210,58 @@ static void send_event(const char *event_name, int fd) { } } -static inline void check_error(int status) { +static inline void check_error(int status, const char *prefix) { if (status < 0) { - fprintf(stderr, "Error: mpv error: %s\n", mpv_error_string(status)); + fprintf(stderr, "Error: %s mpv error: %s\n", prefix, mpv_error_string(status)); exit(2); } } -struct Args { - long wid_num = 0; - long ipc_fd_num = 0; - - const char *wid = nullptr; - const char *ipc_fd = nullptr; - const char *file_to_play = nullptr; - - bool no_config = false; -}; - -static Args parse_args(int argc, char **argv) { - Args args; - - for(int i = 1; i < argc; ++i) { - const char *arg = argv[i]; - if(strcmp(arg, "--wid") == 0) { - if(args.wid) { - fprintf(stderr, "Error: option --wid was specified multiple times\n"); - usage(); - } - - if(i + 1 == argc) { - fprintf(stderr, "Error: missing window id after option --wid\n"); - usage(); - } - - args.wid = argv[i + 1]; - ++i; - } else if(strcmp(arg, "--ipc-fd") == 0) { - if(args.ipc_fd) { - fprintf(stderr, "Error: option --ipc-fd was specified multiple times\n"); - usage(); - } - - if(i + 1 == argc) { - fprintf(stderr, "Error: missing fd after option --ipc-fd\n"); - usage(); - } - - args.ipc_fd = argv[i + 1]; - ++i; - } else if(strcmp(arg, "--no-config") == 0) { - args.no_config = true; - } else if(strcmp(arg, "--") == 0) { - if(i + 1 == argc) { - fprintf(stderr, "Error: missing file option after --\n"); - usage(); - } else if(i + 1 != argc - 1) { - fprintf(stderr, "Error: more than one option was specified after --\n"); - usage(); - } - - args.file_to_play = argv[i + 1]; - ++i; - } else if(strncmp(arg, "--", 2) == 0) { - fprintf(stderr, "Error: invalid option %s\n", arg); - usage(); - } else { - if(args.file_to_play) { - fprintf(stderr, "Error: file option was specified multiple times\n"); - usage(); - } +static bool is_num(char c) { + return c >= '0' && c <= '9'; +} - args.file_to_play = arg; - } +static bool string_looks_like_int(const char *str, size_t size) { + for(size_t i = 0; i < size; ++i) { + char c = str[i]; + if(!is_num(c) && c != '-') + return false; } + return true; +} - if(!args.file_to_play) { - fprintf(stderr, "Error: missing file option\n"); - usage(); +static bool string_looks_like_double(const char *str, size_t size) { + for(size_t i = 0; i < size; ++i) { + char c = str[i]; + if(!is_num(c) && c != '-' && c != '.') + return false; } + return true; +} - if(args.wid) { - if(!string_to_long(args.wid, args.wid_num)) { - fprintf(stderr, "Error: invalid number %s was specified for option --wid\n", args.wid); - usage(); - } - } +static void mpv_set_before_init_options(mpv_handle *mpv_ctx, const Args &args) { + long value_long = 0; + double value_double = 0; - if(args.ipc_fd) { - if(!string_to_long(args.ipc_fd, args.ipc_fd_num)) { - fprintf(stderr, "Error: invalid number %s was specified for option --ipc-fd\n", args.ipc_fd); - usage(); - } + std::set<std::string> known_string_properties = { "start", "force-media-title" }; - if(!fd_is_valid(args.ipc_fd_num)) { - fprintf(stderr, "Error: invalid fd %s was specified for option --ipc-fd\n", args.ipc_fd); - usage(); - } + for(const MpvProperty &property : args.mpv_properties) { + const bool is_string_property = known_string_properties.find(property.key) != known_string_properties.end(); + + if(!is_string_property && string_looks_like_int(property.value.c_str(), property.value.size()) && string_to_long(property.value.c_str(), value_long)) + check_error(mpv_set_option(mpv_ctx, property.key.c_str(), MPV_FORMAT_INT64, &value_long), property.key.c_str()); + else if(!is_string_property && string_looks_like_double(property.value.c_str(), property.value.size()) && string_to_double(property.value.c_str(), value_double)) + check_error(mpv_set_option(mpv_ctx, property.key.c_str(), MPV_FORMAT_DOUBLE, &value_double), property.key.c_str()); + else + check_error(mpv_set_option_string(mpv_ctx, property.key.c_str(), property.value.c_str()), property.key.c_str()); } +} - return args; +static void mpv_set_after_load_options(mpv_handle *mpv_ctx, const Args &args) { + if(args.audio_file) { + const char* cmd_args[] = { "audio-add", args.audio_file, "select", "Default track", nullptr }; + check_error(mpv_command_async(mpv_ctx, 0, cmd_args), "audio-add"); + } } int main(int argc, char **argv) { @@ -335,23 +276,13 @@ int main(int argc, char **argv) { return 1; } - check_error(mpv_set_option_string(mpv_ctx, "input-default-bindings", "yes")); - check_error(mpv_set_option_string(mpv_ctx, "input-vo-keyboard", "yes")); - check_error(mpv_set_option_string(mpv_ctx, "osc", "yes")); - - check_error(mpv_set_option_string(mpv_ctx, "profile", "gpu-hq")); - check_error(mpv_set_option_string(mpv_ctx, "vo", "gpu")); - check_error(mpv_set_option_string(mpv_ctx, "hwdec", "auto")); - if(!args.no_config) - check_error(mpv_set_option_string(mpv_ctx, "config", "yes")); - - if(args.wid) - check_error(mpv_set_option_string(mpv_ctx, "wid", args.wid)); - - check_error(mpv_initialize(mpv_ctx)); + 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)); + check_error(mpv_command(mpv_ctx, cmd), "loadfile"); + + check_error(mpv_observe_property(mpv_ctx, 0, "idle-active", MPV_FORMAT_FLAG), "observe idle-active"); char command_buffer[COMMAND_BUFFER_MAX_SIZE]; size_t command_buffer_size = 0; @@ -372,27 +303,39 @@ int main(int argc, char **argv) { Json::Value json_root; std::string json_errors; + bool file_started = false; + while (running) { mpv_event *event = mpv_wait_event(mpv_ctx, -1.0); - if (event->event_id == MPV_EVENT_SHUTDOWN) { - running = false; - break; - } - if(event->event_id != MPV_EVENT_NONE) + if(args.ipc_fd && event->event_id != MPV_EVENT_NONE) send_event(mpv_event_name(event->event_id), args.ipc_fd_num); - // TODO: Check if we can get here without mpv_wakeup being called from ipc_handler - std::lock_guard<std::mutex> lock(command_mutex); - - // Other end of the ipc socket has disconnected - if(ipc_disconnected) { - fprintf(stderr, "Warning: the other end of the ipc fd was closed, closing the video player...\n"); + if(event->event_id == MPV_EVENT_START_FILE && !file_started) { + file_started = true; + mpv_set_after_load_options(mpv_ctx, args); + } else if(event->event_id == MPV_EVENT_SHUTDOWN) { running = false; break; + } else if(event->event_id == MPV_EVENT_PROPERTY_CHANGE && file_started) { + // End of file (idle) + mpv_event_property *property = (mpv_event_property*)event->data; + if(strcmp(property->name, "idle-active") == 0 && *(int*)property->data == 1) { + running = false; + break; + } } - handle_request_commands_line_by_line(mpv_ctx, args.ipc_fd_num, command_buffer, command_buffer_size, json_root, json_errors); + if(args.ipc_fd) { + std::lock_guard<std::mutex> lock(command_mutex); + // Other end of the ipc socket has disconnected + if(ipc_disconnected) { + fprintf(stderr, "Warning: the other end of the ipc fd was closed, closing the video player...\n"); + running = false; + break; + } + handle_request_commands_line_by_line(mpv_ctx, args.ipc_fd_num, command_buffer, command_buffer_size, json_root, json_errors); + } } mpv_terminate_destroy(mpv_ctx); |