Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

native/tapes: render tape video frames during playback

+362 -6
+42 -6
fedac/native/pieces/tapes.mjs
··· 28 28 let frame = 0; 29 29 let cloudFetching = false; 30 30 let cloudLoadedOnce = false; 31 + let nowPlaying = ""; 31 32 32 33 function fmtBytes(n) { 33 34 if (!Number.isFinite(n)) return "?"; ··· 150 151 // Play via deck 0 — same mechanism the dj piece uses. 151 152 const ok = sound?.deck?.load?.(0, t.path); 152 153 if (ok) { 154 + const videoOk = sound?.deck?.prepareVideo?.(0, 120, 68, 12); 153 155 sound.deck.play(0); 154 - msg("playing local " + t.name); 156 + nowPlaying = t.name; 157 + msg((videoOk ? "playing local " : "playing audio-only local ") + t.name); 155 158 sound?.speak?.("playing tape"); 156 159 } else { 157 160 msg("failed to play " + t.name); ··· 162 165 // URL loading, otherwise instruct the user. 163 166 const ok = sound?.deck?.load?.(0, t.mp4Url); 164 167 if (ok) { 168 + const videoOk = sound?.deck?.prepareVideo?.(0, 120, 68, 12); 165 169 sound.deck.play(0); 166 - msg("streaming " + t.slug); 170 + nowPlaying = t.slug + ".mp4"; 171 + msg((videoOk ? "streaming " : "streaming audio-only ") + t.slug); 167 172 sound?.speak?.("streaming cloud tape"); 168 173 } else { 169 174 msg("cloud streaming not supported yet — see " + t.mp4Url); ··· 216 221 } 217 222 } 218 223 219 - function paint({ wipe, ink, box, line, write, screen }) { 224 + function paint({ wipe, ink, box, line, write, screen, sound }) { 220 225 const w = screen.width; 221 226 const h = screen.height; 222 227 wipe(16, 14, 20); 228 + const deck0 = sound?.deck?.decks?.[0] || null; 229 + const showPreview = !!deck0?.loaded; 230 + const previewW = showPreview ? Math.min(132, Math.max(92, Math.floor(w * 0.36))) : 0; 231 + const listRight = showPreview ? (w - previewW - 12) : (w - 8); 223 232 224 233 // Title 225 234 ink(200, 220, 255); ··· 235 244 const scrollStart = Math.max(0, selection - Math.floor(visibleRows / 2)); 236 245 const scrollEnd = Math.min(tapes.length, scrollStart + visibleRows); 237 246 247 + if (showPreview) { 248 + const px = listRight + 4; 249 + const py = 40; 250 + const ph = Math.min(h - 60, Math.floor(previewW * 0.62)); 251 + ink(36, 44, 58, 220); 252 + box(px - 2, py - 2, previewW + 4, ph + 18, true); 253 + if (deck0?.videoReady) { 254 + sound?.deck?.videoBlit?.(0, px, py, previewW, ph); 255 + } else { 256 + ink(120, 140, 170); 257 + write("video buffering...", { x: px + 8, y: py + Math.floor(ph / 2), size: 1, font: "font_1" }); 258 + } 259 + ink(220, 230, 240); 260 + write("now playing", { x: px, y: py + ph + 4, size: 1, font: "font_1" }); 261 + ink(150, 180, 210); 262 + write((nowPlaying || deck0?.title || "deck 0").replace(/\.mp4$/, ""), { x: px, y: py + ph + 14, size: 1, font: "font_1" }); 263 + if (deck0?.duration > 0) { 264 + const prog = Math.max(0, Math.min(1, (deck0?.position || 0) / deck0.duration)); 265 + ink(50, 60, 72); 266 + box(px, py + ph + 22, previewW, 5, true); 267 + ink(120, 220, 180); 268 + box(px, py + ph + 22, Math.max(1, Math.floor(previewW * prog)), 5, true); 269 + } 270 + } 271 + 238 272 if (tapes.length === 0) { 239 273 ink(180, 180, 200); 240 274 write("No tapes recorded yet.", { x: 20, y: listY + 8, size: 1, font: "font_1" }); ··· 247 281 const y = listY + (i - scrollStart) * rowH; 248 282 if (i === selection) { 249 283 ink(40, 60, 100, 200); 250 - box(4, y - 2, w - 8, rowH, true); 284 + box(4, y - 2, listRight - 4, rowH, true); 251 285 } 252 286 // Source badge (☁ cloud / 📁 local — shown as small ascii tag) 253 287 const badge = t.source === "cloud" ? "C" : "L"; ··· 257 291 // Name 258 292 if (i === selection) ink(255, 255, 255); 259 293 else ink(200, 220, 240); 260 - write(t.name.replace(/\.mp4$/, ""), { x: 24, y: y + 2, size: 1, font: "font_1" }); 294 + let label = t.name.replace(/\.mp4$/, ""); 295 + if (showPreview && label.length > 20) label = label.slice(0, 20) + "..."; 296 + write(label, { x: 24, y: y + 2, size: 1, font: "font_1" }); 261 297 // Right meta: local shows size, cloud shows code 262 298 ink(140, 160, 190); 263 299 const meta = t.source === "local" ? fmtBytes(t.size) : ("!" + (t.code || "?")); 264 - const metaX = w - meta.length * 6 - 10; 300 + const metaX = listRight - meta.length * 6 - 6; 265 301 write(meta, { x: metaX, y: y + 2, size: 1, font: "font_1" }); 266 302 } 267 303 }
+204
fedac/native/src/audio-decode.c
··· 13 13 #include <libavcodec/avcodec.h> 14 14 #include <libavformat/avformat.h> 15 15 #include <libavutil/opt.h> 16 + #include <libavutil/imgutils.h> 16 17 #include <libavutil/channel_layout.h> 18 + #include <libswscale/swscale.h> 17 19 #include <libswresample/swresample.h> 18 20 19 21 extern void ac_log(const char *fmt, ...); ··· 36 38 return d->ring_size - ring_available(d); 37 39 } 38 40 41 + static void free_video_preview(ACDeckDecoder *d) { 42 + if (!d) return; 43 + if (d->video_frames) { 44 + free(d->video_frames); 45 + d->video_frames = NULL; 46 + } 47 + d->video_frame_count = 0; 48 + d->video_width = 0; 49 + d->video_height = 0; 50 + d->video_fps = 0.0; 51 + d->video_ready = 0; 52 + } 53 + 39 54 // --- Decoder thread --- 40 55 41 56 static void *decoder_thread_fn(void *arg) { ··· 217 232 d->seek_requested = 0; 218 233 d->speed = 1.0; 219 234 snprintf(d->path, sizeof(d->path), "%s", path); 235 + free_video_preview(d); 220 236 221 237 // Open file 222 238 AVFormatContext *fmt_ctx = NULL; ··· 402 418 d->peaks = NULL; 403 419 d->peak_count = 0; 404 420 } 421 + free_video_preview(d); 405 422 406 423 d->loaded = 0; 407 424 d->finished = 0; ··· 513 530 avformat_close_input(&fmt); 514 531 515 532 return target_count; 533 + } 534 + 535 + int deck_decoder_generate_video_preview(ACDeckDecoder *d, int width, int height, int fps) { 536 + if (!d || !d->loaded || !d->path[0]) return -1; 537 + if (width <= 0) width = 96; 538 + if (height <= 0) height = 54; 539 + if (fps <= 0) fps = 12; 540 + if (fps > 24) fps = 24; 541 + 542 + // Reuse cached preview when the request matches the existing buffer. 543 + if (d->video_ready && 544 + d->video_frames && 545 + d->video_width == width && 546 + d->video_height == height && 547 + fabs(d->video_fps - (double)fps) < 0.001) { 548 + return d->video_frame_count; 549 + } 550 + 551 + free_video_preview(d); 552 + 553 + AVFormatContext *fmt = NULL; 554 + if (avformat_open_input(&fmt, d->path, NULL, NULL) < 0) return -1; 555 + if (avformat_find_stream_info(fmt, NULL) < 0) { 556 + avformat_close_input(&fmt); 557 + return -1; 558 + } 559 + 560 + int sidx = av_find_best_stream(fmt, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); 561 + if (sidx < 0) { 562 + avformat_close_input(&fmt); 563 + return -1; 564 + } 565 + 566 + AVStream *st = fmt->streams[sidx]; 567 + const AVCodec *codec = avcodec_find_decoder(st->codecpar->codec_id); 568 + if (!codec) { 569 + avformat_close_input(&fmt); 570 + return -1; 571 + } 572 + 573 + AVCodecContext *cctx = avcodec_alloc_context3(codec); 574 + if (!cctx) { 575 + avformat_close_input(&fmt); 576 + return -1; 577 + } 578 + avcodec_parameters_to_context(cctx, st->codecpar); 579 + if (avcodec_open2(cctx, codec, NULL) < 0) { 580 + avcodec_free_context(&cctx); 581 + avformat_close_input(&fmt); 582 + return -1; 583 + } 584 + 585 + double duration = d->duration; 586 + if (duration <= 0.0 && fmt->duration > 0) 587 + duration = (double)fmt->duration / AV_TIME_BASE; 588 + if (duration <= 0.0 && st->duration > 0) 589 + duration = st->duration * av_q2d(st->time_base); 590 + if (duration <= 0.0) 591 + duration = 30.0; 592 + 593 + int target_count = (int)ceil(duration * (double)fps) + 1; 594 + if (target_count < 1) target_count = 1; 595 + if (target_count > 900) target_count = 900; 596 + 597 + size_t pixels_per_frame = (size_t)width * (size_t)height; 598 + size_t total_pixels = pixels_per_frame * (size_t)target_count; 599 + uint32_t *frames = (uint32_t *)calloc(total_pixels, sizeof(uint32_t)); 600 + if (!frames) { 601 + avcodec_free_context(&cctx); 602 + avformat_close_input(&fmt); 603 + return -1; 604 + } 605 + 606 + struct SwsContext *sws = sws_getContext( 607 + cctx->width, cctx->height, cctx->pix_fmt, 608 + width, height, AV_PIX_FMT_BGRA, 609 + SWS_BILINEAR, NULL, NULL, NULL); 610 + if (!sws) { 611 + free(frames); 612 + avcodec_free_context(&cctx); 613 + avformat_close_input(&fmt); 614 + return -1; 615 + } 616 + 617 + AVPacket *pkt = av_packet_alloc(); 618 + AVFrame *frame = av_frame_alloc(); 619 + if (!pkt || !frame) { 620 + av_packet_free(&pkt); 621 + av_frame_free(&frame); 622 + sws_freeContext(sws); 623 + free(frames); 624 + avcodec_free_context(&cctx); 625 + avformat_close_input(&fmt); 626 + return -1; 627 + } 628 + 629 + uint8_t *last_frame = NULL; 630 + int frame_idx = 0; 631 + double next_time = 0.0; 632 + 633 + while (av_read_frame(fmt, pkt) >= 0 && frame_idx < target_count) { 634 + if (pkt->stream_index != sidx) { 635 + av_packet_unref(pkt); 636 + continue; 637 + } 638 + if (avcodec_send_packet(cctx, pkt) < 0) { 639 + av_packet_unref(pkt); 640 + continue; 641 + } 642 + av_packet_unref(pkt); 643 + 644 + while (avcodec_receive_frame(cctx, frame) >= 0 && frame_idx < target_count) { 645 + double frame_time = 0.0; 646 + if (frame->best_effort_timestamp != AV_NOPTS_VALUE) 647 + frame_time = frame->best_effort_timestamp * av_q2d(st->time_base); 648 + else if (frame->pts != AV_NOPTS_VALUE) 649 + frame_time = frame->pts * av_q2d(st->time_base); 650 + 651 + uint8_t *dst_data[4] = {0}; 652 + int dst_linesize[4] = {0}; 653 + uint32_t *dst = frames + ((size_t)frame_idx * pixels_per_frame); 654 + av_image_fill_arrays(dst_data, dst_linesize, (uint8_t *)dst, 655 + AV_PIX_FMT_BGRA, width, height, 1); 656 + 657 + // Fill every preview sample point that this decoded frame covers. 658 + while (frame_idx < target_count && 659 + (frame_time >= next_time || frame_idx == 0)) { 660 + dst = frames + ((size_t)frame_idx * pixels_per_frame); 661 + av_image_fill_arrays(dst_data, dst_linesize, (uint8_t *)dst, 662 + AV_PIX_FMT_BGRA, width, height, 1); 663 + sws_scale(sws, (const uint8_t * const *)frame->data, frame->linesize, 664 + 0, cctx->height, dst_data, dst_linesize); 665 + last_frame = (uint8_t *)dst; 666 + frame_idx++; 667 + next_time = (double)frame_idx / (double)fps; 668 + } 669 + 670 + av_frame_unref(frame); 671 + } 672 + } 673 + 674 + // Flush decoder for the tail of the file. 675 + avcodec_send_packet(cctx, NULL); 676 + while (avcodec_receive_frame(cctx, frame) >= 0 && frame_idx < target_count) { 677 + uint8_t *dst_data[4] = {0}; 678 + int dst_linesize[4] = {0}; 679 + uint32_t *dst = frames + ((size_t)frame_idx * pixels_per_frame); 680 + av_image_fill_arrays(dst_data, dst_linesize, (uint8_t *)dst, 681 + AV_PIX_FMT_BGRA, width, height, 1); 682 + sws_scale(sws, (const uint8_t * const *)frame->data, frame->linesize, 683 + 0, cctx->height, dst_data, dst_linesize); 684 + last_frame = (uint8_t *)dst; 685 + frame_idx++; 686 + av_frame_unref(frame); 687 + } 688 + 689 + // Pad remaining preview slots with the final decoded frame so playback 690 + // keeps showing a still image once audio reaches the tail. 691 + if (last_frame) { 692 + while (frame_idx < target_count) { 693 + uint32_t *dst = frames + ((size_t)frame_idx * pixels_per_frame); 694 + memcpy(dst, last_frame, pixels_per_frame * sizeof(uint32_t)); 695 + frame_idx++; 696 + } 697 + } 698 + 699 + av_packet_free(&pkt); 700 + av_frame_free(&frame); 701 + sws_freeContext(sws); 702 + avcodec_free_context(&cctx); 703 + avformat_close_input(&fmt); 704 + 705 + if (frame_idx <= 0) { 706 + free(frames); 707 + return -1; 708 + } 709 + 710 + d->video_frames = frames; 711 + d->video_frame_count = frame_idx; 712 + d->video_width = width; 713 + d->video_height = height; 714 + d->video_fps = (double)fps; 715 + d->video_ready = 1; 716 + 717 + ac_log("[deck] video preview ready: %s (%d frames @ %dx%d %.1ffps)\n", 718 + d->path, d->video_frame_count, d->video_width, d->video_height, d->video_fps); 719 + return d->video_frame_count; 516 720 } 517 721 518 722 void deck_decoder_destroy(ACDeckDecoder *d) {
+23
fedac/native/src/audio-decode.h
··· 61 61 // Generated once on load by scanning the entire file via separate pass. 62 62 float *peaks; // [0..1] amplitude peaks 63 63 int peak_count; // number of peaks (typically 1024) 64 + 65 + // Optional downscaled video preview frames for pieces like tapes.mjs. 66 + // Frames are stored as opaque ARGB32 pixels (0xAARRGGBB) in a flat array: 67 + // [frame0 pixels][frame1 pixels]... 68 + uint32_t *video_frames; 69 + int video_frame_count; 70 + int video_width; 71 + int video_height; 72 + double video_fps; 73 + volatile int video_ready; 64 74 } ACDeckDecoder; 65 75 66 76 // Create a decoder instance for the given output sample rate ··· 83 93 // Safe to call from main thread; takes a few hundred ms for typical tracks. 84 94 int deck_decoder_generate_peaks(ACDeckDecoder *d, int target_count); 85 95 96 + // Generate a lightweight downscaled video preview strip for the loaded file. 97 + // Safe to call from main thread; intended for UI playback rather than full-res video. 98 + int deck_decoder_generate_video_preview(ACDeckDecoder *d, int width, int height, int fps); 99 + 86 100 // Destroy decoder and free all resources 87 101 void deck_decoder_destroy(ACDeckDecoder *d); 88 102 ··· 105 119 int ring_size; 106 120 volatile int ring_write; 107 121 volatile int ring_read; 122 + uint32_t *video_frames; 123 + int video_frame_count; 124 + int video_width; 125 + int video_height; 126 + double video_fps; 127 + volatile int video_ready; 108 128 } ACDeckDecoder; 109 129 110 130 static inline ACDeckDecoder *deck_decoder_create(unsigned int output_rate) { ··· 119 139 static inline void deck_decoder_set_speed(ACDeckDecoder *d, double s) { (void)d; (void)s; } 120 140 static inline void deck_decoder_unload(ACDeckDecoder *d) { (void)d; } 121 141 static inline void deck_decoder_destroy(ACDeckDecoder *d) { (void)d; } 142 + static inline int deck_decoder_generate_video_preview(ACDeckDecoder *d, int width, int height, int fps) { 143 + (void)d; (void)width; (void)height; (void)fps; return -1; 144 + } 122 145 123 146 #endif // HAVE_AVCODEC 124 147 #endif // AC_AUDIO_DECODE_H
+93
fedac/native/src/js-bindings.c
··· 2299 2299 2300 2300 // --- DJ deck bindings --- 2301 2301 2302 + static void blit_argb_nearest(ACFramebuffer *fb, const uint32_t *src, 2303 + int sw, int sh, int dx, int dy, int dw, int dh) { 2304 + if (!fb || !src || sw <= 0 || sh <= 0 || dw <= 0 || dh <= 0) return; 2305 + 2306 + int x0 = dx < 0 ? 0 : dx; 2307 + int y0 = dy < 0 ? 0 : dy; 2308 + int x1 = dx + dw; 2309 + int y1 = dy + dh; 2310 + if (x1 > fb->width) x1 = fb->width; 2311 + if (y1 > fb->height) y1 = fb->height; 2312 + if (x0 >= x1 || y0 >= y1) return; 2313 + 2314 + for (int y = y0; y < y1; y++) { 2315 + int sy = (int)(((int64_t)(y - dy) * sh) / dh); 2316 + if (sy < 0) sy = 0; 2317 + if (sy >= sh) sy = sh - 1; 2318 + const uint32_t *src_row = src + sy * sw; 2319 + uint32_t *dst_row = fb->pixels + y * fb->stride; 2320 + for (int x = x0; x < x1; x++) { 2321 + int sx = (int)(((int64_t)(x - dx) * sw) / dw); 2322 + if (sx < 0) sx = 0; 2323 + if (sx >= sw) sx = sw - 1; 2324 + uint32_t px = src_row[sx]; 2325 + if ((px >> 24) == 0) continue; 2326 + dst_row[x] = px; 2327 + } 2328 + } 2329 + } 2330 + 2302 2331 // sound.deck.load(deck, path) -> true/false 2303 2332 static JSValue js_deck_load(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 2304 2333 (void)this_val; ··· 2367 2396 return arr; 2368 2397 } 2369 2398 2399 + // sound.deck.prepareVideo(deck[, width, height, fps]) — decode a low-res preview strip 2400 + static JSValue js_deck_prepare_video(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 2401 + (void)this_val; 2402 + if (argc < 1 || !current_rt || !current_rt->audio) return JS_FALSE; 2403 + int deck = 0, width = 96, height = 54, fps = 12; 2404 + JS_ToInt32(ctx, &deck, argv[0]); 2405 + if (argc > 1) JS_ToInt32(ctx, &width, argv[1]); 2406 + if (argc > 2) JS_ToInt32(ctx, &height, argv[2]); 2407 + if (argc > 3) JS_ToInt32(ctx, &fps, argv[3]); 2408 + if (deck < 0 || deck >= AUDIO_MAX_DECKS) return JS_FALSE; 2409 + ACDeck *dk = &current_rt->audio->decks[deck]; 2410 + if (!dk->decoder) return JS_FALSE; 2411 + int ret = deck_decoder_generate_video_preview(dk->decoder, width, height, fps); 2412 + return JS_NewBool(ctx, ret > 0); 2413 + } 2414 + 2415 + // sound.deck.videoBlit(deck, x, y, w, h) — draw current preview frame into the framebuffer 2416 + static JSValue js_deck_video_blit(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 2417 + (void)this_val; 2418 + if (argc < 5 || !current_rt || !current_rt->audio || !current_rt->graph || !current_rt->graph->fb) 2419 + return JS_FALSE; 2420 + 2421 + int deck = 0, x = 0, y = 0, w = 0, h = 0; 2422 + JS_ToInt32(ctx, &deck, argv[0]); 2423 + JS_ToInt32(ctx, &x, argv[1]); 2424 + JS_ToInt32(ctx, &y, argv[2]); 2425 + JS_ToInt32(ctx, &w, argv[3]); 2426 + JS_ToInt32(ctx, &h, argv[4]); 2427 + if (deck < 0 || deck >= AUDIO_MAX_DECKS) return JS_FALSE; 2428 + 2429 + ACDeck *dk = &current_rt->audio->decks[deck]; 2430 + if (!dk->decoder || !dk->decoder->video_ready || !dk->decoder->video_frames || 2431 + dk->decoder->video_frame_count <= 0 || dk->decoder->video_width <= 0 || 2432 + dk->decoder->video_height <= 0 || dk->decoder->video_fps <= 0.0) { 2433 + return JS_FALSE; 2434 + } 2435 + 2436 + if (w <= 0) w = dk->decoder->video_width; 2437 + if (h <= 0) h = dk->decoder->video_height; 2438 + 2439 + int idx = (int)floor(dk->decoder->position * dk->decoder->video_fps + 0.0001); 2440 + if (idx < 0) idx = 0; 2441 + if (idx >= dk->decoder->video_frame_count) idx = dk->decoder->video_frame_count - 1; 2442 + 2443 + size_t pixels_per_frame = (size_t)dk->decoder->video_width * (size_t)dk->decoder->video_height; 2444 + const uint32_t *src = dk->decoder->video_frames + ((size_t)idx * pixels_per_frame); 2445 + blit_argb_nearest(current_rt->graph->fb, src, 2446 + dk->decoder->video_width, dk->decoder->video_height, 2447 + x, y, w, h); 2448 + return JS_TRUE; 2449 + } 2450 + 2370 2451 // sound.deck.setVolume(deck, vol) 2371 2452 static JSValue js_deck_set_volume(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { 2372 2453 (void)this_val; ··· 2541 2622 JS_SetPropertyStr(ctx, deck_obj, "setCrossfader", JS_NewCFunction(ctx, js_deck_set_crossfader, "setCrossfader", 1)); 2542 2623 JS_SetPropertyStr(ctx, deck_obj, "setMasterVolume", JS_NewCFunction(ctx, js_deck_set_master_vol, "setMasterVolume", 1)); 2543 2624 JS_SetPropertyStr(ctx, deck_obj, "getPeaks", JS_NewCFunction(ctx, js_deck_get_peaks, "getPeaks", 1)); 2625 + JS_SetPropertyStr(ctx, deck_obj, "prepareVideo", JS_NewCFunction(ctx, js_deck_prepare_video, "prepareVideo", 4)); 2626 + JS_SetPropertyStr(ctx, deck_obj, "videoBlit", JS_NewCFunction(ctx, js_deck_video_blit, "videoBlit", 5)); 2544 2627 2545 2628 // Deck state (read-only, rebuilt each frame) 2546 2629 JSValue decks_arr = JS_NewArray(ctx); ··· 2560 2643 JS_SetPropertyStr(ctx, di, "artist", JS_NewString(ctx, dk->decoder->artist)); 2561 2644 JS_SetPropertyStr(ctx, di, "finished", JS_NewBool(ctx, dk->decoder->finished)); 2562 2645 JS_SetPropertyStr(ctx, di, "error", JS_NewString(ctx, dk->decoder->error ? dk->decoder->error_msg : "")); 2646 + JS_SetPropertyStr(ctx, di, "videoReady", JS_NewBool(ctx, dk->decoder->video_ready)); 2647 + JS_SetPropertyStr(ctx, di, "videoWidth", JS_NewInt32(ctx, dk->decoder->video_width)); 2648 + JS_SetPropertyStr(ctx, di, "videoHeight", JS_NewInt32(ctx, dk->decoder->video_height)); 2649 + JS_SetPropertyStr(ctx, di, "videoFps", JS_NewFloat64(ctx, dk->decoder->video_fps)); 2650 + JS_SetPropertyStr(ctx, di, "videoFrames", JS_NewInt32(ctx, dk->decoder->video_frame_count)); 2563 2651 } else { 2564 2652 JS_SetPropertyStr(ctx, di, "position", JS_NewFloat64(ctx, 0)); 2565 2653 JS_SetPropertyStr(ctx, di, "duration", JS_NewFloat64(ctx, 0)); ··· 2568 2656 JS_SetPropertyStr(ctx, di, "artist", JS_NewString(ctx, "")); 2569 2657 JS_SetPropertyStr(ctx, di, "finished", JS_FALSE); 2570 2658 JS_SetPropertyStr(ctx, di, "error", JS_NewString(ctx, "")); 2659 + JS_SetPropertyStr(ctx, di, "videoReady", JS_FALSE); 2660 + JS_SetPropertyStr(ctx, di, "videoWidth", JS_NewInt32(ctx, 0)); 2661 + JS_SetPropertyStr(ctx, di, "videoHeight", JS_NewInt32(ctx, 0)); 2662 + JS_SetPropertyStr(ctx, di, "videoFps", JS_NewFloat64(ctx, 0)); 2663 + JS_SetPropertyStr(ctx, di, "videoFrames", JS_NewInt32(ctx, 0)); 2571 2664 } 2572 2665 } 2573 2666 JS_SetPropertyUint32(ctx, decks_arr, d, di);