summaryrefslogtreecommitdiff
path: root/ytdlsb-main.c
diff options
context:
space:
mode:
authordyknon dyknonr5fjp2026-01-11 23:04:54 +0900
committerdyknon dyknonr5fjp2026-01-11 23:04:54 +0900
commitb7bc3f0d4488b6822506b9f93121249d078c38e3 (patch)
tree99d287cdefbfeeb20c4470c4f1bfd1eb909e7538 /ytdlsb-main.c
parent8439d0383adaee15bfd9a82a4d76db352690750e (diff)
Rewritten: stop using ffmpeg. better flexibility about image size.
Diffstat (limited to 'ytdlsb-main.c')
-rw-r--r--ytdlsb-main.c664
1 files changed, 664 insertions, 0 deletions
diff --git a/ytdlsb-main.c b/ytdlsb-main.c
new file mode 100644
index 0000000..4956743
--- /dev/null
+++ b/ytdlsb-main.c
@@ -0,0 +1,664 @@
+#include <mpv/client.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "ytdlsb-utils.h"
+#include "ytdlsb-tasks.h"
+#include "ytdlsb-mpv.h"
+#include <cJSON.h>
+#include <curl/curl.h>
+#include <webp/decode.h>
+
+enum ytdlsb_hook{
+ YTDLSB_HOOK_PRELOADED,
+ YTDLSB_HOOK_UNLOAD
+};
+enum ytdlsb_observe{
+ YTDLSB_OBSERVE_OSC
+};
+enum ytdlsb_osd_id{
+ YTDLSB_OSD_IMG
+};
+
+enum ytdlsb_sb_state{
+ YTDLSB_SB_EMPTY = 0,
+ YTDLSB_SB_FAILED,
+ YTDLSB_SB_JPEG,
+ YTDLSB_SB_WEBP,
+ YTDLSB_SB_RAW,
+};
+struct ytdlsb_sb_fragment{
+ char *url;
+ enum ytdlsb_sb_state state;
+ char *data;
+ size_t data_len;
+ double start;
+};
+struct ytdlsb_sb{
+ double fps;
+ size_t width;
+ size_t height;
+ size_t columns;
+ size_t rows;
+ struct ytdlsb_sb_fragment *fragments;
+ size_t fragment_num;
+};
+
+enum ytdlsb_tasknum{
+ YTDLSB_TASK_MPVEV = 0,
+ YTDLSB_TASK_PRELOAD,
+ YTDLSB_TASK_NUM
+};
+
+struct ytdlsb{
+ mpv_handle *m;
+ struct ytdlsb_tasks t;
+ struct ytdlsb_sb best_sb;
+ struct ytdlsb_sb fast_sb;
+
+ struct curl_slist *preload_headers;
+ size_t preload_p;
+ CURL *preload_easy;
+ CURLM *preload_multi;
+
+ double current_sb_show;
+
+ int exit;
+};
+
+int ytdlsb_sb_some(struct ytdlsb_sb *sb){
+ return sb->fragments != NULL;
+}
+
+#define FRAG(p, sb, pp) \
+ if(p->preload_p < p->fast_sb.fragment_num){ \
+ pp = p->preload_p; \
+ sb = &p->fast_sb; \
+ }else{ \
+ pp = p->preload_p - p->fast_sb.fragment_num; \
+ assert(pp < p->best_sb.fragment_num); \
+ sb = &p->best_sb; \
+ }
+size_t ytdlsb_fragment_preload_write(
+ char *data, size_t, size_t nmemb, void *_p
+){
+ struct ytdlsb *p = _p;
+ size_t pp;
+ struct ytdlsb_sb *sb;
+ FRAG(p, sb, pp);
+
+ //eprintf("received %zubytes\n", nmemb);
+
+ struct ytdlsb_sb_fragment *f = &sb->fragments[pp];
+ if(f->data == NULL) f->data_len = 0;
+ f->data_len += nmemb;
+ CKA(f->data_len, >= nmemb);
+ f->data = CKAR(realloc(f->data, f->data_len));
+ memcpy(&f->data[f->data_len - nmemb], data, nmemb);
+ return nmemb;
+}
+int ytdlsb_start_fragment_preload(struct ytdlsb *p){
+ size_t pp;
+ struct ytdlsb_sb *sb;
+
+ FRAG(p, sb, pp);
+ assert(sb->fragments[pp].state == YTDLSB_SB_EMPTY);
+
+#define OPT(k, v) CKZ(err, curl_easy_setopt(p->preload_easy, k, v))
+ OPT(CURLOPT_URL, sb->fragments[pp].url);
+#undef OPT
+ return 0;
+err: return -1;
+}
+int ytdlsb_abort_load(struct ytdlsb *p){
+ if(p->preload_easy){
+ CKZ(err, curl_multi_remove_handle(p->preload_multi, p->preload_easy));
+ curl_easy_cleanup(p->preload_easy);
+ p->preload_easy = NULL;
+ curl_slist_free_all(p->preload_headers);
+ p->preload_headers = NULL;
+ }
+ p->t.tasks[YTDLSB_TASK_PRELOAD].process = NULL;
+ return 0;
+err: return -1;
+}
+int ytdlsb_done_fragment_preload(struct ytdlsb *p){
+ size_t max_prelp = p->fast_sb.fragment_num + p->best_sb.fragment_num;
+ while(1){
+ size_t pp;
+ struct ytdlsb_sb *sb;
+ p->preload_p++;
+ if(p->preload_p < max_prelp){
+ FRAG(p, sb, pp);
+ if(sb->fragments[pp].state == YTDLSB_SB_EMPTY) break;
+ }else{
+ break;
+ }
+ }
+ if(p->preload_p < max_prelp){
+ CKZ(err, curl_multi_remove_handle(
+ p->preload_multi, p->preload_easy));
+ ytdlsb_start_fragment_preload(p);
+ CKZ(err, curl_multi_add_handle(
+ p->preload_multi, p->preload_easy));
+ eprintf("downloading storyboard: %zu/%zu\n",
+ p->preload_p, max_prelp);
+ return 1;
+ }else{
+ eprintf("storyboard downloaded\n");
+ CKP(err, ytdlsb_abort_load(p));
+ return 0;
+ }
+err: return -1;
+}
+int ytdlsb_process_preload(struct ytdlsb_task *t){
+ struct ytdlsb *p = t->data;
+ size_t max_prelp = p->fast_sb.fragment_num + p->best_sb.fragment_num;
+ int running;
+ while(1){
+ CKZ(err, curl_multi_perform(p->preload_multi, &running));
+
+ if(!running){
+ int mq;
+ size_t pp;
+ struct ytdlsb_sb *sb;
+ FRAG(p, sb, pp);
+
+ CURLMsg *m = curl_multi_info_read(p->preload_multi, &mq);
+ assert(m != NULL);
+ assert(m->msg == CURLMSG_DONE);
+ assert(m->easy_handle == p->preload_easy);
+ assert(mq == 0);
+
+ if(m->data.result == 0){
+ long respc;
+ char *mtype;
+ CKZ(err, curl_easy_getinfo(p->preload_easy,
+ CURLINFO_RESPONSE_CODE, &respc));
+ CKZ(err, curl_easy_getinfo(p->preload_easy,
+ CURLINFO_CONTENT_TYPE, &mtype));
+ if(respc == 200){
+ if(strcmp(mtype, "image/jpeg") == 0){
+ sb->fragments[pp].state = YTDLSB_SB_JPEG;
+ }else if(strcmp(mtype, "image/webp") == 0){
+ sb->fragments[pp].state = YTDLSB_SB_WEBP;
+ }else{
+ eprintf("invalid storyboard type: %s\n", mtype);
+ sb->fragments[pp].state = YTDLSB_SB_FAILED;
+ }
+ }else{
+ eprintf("invalid storyboard response: %03ld\n", respc);
+ sb->fragments[pp].state = YTDLSB_SB_FAILED;
+ }
+ }else{
+ eprintf("storyboard download error: %d\n", (int)m->data.result);
+ sb->fragments[pp].state = YTDLSB_SB_FAILED;
+ }
+
+ if(CKP(err, ytdlsb_done_fragment_preload(p))) continue;
+ break;
+ }else{
+ break;
+ }
+ }
+ CKP(err, ytdlsb_task_fdto_from_curl(&p->t.tasks[YTDLSB_TASK_PRELOAD],
+ 0, 1, p->preload_multi));
+ return 0;
+err: return -1;
+}
+#undef FRAG
+int ytdlsb_start_preload(struct ytdlsb *p){
+ CKP(err, ytdlsb_abort_load(p));
+
+ if(!ytdlsb_sb_some(&p->fast_sb)){
+ return 0;
+ }
+
+ if(!p->preload_multi){
+ p->preload_multi = CKR(err, curl_multi_init());
+ }
+ p->preload_easy = CKR(err, curl_easy_init());
+
+ p->preload_headers = CKR(err,
+ curl_slist_append(p->preload_headers, "Accept: image/webp,*/*;q=0.1"));
+#define OPT(k, v) CKZ(err, curl_easy_setopt(p->preload_easy, k, v))
+ OPT(CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
+ OPT(CURLOPT_PROTOCOLS_STR, "http,https");
+ OPT(CURLOPT_FOLLOWLOCATION, 1l);
+ OPT(CURLOPT_NOSIGNAL, 1l);
+ OPT(CURLOPT_TIMEOUT_MS, 10000l);
+ OPT(CURLOPT_CONNECTTIMEOUT_MS, 3000l);
+ OPT(CURLOPT_HTTPHEADER, p->preload_headers);
+ OPT(CURLOPT_WRITEFUNCTION, ytdlsb_fragment_preload_write);
+ OPT(CURLOPT_WRITEDATA, (void *)p);
+#undef OPT
+
+ p->preload_p = 0;
+ ytdlsb_start_fragment_preload(p);
+ CKZ(err, curl_multi_add_handle(p->preload_multi, p->preload_easy));
+
+ p->t.tasks[YTDLSB_TASK_PRELOAD].process = ytdlsb_process_preload;
+ p->t.tasks[YTDLSB_TASK_PRELOAD].data = p;
+ ytdlsb_process_preload(&p->t.tasks[YTDLSB_TASK_PRELOAD]);
+ return 0;
+err: return -1;
+}
+int ytdlsb_cleanup_load(struct ytdlsb *p){
+ CKP(err, ytdlsb_abort_load(p));
+ if(p->preload_multi){
+ CKZ(err, curl_multi_cleanup(p->preload_multi));
+ p->preload_multi = NULL;
+ }
+ free(p->t.tasks[YTDLSB_TASK_PRELOAD].pollfd);
+ return 0;
+err: return -1;
+}
+
+void ytdlsb_sb_destroy(struct ytdlsb_sb *sb){
+ if(ytdlsb_sb_some(sb)){
+ for(size_t i = 0; i < sb->fragment_num; i++){
+ free(sb->fragments[i].url);
+ free(sb->fragments[i].data);
+ }
+ free(sb->fragments);
+ sb->fragments = NULL;
+ }
+}
+void ytdlsb_sb_dup(struct ytdlsb_sb *dst, struct ytdlsb_sb *src){
+ ytdlsb_sb_destroy(dst);
+ if(ytdlsb_sb_some(src)){
+ memcpy(dst, src, sizeof(*dst));
+ dst->fragments = CKAR(reallocarray(NULL,
+ dst->fragment_num, sizeof(*dst->fragments)));
+ for(size_t i = 0; i < dst->fragment_num; i++){
+ struct ytdlsb_sb_fragment *dstf = &dst->fragments[i];
+ struct ytdlsb_sb_fragment *srcf = &src->fragments[i];
+ dstf->url = CKAR(strdup(srcf->url));
+ dstf->start = srcf->start;
+ dstf->state = srcf->state;
+ if(srcf->data){
+ dstf->data = CKAR(malloc(srcf->data_len));
+ memcpy(dstf->data, srcf->data, srcf->data_len);
+ dstf->data_len = srcf->data_len;
+ }else{
+ dstf->data = NULL;
+ }
+ }
+ }
+}
+double ytdlsb_sb_quality(struct ytdlsb_sb *sb){
+ assert(ytdlsb_sb_some(sb));
+ return sb->width * sb->height * sb->fps;
+}
+int ytdlsb_sb_import_json(struct ytdlsb_sb *sb, cJSON *json){
+ cJSON *frag, *p;
+ double start;
+ ytdlsb_sb_destroy(sb);
+#define JSON_OI(json, key) cJSON_GetObjectItemCaseSensitive(json, key)
+#define JSON_NUM(json) \
+ CK_MSG(err, cJSON_GetNumberValue(json), == cktmp, "NaN check")
+#define JSON_STR(json) CKR(err, cJSON_GetStringValue(json))
+ char *format_id = JSON_STR(JSON_OI(json, "format_id"));
+ if(strncmp(format_id, "sb", 2) != 0) goto err;
+ if(format_id[2] < '0' || '9' < format_id[2]) goto err;
+ if(format_id[3] != 0) goto err;
+
+ sb->fps = JSON_NUM(JSON_OI(json, "fps"));
+ CK(err, sb->fps, > 0);
+#define SIZEF(f) \
+ sb->f = CK(err, TRY_NUMCAST(err, size_t, JSON_NUM(JSON_OI(json, #f))), > 0)
+ SIZEF(width);
+ SIZEF(height);
+ SIZEF(columns);
+ SIZEF(rows);
+#undef SIZEF
+ frag = JSON_OI(json, "fragments");
+ CK(err, cJSON_IsArray(frag),);
+ sb->fragment_num = CK(err, cJSON_GetArraySize(frag), > 0);
+ CK_MSG(err, sb->fragment_num, < 4096, "Too many fragments");
+ CK(err, sb->fragment_num, > 0);
+ sb->fragments = CKAR(reallocarray(NULL,
+ sb->fragment_num, sizeof(*sb->fragments)));
+ p = frag->child;
+ start = 0;
+ for(size_t i = 0; i < sb->fragment_num; i++){
+ assert(p);
+ struct ytdlsb_sb_fragment *f = &sb->fragments[i];
+ f->url = CKR(err, strdup(JSON_STR(JSON_OI(p, "url"))));
+ f->state = YTDLSB_SB_EMPTY;
+ f->data = NULL;
+ f->start = start;
+ start += JSON_NUM(JSON_OI(p, "duration"));
+ p = p->next;
+ }
+ return 0;
+#undef JSON_OI
+#undef JSON_NUM
+#undef JSON_STR
+err:
+ free(sb->fragments); sb->fragments = NULL;
+ return -1;
+}
+void ytdlsb_sb_dump(struct ytdlsb_sb *sb){
+ if(!ytdlsb_sb_some(sb)){
+ eprintf("None\n");
+ return;
+ }
+ eprintf("FPS: %f\n", sb->fps);
+ eprintf("WxH, CxR: %zux%zu, %zux%zu\n",
+ sb->width, sb->height, sb->columns, sb->rows);
+ for(size_t i = 0; i < sb->fragment_num; i++){
+ eprintf("%s\n", sb->fragments[i].url);
+ if(5 < i){
+ eprintf("...\n");
+ break;
+ }
+ }
+}
+
+// <0 = error (only on first error)
+// 0 = not loaded
+// 1 = ref
+// 2 = copy(?)
+int ytdlsb_sb_get_frame(
+ struct ytdlsb_sb *sb, double pos, char **data, size_t *len
+){
+ struct ytdlsb_sb_fragment *f = NULL;
+ for(size_t i = 0; i < sb->fragment_num; i++){
+ struct ytdlsb_sb_fragment *cf = &sb->fragments[i];
+ if(cf->start <= pos) f = cf;
+ else break;
+ }
+ //eprintf("fragment %d\n", (int)((f-sb->fragments)/sizeof(*f)));
+ if(!f || f->state == YTDLSB_SB_EMPTY) return 0;
+ //eprintf(".!!!\n");
+ if(f->state == YTDLSB_SB_WEBP){
+ //eprintf("webp!\n");
+ char *decoded;
+ int width, height;
+ if(is_little_endian()){
+ decoded = (char *)CKR(decode_failed, WebPDecodeBGRA(
+ (uint8_t *)f->data, f->data_len, &width, &height));
+ }else{
+ decoded = (char *)CKR(decode_failed, WebPDecodeRGBA(
+ (uint8_t *)f->data, f->data_len, &width, &height));
+ }
+ CK(sizemismatch, width, <= sb->columns * sb->width);
+ CK(sizemismatch, width % sb->width, == 0);
+ CK(sizemismatch, height, <= sb->rows * sb->height);
+ CK(sizemismatch, height % sb->height, == 0);
+ f->data_len = TRY_NUMCAST(sizemismatch, size_t, width)
+ * TRY_NUMCAST(sizemismatch, size_t, height) * 4;
+ free(f->data);
+ f->data = CKAR(malloc(f->data_len));
+ size_t frame = sb->width * sb->height * 4;
+ for(size_t y = 0; y < height/sb->height; y++){
+ for(size_t x = 0; x < width/sb->width; x++){
+ char *src_orig = decoded + (x*sb->width + y*sb->height*width)*4;
+ char *dest_orig = f->data + (x + y*(width/sb->width))*frame;
+ for(size_t i = 0; i < sb->height; i++){
+ assert(src_orig + i*width*4 + 4*sb->width
+ <= decoded+f->data_len);
+ assert(dest_orig + i*sb->width*4 + 4*sb->width
+ <= f->data+f->data_len);
+ memcpy(dest_orig + i*sb->width*4,
+ src_orig + i*width*4, 4*sb->width);
+ }
+ }
+ }
+ free(decoded);
+ f->state = YTDLSB_SB_RAW;
+ goto webpok;
+sizemismatch:
+ free(decoded);
+ goto decode_failed;
+webpok:
+ }else if(f->state == YTDLSB_SB_JPEG){
+ eprintf("Jpeg decoding is not implemented yet.\n");
+ goto decode_failed;
+ }
+ if(f->state == YTDLSB_SB_RAW){
+ //eprintf("raw!\n");
+ size_t frame = sb->width * sb->height * 4;
+ size_t findex = (pos - f->start) * sb->fps;
+ if(f->data_len < findex*frame+frame){
+ findex = (f->data_len - frame) / frame;
+ }
+ *data = f->data + frame*findex;
+ *len = frame;
+ return 1;
+ }
+ return 0;
+decode_failed:
+ f->state = YTDLSB_SB_FAILED;
+ return -1;
+}
+int ytdlsb_get_frame(struct ytdlsb *p, double pos,
+ char **data, size_t *w, size_t *h
+){
+ size_t len;
+ if(ytdlsb_sb_some(&p->best_sb)
+ && ytdlsb_sb_get_frame(&p->best_sb, pos, data, &len) == 1
+ ){
+ assert(p->best_sb.width * p->best_sb.height * 4 == len);
+ *w = p->best_sb.width;
+ *h = p->best_sb.height;
+ return 1;
+ }
+ if(ytdlsb_sb_some(&p->fast_sb)
+ && ytdlsb_sb_get_frame(&p->fast_sb, pos, data, &len) == 1
+ ){
+ assert(p->fast_sb.width * p->fast_sb.height * 4 == len);
+ *w = p->fast_sb.width;
+ *h = p->fast_sb.height;
+ return 1;
+ }
+ return 0;
+}
+
+int ytdlsb_hook_on_preloaded(struct ytdlsb *p){
+ ytdlsb_sb_destroy(&p->best_sb);
+ ytdlsb_sb_destroy(&p->fast_sb);
+ char *ytjson_w, *ytjson;
+ cJSON *yt_w, *yt, *yt_formats, *fmt;
+ ytjson_w = mpv_get_property_string(
+ p->m, "user-data/mpv/ytdl/json-subprocess-result");
+ if(!ytjson_w) goto noytdl;
+ yt_w = CKR(broken_json_w, cJSON_Parse(ytjson_w));
+ ytjson = CKR(broken_json,
+ cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(yt_w, "stdout")));
+ yt = CKR(broken_json, cJSON_Parse(ytjson));
+ yt_formats = CKR(end_json,
+ cJSON_GetObjectItemCaseSensitive(yt, "formats"));
+ CKT(end_json, cJSON_IsArray(yt_formats));
+ while(fmt = cJSON_DetachItemFromArray(yt_formats, 0)){
+ struct ytdlsb_sb sb = {0};
+ if(ytdlsb_sb_import_json(&sb, fmt) >= 0){
+ int refed = 0;
+ if(!ytdlsb_sb_some(&p->best_sb)
+ || ytdlsb_sb_quality(&p->best_sb) < ytdlsb_sb_quality(&sb)
+ ){
+ ytdlsb_sb_destroy(&p->best_sb);
+ memcpy(&p->best_sb, &sb, sizeof(sb));
+ refed = 1;
+ }
+ if(!ytdlsb_sb_some(&p->fast_sb)
+ || ytdlsb_sb_quality(&p->fast_sb) > ytdlsb_sb_quality(&sb)
+ ){
+ ytdlsb_sb_destroy(&p->fast_sb);
+ if(refed){
+ ytdlsb_sb_dup(&p->fast_sb, &sb);
+ }else{
+ memcpy(&p->fast_sb, &sb, sizeof(sb));
+ refed = 1;
+ }
+ }
+ if(!refed) ytdlsb_sb_destroy(&sb);
+ }
+ cJSON_Delete(fmt);
+ }
+ ytdlsb_start_preload(p);
+ ytdlsb_sb_dump(&p->fast_sb);
+ ytdlsb_sb_dump(&p->best_sb);
+end_json:
+ cJSON_Delete(yt);
+broken_json:
+ cJSON_Delete(yt_w);
+broken_json_w:
+ mpv_free(ytjson_w);
+noytdl:
+ return 0;
+}
+
+int ytdlsb_hook_on_unload(struct ytdlsb *p){
+ ytdlsb_abort_load(p);
+ ytdlsb_sb_destroy(&p->best_sb);
+ ytdlsb_sb_destroy(&p->fast_sb);
+ return 0;
+}
+int ytdlsb_propchange_osc(struct ytdlsb *p, mpv_event_property *ep){
+ char *image;
+ size_t width, height;
+ double pos;
+ char *osc_activeelem;
+ int osc_visible;
+ int ret = -1;
+
+#define GETPROP(key, ty, ptr, def) { \
+ int getres = mpv_get_property(p->m, key, ty, ptr); \
+ if(getres < 0 && getres != MPV_ERROR_PROPERTY_NOT_FOUND \
+ && getres != MPV_ERROR_PROPERTY_UNAVAILABLE \
+ ){ \
+ goto err; \
+ }else if(getres < 0){ \
+ *(ptr) = def; \
+ } \
+}
+ GETPROP("user-data/osc/visible",
+ MPV_FORMAT_FLAG, &osc_visible, 0);
+ GETPROP("user-data/osc/active-element",
+ MPV_FORMAT_STRING, &osc_activeelem, NULL);
+ GETPROP("user-data/osc/seekbar/possec",
+ MPV_FORMAT_DOUBLE, &pos, -1);
+#undef GETPROP
+
+ if(!osc_visible) pos = -1;
+ if(!osc_activeelem || strcmp(osc_activeelem, "\"seekbar\"")) pos = -1;
+ if(pos >= 0 && CKP(err, ytdlsb_get_frame(p, pos, &image, &width, &height))){
+ CKP(err, ytdlsb_mpv_overlay_add(p->m,
+ YTDLSB_OSD_IMG, 0, 0,
+ image, 0, width, height, width*4,
+ width, height
+ ));
+ p->current_sb_show = pos;
+ }else{
+ CKP(err, ytdlsb_mpv_overlay_remove(p->m, YTDLSB_OSD_IMG));
+ p->current_sb_show = -1;
+ }
+ ret = 0;
+err:
+ mpv_free(osc_activeelem);
+ return ret;
+}
+
+int ytdlsb_ev(struct ytdlsb *p, mpv_event *ev){
+ switch(ev->event_id){
+ case MPV_EVENT_HOOK:
+ mpv_event_hook *hook = ev->data;
+ switch(ev->reply_userdata){
+ case YTDLSB_HOOK_PRELOADED:
+ CKP(err, ytdlsb_hook_on_preloaded(p));
+ break;
+ case YTDLSB_HOOK_UNLOAD:
+ CKP(err, ytdlsb_hook_on_unload(p));
+ break;
+ }
+ CKM(err, mpv_hook_continue(p->m, hook->id));
+ return 0;
+ case MPV_EVENT_PROPERTY_CHANGE:
+ switch(ev->reply_userdata){
+ case YTDLSB_OBSERVE_OSC:
+ CKP(err, ytdlsb_propchange_osc(p, ev->data));
+ break;
+ }
+ return 0;
+ default:
+ return 0;
+ }
+err:
+ return -1;
+}
+
+void ytdlsb_mpvev_waker(void *_p){
+ struct ytdlsb *p = _p;
+ CKAP(ytdlsb_task_event_wake(&p->t.tasks[YTDLSB_TASK_MPVEV]));
+}
+int ytdlsb_mpvev(void *_p){
+ struct ytdlsb *p = _p;
+
+ while(1){
+ mpv_event *ev = CKR(err, mpv_wait_event(p->m, 0));
+ if(ev->event_id == MPV_EVENT_SHUTDOWN){
+ p->exit = 1;
+ break;
+ }else if(ev->event_id == MPV_EVENT_NONE){
+ break;
+ }else{
+ CKP(err, ytdlsb_ev(p, ev));
+ }
+ }
+
+ return 0;
+err:
+ return -1;
+}
+
+int mpv_open_cplugin(mpv_handle *h){
+ int ret = -1;
+ struct ytdlsb p = {0};
+ struct ytdlsb_task tasks[YTDLSB_TASK_NUM] = {{0}};
+ curl_version_info_data *curl_v;
+
+ curl_v = CKR(err, curl_version_info(CURLVERSION_NOW));
+ // Main thread of mpv is blocked here.
+ // I think it is safe enough to call this.
+ // (But can make conflicts with other plugins...)
+ CKZ(err, curl_global_init(CURL_GLOBAL_ALL));
+
+ p.m = h;
+ p.t.tasks = tasks;
+ p.t.task_num = YTDLSB_TASK_NUM;
+ p.current_sb_show = -1;
+ CKM(err, mpv_hook_add(p.m, YTDLSB_HOOK_PRELOADED, "on_preloaded", 0));
+ CKM(err, mpv_hook_add(p.m, YTDLSB_HOOK_UNLOAD, "on_unload", 0));
+ CKM(err, mpv_observe_property(p.m, YTDLSB_OBSERVE_OSC,
+ "user-data/osc/visible", MPV_FORMAT_NONE));
+ CKM(err, mpv_observe_property(p.m, YTDLSB_OBSERVE_OSC,
+ "user-data/osc/active-element", MPV_FORMAT_NONE));
+ CKM(err, mpv_observe_property(p.m, YTDLSB_OBSERVE_OSC,
+ "user-data/osc/seekbar/possec", MPV_FORMAT_NONE));
+
+ CKP(err, ytdlsb_task_event_init(&tasks[YTDLSB_TASK_MPVEV],
+ ytdlsb_mpvev, &p));
+ mpv_set_wakeup_callback(h, ytdlsb_mpvev_waker, &p);
+ CKP(err, ytdlsb_task_event_wake(&tasks[YTDLSB_TASK_MPVEV]));
+
+ while(!p.exit){
+ CKP(err, ytdlsb_tasks_step(&p.t));
+ }
+
+ CKP(err, ytdlsb_cleanup_load(&p));
+ ytdlsb_task_event_destroy(&tasks[YTDLSB_TASK_MPVEV]);
+ ytdlsb_sb_destroy(&p.best_sb);
+ ytdlsb_sb_destroy(&p.fast_sb);
+
+ if(curl_v->features & CURL_VERSION_THREADSAFE){
+ // If it is not thread-safe, not clean.
+ // It may lead to memory leaks. Better than UB.
+ curl_global_cleanup();
+ }
+ ret = 0;
+err:
+ return ret;
+}