@next2d/builder 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +315 -3
  3. package/package.json +10 -8
  4. package/templates/xbox/CMakeLists.txt +124 -0
  5. package/templates/xbox/MicrosoftGame.config +36 -0
  6. package/templates/xbox/README.md +372 -0
  7. package/templates/xbox/cmake/FetchDawn.cmake +42 -0
  8. package/templates/xbox/cmake/FindV8.cmake +68 -0
  9. package/templates/xbox/js/bootstrap.js +143 -0
  10. package/templates/xbox/js/selftest.js +515 -0
  11. package/templates/xbox/src/AssetLoader.cpp +106 -0
  12. package/templates/xbox/src/AssetLoader.h +35 -0
  13. package/templates/xbox/src/EventLoop.cpp +162 -0
  14. package/templates/xbox/src/EventLoop.h +73 -0
  15. package/templates/xbox/src/HostContext.h +58 -0
  16. package/templates/xbox/src/bindings/Audio.cpp +320 -0
  17. package/templates/xbox/src/bindings/Bindings.h +75 -0
  18. package/templates/xbox/src/bindings/Canvas.cpp +171 -0
  19. package/templates/xbox/src/bindings/Canvas2D.cpp +462 -0
  20. package/templates/xbox/src/bindings/Console.cpp +52 -0
  21. package/templates/xbox/src/bindings/DomShims.cpp +221 -0
  22. package/templates/xbox/src/bindings/EventTarget.cpp +127 -0
  23. package/templates/xbox/src/bindings/EventTarget.h +17 -0
  24. package/templates/xbox/src/bindings/Fetch.cpp +149 -0
  25. package/templates/xbox/src/bindings/Gamepad.cpp +79 -0
  26. package/templates/xbox/src/bindings/Image.cpp +160 -0
  27. package/templates/xbox/src/bindings/ImageSource.h +26 -0
  28. package/templates/xbox/src/bindings/Network.cpp +303 -0
  29. package/templates/xbox/src/bindings/RasterCore.h +265 -0
  30. package/templates/xbox/src/bindings/Timers.cpp +87 -0
  31. package/templates/xbox/src/bindings/Video.cpp +235 -0
  32. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +1318 -0
  33. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +119 -0
  34. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +245 -0
  35. package/templates/xbox/src/gpu/DawnContext.cpp +170 -0
  36. package/templates/xbox/src/gpu/DawnContext.h +64 -0
  37. package/templates/xbox/src/main.cpp +368 -0
  38. package/templates/xbox/src/platform/AudioEngine.cpp +184 -0
  39. package/templates/xbox/src/platform/AudioEngine.h +65 -0
  40. package/templates/xbox/src/platform/GamepadManager.cpp +83 -0
  41. package/templates/xbox/src/platform/GamepadManager.h +40 -0
  42. package/templates/xbox/src/platform/ImageTypes.h +17 -0
  43. package/templates/xbox/src/platform/TextRasterizer.cpp +183 -0
  44. package/templates/xbox/src/platform/TextRasterizer.h +35 -0
  45. package/templates/xbox/src/platform/WicDecoder.cpp +63 -0
  46. package/templates/xbox/src/platform/WicDecoder.h +16 -0
  47. package/templates/xbox/src/v8/V8Runtime.cpp +285 -0
  48. package/templates/xbox/src/v8/V8Runtime.h +68 -0
  49. package/templates/xbox/src/v8/V8Util.h +56 -0
  50. package/templates/xbox/src/worker/WorkerRuntime.cpp +453 -0
  51. package/templates/xbox/src/worker/WorkerRuntime.h +108 -0
  52. package/templates/xbox/tests/check_local.sh +56 -0
  53. package/templates/xbox/tests/raster_test.cpp +279 -0
  54. package/templates/xbox/tests/windows_platform_test.cpp +187 -0
@@ -0,0 +1,265 @@
1
+ // Canvas2D のソフトウェアラスタライザ純ロジック。
2
+ // V8 / Windows API に依存しないため、macOS/Linux でも単体テストできる
3
+ // (tests/raster_test.cpp)。Canvas2D.cpp はこのヘッダの薄いバインディング層。
4
+ #pragma once
5
+
6
+ #include <algorithm>
7
+ #include <cctype>
8
+ #include <cmath>
9
+ #include <cstdint>
10
+ #include <string>
11
+ #include <utility>
12
+ #include <vector>
13
+
14
+ namespace next2d::raster {
15
+
16
+ inline constexpr double kPi = 3.14159265358979323846;
17
+
18
+ struct Mat { double a=1,b=0,c=0,d=1,e=0,f=0; };
19
+
20
+ struct Point { double x, y; };
21
+
22
+ struct SubPath {
23
+ std::vector<Point> pts;
24
+ bool closed = false;
25
+ };
26
+
27
+ struct RGBA { uint8_t r=0,g=0,b=0,a=255; };
28
+
29
+ // クリップ矩形 (デバイス座標, [x0,x1) x [y0,y1))。active=false なら無制限。
30
+ struct ClipRect {
31
+ bool active = false;
32
+ double x0 = 0, y0 = 0, x1 = 0, y1 = 0;
33
+ };
34
+
35
+ // RGBA8 のピクセルバッファ。
36
+ struct Surface {
37
+ int width = 0;
38
+ int height = 0;
39
+ std::vector<uint8_t> pixels;
40
+
41
+ Surface(int w, int h)
42
+ : width(w), height(h), pixels(static_cast<size_t>(w) * h * 4, 0) {}
43
+ };
44
+
45
+ inline Point Apply(const Mat& m, double x, double y)
46
+ {
47
+ return { m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f };
48
+ }
49
+
50
+ inline Mat Multiply(const Mat& m, const Mat& n)
51
+ {
52
+ return {
53
+ m.a * n.a + m.c * n.b,
54
+ m.b * n.a + m.d * n.b,
55
+ m.a * n.c + m.c * n.d,
56
+ m.b * n.c + m.d * n.d,
57
+ m.a * n.e + m.c * n.f + m.e,
58
+ m.b * n.e + m.d * n.f + m.f,
59
+ };
60
+ }
61
+
62
+ // "bold 24px sans-serif" 等から px を抽出 (近似メトリクス用)
63
+ inline double FontPixelSize(const std::string& font)
64
+ {
65
+ const auto pos = font.find("px");
66
+ if (pos == std::string::npos) return 10.0;
67
+ size_t start = pos;
68
+ while (start > 0 && (isdigit(static_cast<unsigned char>(font[start - 1])) || font[start - 1] == '.')) --start;
69
+ try { return std::stod(font.substr(start, pos - start)); } catch (...) { return 10.0; }
70
+ }
71
+
72
+ // #rgb / #rrggbb / rgb(...) / rgba(...) を解析する。
73
+ inline RGBA ParseColor(const std::string& s)
74
+ {
75
+ RGBA c;
76
+ if (s.empty()) return c;
77
+ if (s[0] == '#') {
78
+ auto hex = [&](int i, int n) {
79
+ return static_cast<uint8_t>(std::stoi(s.substr(i, n), nullptr, 16) * (n == 1 ? 17 : 1));
80
+ };
81
+ if (s.size() == 4) { c.r = hex(1,1); c.g = hex(2,1); c.b = hex(3,1); }
82
+ else if (s.size() >= 7) { c.r = hex(1,2); c.g = hex(3,2); c.b = hex(5,2); }
83
+ return c;
84
+ }
85
+ if (s.rfind("rgb", 0) == 0) {
86
+ double vals[4] = {0,0,0,1};
87
+ int n = 0; size_t i = s.find('(');
88
+ while (i != std::string::npos && n < 4) {
89
+ i++;
90
+ try { vals[n++] = std::stod(s.substr(i)); } catch (...) { break; }
91
+ i = s.find(',', i);
92
+ }
93
+ c.r = static_cast<uint8_t>(vals[0]); c.g = static_cast<uint8_t>(vals[1]);
94
+ c.b = static_cast<uint8_t>(vals[2]); c.a = static_cast<uint8_t>(vals[3] * 255);
95
+ }
96
+ return c;
97
+ }
98
+
99
+ // 1 ピクセルをアルファ合成する。クリップ矩形の外は書き込まない。
100
+ inline void Blend(Surface& s, const ClipRect& clip, int x, int y, const RGBA& col, double alpha)
101
+ {
102
+ if (x < 0 || y < 0 || x >= s.width || y >= s.height) return;
103
+ if (clip.active && (x < clip.x0 || x >= clip.x1 || y < clip.y0 || y >= clip.y1)) return;
104
+ size_t i = (static_cast<size_t>(y) * s.width + x) * 4;
105
+ double a = (col.a / 255.0) * alpha;
106
+ s.pixels[i+0] = static_cast<uint8_t>(col.r * a + s.pixels[i+0] * (1 - a));
107
+ s.pixels[i+1] = static_cast<uint8_t>(col.g * a + s.pixels[i+1] * (1 - a));
108
+ s.pixels[i+2] = static_cast<uint8_t>(col.b * a + s.pixels[i+2] * (1 - a));
109
+ s.pixels[i+3] = static_cast<uint8_t>(255 * a + s.pixels[i+3] * (1 - a));
110
+ }
111
+
112
+ // スキャンライン塗り (nonzero winding)。
113
+ // HTML Canvas 仕様どおり、未クローズのサブパスも塗りでは暗黙にクローズする
114
+ // (isPointInPath と同じ扱い)。
115
+ inline void FillPath(Surface& s, const ClipRect& clip,
116
+ const std::vector<SubPath>& path, const RGBA& col, double alpha)
117
+ {
118
+ double miny = 1e18, maxy = -1e18;
119
+ for (auto& sp : path) for (auto& p : sp.pts) { miny = std::min(miny,p.y); maxy = std::max(maxy,p.y); }
120
+ if (maxy < miny) return;
121
+ int y0 = std::max(0, static_cast<int>(std::floor(miny)));
122
+ int y1 = std::min(s.height - 1, static_cast<int>(std::ceil(maxy)));
123
+ for (int y = y0; y <= y1; ++y) {
124
+ double sy = y + 0.5;
125
+ std::vector<std::pair<double,int>> xs; // x, winding dir
126
+ for (auto& sp : path) {
127
+ size_t n = sp.pts.size();
128
+ if (n < 2) continue;
129
+ for (size_t i = 0; i < n; ++i) {
130
+ Point a = sp.pts[i];
131
+ Point b = sp.pts[(i+1) % n];
132
+ if ((a.y <= sy && b.y > sy) || (b.y <= sy && a.y > sy)) {
133
+ double t = (sy - a.y) / (b.y - a.y);
134
+ xs.push_back({a.x + t * (b.x - a.x), a.y < b.y ? 1 : -1});
135
+ }
136
+ }
137
+ }
138
+ std::sort(xs.begin(), xs.end());
139
+ int wind = 0;
140
+ for (size_t i = 0; i + 1 < xs.size(); ++i) {
141
+ wind += xs[i].second;
142
+ if (wind != 0) {
143
+ int xa = std::max(0, static_cast<int>(std::floor(xs[i].first)));
144
+ int xb = std::min(s.width - 1, static_cast<int>(std::ceil(xs[i+1].first)));
145
+ for (int x = xa; x <= xb; ++x) Blend(s, clip, x, y, col, alpha);
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // 単純な線分描画 (太さは近似)。stroke は未クローズのサブパスを閉じない。
152
+ inline void StrokePath(Surface& s, const ClipRect& clip,
153
+ const std::vector<SubPath>& path, const RGBA& col, double alpha)
154
+ {
155
+ for (auto& sp : path) {
156
+ size_t n = sp.pts.size();
157
+ size_t last = sp.closed ? n : (n > 0 ? n - 1 : 0);
158
+ for (size_t i = 0; i < last; ++i) {
159
+ Point a0 = sp.pts[i], b0 = sp.pts[(i+1) % n];
160
+ int steps = static_cast<int>(std::hypot(b0.x - a0.x, b0.y - a0.y)) + 1;
161
+ for (int st = 0; st <= steps; ++st) {
162
+ double t = static_cast<double>(st) / steps;
163
+ Blend(s, clip, static_cast<int>(a0.x + (b0.x-a0.x)*t),
164
+ static_cast<int>(a0.y + (b0.y-a0.y)*t), col, alpha);
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ // nonzero winding の点内包判定。ヒット判定の中核。
171
+ inline bool PointInPath(const std::vector<SubPath>& path, double px, double py)
172
+ {
173
+ int wind = 0;
174
+ for (auto& sp : path) {
175
+ size_t n = sp.pts.size();
176
+ if (n < 2) continue;
177
+ for (size_t i = 0; i < n; ++i) {
178
+ Point A = sp.pts[i];
179
+ Point B = sp.pts[(i+1) % n];
180
+ if ((A.y <= py && B.y > py) || (B.y <= py && A.y > py)) {
181
+ double t = (py - A.y) / (B.y - A.y);
182
+ double xcross = A.x + t * (B.x - A.x);
183
+ if (xcross > px) wind += (A.y < B.y) ? 1 : -1;
184
+ }
185
+ }
186
+ }
187
+ return wind != 0;
188
+ }
189
+
190
+ // パス全点の外接矩形。点が無ければ false。
191
+ inline bool PathBounds(const std::vector<SubPath>& path,
192
+ double* minx, double* miny, double* maxx, double* maxy)
193
+ {
194
+ bool any = false;
195
+ *minx = 1e18; *miny = 1e18; *maxx = -1e18; *maxy = -1e18;
196
+ for (auto& sp : path) for (auto& p : sp.pts) {
197
+ *minx = std::min(*minx, p.x); *maxx = std::max(*maxx, p.x);
198
+ *miny = std::min(*miny, p.y); *maxy = std::max(*maxy, p.y);
199
+ any = true;
200
+ }
201
+ return any;
202
+ }
203
+
204
+ // クリップ矩形へ [nx0,nx1)x[ny0,ny1) を積集合で反映する (clip() の実体)。
205
+ inline void IntersectClip(ClipRect& clip, double nx0, double ny0, double nx1, double ny1)
206
+ {
207
+ if (clip.active) {
208
+ clip.x0 = std::max(clip.x0, nx0);
209
+ clip.y0 = std::max(clip.y0, ny0);
210
+ clip.x1 = std::min(clip.x1, nx1);
211
+ clip.y1 = std::min(clip.y1, ny1);
212
+ } else {
213
+ clip.active = true;
214
+ clip.x0 = nx0; clip.y0 = ny0; clip.x1 = nx1; clip.y1 = ny1;
215
+ }
216
+ }
217
+
218
+ // 3次ベジェを現在サブパスへフラット化して追記する。p0 は変換済みの直前点。
219
+ inline void FlattenCubic(std::vector<Point>& out,
220
+ Point p0, Point p1, Point p2, Point p3, int steps = 16)
221
+ {
222
+ for (int i = 1; i <= steps; ++i) {
223
+ double t = static_cast<double>(i) / steps, u = 1 - t;
224
+ out.push_back({
225
+ u*u*u*p0.x + 3*u*u*t*p1.x + 3*u*t*t*p2.x + t*t*t*p3.x,
226
+ u*u*u*p0.y + 3*u*u*t*p1.y + 3*u*t*t*p2.y + t*t*t*p3.y });
227
+ }
228
+ }
229
+
230
+ // 2次ベジェを現在サブパスへフラット化して追記する。
231
+ inline void FlattenQuadratic(std::vector<Point>& out, Point p0, Point p1, Point p2, int steps = 12)
232
+ {
233
+ for (int i = 1; i <= steps; ++i) {
234
+ double t = static_cast<double>(i) / steps, u = 1 - t;
235
+ out.push_back({
236
+ u*u*p0.x + 2*u*t*p1.x + t*t*p2.x,
237
+ u*u*p0.y + 2*u*t*p1.y + t*t*p2.y });
238
+ }
239
+ }
240
+
241
+ // arc() を変換適用済みの点列へフラット化して追記する。ccw の巻き戻しも処理。
242
+ inline void FlattenArc(std::vector<Point>& out, const Mat& m,
243
+ double cx, double cy, double r,
244
+ double start, double end, bool ccw, int steps = 32)
245
+ {
246
+ if (ccw && end > start) end -= 2 * kPi;
247
+ if (!ccw && end < start) end += 2 * kPi;
248
+ for (int i = 0; i <= steps; ++i) {
249
+ double t = start + (end - start) * (static_cast<double>(i) / steps);
250
+ out.push_back(Apply(m, cx + std::cos(t) * r, cy + std::sin(t) * r));
251
+ }
252
+ }
253
+
254
+ // clearRect: 矩形を透明で塗りつぶす (クリップ非適用、Canvas 仕様どおり全消去)。
255
+ inline void ClearRect(Surface& s, double x, double y, double w, double h)
256
+ {
257
+ for (int yy = static_cast<int>(y); yy < y + h; ++yy)
258
+ for (int xx = static_cast<int>(x); xx < x + w; ++xx)
259
+ if (xx >= 0 && yy >= 0 && xx < s.width && yy < s.height) {
260
+ size_t i = (static_cast<size_t>(yy) * s.width + xx) * 4;
261
+ s.pixels[i] = s.pixels[i+1] = s.pixels[i+2] = s.pixels[i+3] = 0;
262
+ }
263
+ }
264
+
265
+ } // namespace next2d::raster
@@ -0,0 +1,87 @@
1
+ #include "Bindings.h"
2
+
3
+ #include "HostContext.h"
4
+ #include "EventLoop.h"
5
+ #include "v8/V8Util.h"
6
+
7
+ namespace next2d {
8
+
9
+ namespace {
10
+
11
+ EventLoop* Loop(v8::Isolate* isolate)
12
+ {
13
+ // 実行中の Context (main / worker) に紐づく EventLoop を返す。
14
+ return CurrentEventLoop(isolate);
15
+ }
16
+
17
+ // setTimeout(fn, delay) / setInterval(fn, delay)
18
+ template <bool Repeat>
19
+ void SetTimer(const v8::FunctionCallbackInfo<v8::Value>& args)
20
+ {
21
+ v8::Isolate* isolate = args.GetIsolate();
22
+ if (args.Length() < 1 || !args[0]->IsFunction()) {
23
+ v8util::ThrowTypeError(isolate, "callback function required");
24
+ return;
25
+ }
26
+
27
+ v8::Local<v8::Function> callback = args[0].As<v8::Function>();
28
+ double delay = 0.0;
29
+ if (args.Length() >= 2 && args[1]->IsNumber()) {
30
+ delay = args[1].As<v8::Number>()->Value();
31
+ }
32
+
33
+ const uint32_t id = Loop(isolate)->SetTimer(callback, delay, Repeat);
34
+ args.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, id));
35
+ }
36
+
37
+ void ClearTimer(const v8::FunctionCallbackInfo<v8::Value>& args)
38
+ {
39
+ v8::Isolate* isolate = args.GetIsolate();
40
+ if (args.Length() >= 1 && args[0]->IsNumber()) {
41
+ Loop(isolate)->ClearTimer(args[0].As<v8::Uint32>()->Value());
42
+ }
43
+ }
44
+
45
+ void RequestAnimationFrame(const v8::FunctionCallbackInfo<v8::Value>& args)
46
+ {
47
+ v8::Isolate* isolate = args.GetIsolate();
48
+ if (args.Length() < 1 || !args[0]->IsFunction()) {
49
+ v8util::ThrowTypeError(isolate, "callback function required");
50
+ return;
51
+ }
52
+ const uint32_t id = Loop(isolate)->RequestAnimationFrame(args[0].As<v8::Function>());
53
+ args.GetReturnValue().Set(v8::Integer::NewFromUnsigned(isolate, id));
54
+ }
55
+
56
+ void CancelAnimationFrame(const v8::FunctionCallbackInfo<v8::Value>& args)
57
+ {
58
+ v8::Isolate* isolate = args.GetIsolate();
59
+ if (args.Length() >= 1 && args[0]->IsNumber()) {
60
+ Loop(isolate)->CancelAnimationFrame(args[0].As<v8::Uint32>()->Value());
61
+ }
62
+ }
63
+
64
+ void PerformanceNow(const v8::FunctionCallbackInfo<v8::Value>& args)
65
+ {
66
+ v8::Isolate* isolate = args.GetIsolate();
67
+ args.GetReturnValue().Set(v8::Number::New(isolate, Loop(isolate)->Now()));
68
+ }
69
+
70
+ } // namespace
71
+
72
+ void InstallTimers(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* /*host*/)
73
+ {
74
+ v8util::SetMethod(isolate, global, "setTimeout", SetTimer<false>);
75
+ v8util::SetMethod(isolate, global, "setInterval", SetTimer<true>);
76
+ v8util::SetMethod(isolate, global, "clearTimeout", ClearTimer);
77
+ v8util::SetMethod(isolate, global, "clearInterval", ClearTimer);
78
+ v8util::SetMethod(isolate, global, "requestAnimationFrame", RequestAnimationFrame);
79
+ v8util::SetMethod(isolate, global, "cancelAnimationFrame", CancelAnimationFrame);
80
+
81
+ // performance.now()
82
+ v8::Local<v8::Object> performance = v8::Object::New(isolate);
83
+ v8util::SetMethod(isolate, performance, "now", PerformanceNow);
84
+ v8util::SetValue(isolate, global, "performance", performance);
85
+ }
86
+
87
+ } // namespace next2d
@@ -0,0 +1,235 @@
1
+ // HTMLVideoElement の最小実装 (Media Foundation)。
2
+ //
3
+ // player は document.createElement("video") で生成し、描画ループ中に
4
+ // ctx2d.drawImage(videoElement) でフレームを OffscreenCanvas に描いて GPU へ渡す
5
+ // (VideoGenerateRenderQueueUseCase)。よって video 要素は「画像ソース」として
6
+ // 現在フレームの RGBA を返せる必要がある (GetImageSourcePixels が __isVideoElement を扱う)。
7
+ //
8
+ // NOTE: フレーム精度・シーク・音声同期・ストリーミングは最小限。実機で要検証。
9
+ #include "Bindings.h"
10
+
11
+ #include "HostContext.h"
12
+ #include "AssetLoader.h"
13
+ #include "ImageSource.h"
14
+ #include "EventTarget.h"
15
+ #include "v8/V8Util.h"
16
+
17
+ #include <mfapi.h>
18
+ #include <mfidl.h>
19
+ #include <mfreadwrite.h>
20
+ #include <shlwapi.h>
21
+ #include <wrl/client.h>
22
+
23
+ #include <string>
24
+ #include <vector>
25
+
26
+ #pragma comment(lib, "mfplat.lib")
27
+ #pragma comment(lib, "mfreadwrite.lib")
28
+ #pragma comment(lib, "mfuuid.lib")
29
+ #pragma comment(lib, "shlwapi.lib")
30
+
31
+ using Microsoft::WRL::ComPtr;
32
+
33
+ namespace next2d {
34
+
35
+ using v8util::Str;
36
+ using v8util::SetMethod;
37
+ using v8util::SetValue;
38
+ using v8util::ToStdString;
39
+
40
+ namespace {
41
+
42
+ struct VideoDecoder {
43
+ ComPtr<IMFSourceReader> reader;
44
+ uint32_t width = 0;
45
+ uint32_t height = 0;
46
+ std::vector<uint8_t> frame; // RGBA8 (現在フレーム)
47
+ bool playing = false;
48
+ bool ended = false;
49
+ double duration = 0.0;
50
+
51
+ bool Open(const std::vector<uint8_t>& bytes)
52
+ {
53
+ MFStartup(MF_VERSION);
54
+ ComPtr<IStream> stream = SHCreateMemStream(bytes.data(), static_cast<UINT>(bytes.size()));
55
+ if (!stream) return false;
56
+ ComPtr<IMFByteStream> byte_stream;
57
+ if (FAILED(MFCreateMFByteStreamOnStream(stream.Get(), &byte_stream))) return false;
58
+ if (FAILED(MFCreateSourceReaderFromByteStream(byte_stream.Get(), nullptr, &reader))) return false;
59
+
60
+ // 出力を RGB32 (BGRX) に指定
61
+ ComPtr<IMFMediaType> out;
62
+ MFCreateMediaType(&out);
63
+ out->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
64
+ out->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
65
+ if (FAILED(reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, nullptr, out.Get()))) {
66
+ return false;
67
+ }
68
+
69
+ ComPtr<IMFMediaType> actual;
70
+ reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, &actual);
71
+ UINT32 w = 0, h = 0;
72
+ MFGetAttributeSize(actual.Get(), MF_MT_FRAME_SIZE, &w, &h);
73
+ width = w; height = h;
74
+ frame.assign(static_cast<size_t>(w) * h * 4, 0);
75
+
76
+ PROPVARIANT var;
77
+ if (SUCCEEDED(reader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE,
78
+ MF_PD_DURATION, &var))) {
79
+ duration = static_cast<double>(var.uhVal.QuadPart) / 1e7; // 100ns -> s
80
+ PropVariantClear(&var);
81
+ }
82
+ return width > 0 && height > 0;
83
+ }
84
+
85
+ // 次サンプルを RGBA へデコードして frame を更新する。
86
+ bool ReadNextFrame()
87
+ {
88
+ if (!reader) return false;
89
+ DWORD flags = 0;
90
+ ComPtr<IMFSample> sample;
91
+ if (FAILED(reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0,
92
+ nullptr, &flags, nullptr, &sample))) {
93
+ return false;
94
+ }
95
+ if (flags & MF_SOURCE_READERF_ENDOFSTREAM) { ended = true; return false; }
96
+ if (!sample) return false;
97
+
98
+ ComPtr<IMFMediaBuffer> buffer;
99
+ sample->ConvertToContiguousBuffer(&buffer);
100
+ BYTE* data = nullptr; DWORD len = 0;
101
+ buffer->Lock(&data, nullptr, &len);
102
+ // RGB32 = BGRX。上下反転して RGBA へ (MF は bottom-up の場合がある → ここでは top-down 前提)
103
+ const size_t px = static_cast<size_t>(width) * height;
104
+ for (size_t i = 0; i < px && (i * 4 + 3) < len; ++i) {
105
+ frame[i*4+0] = data[i*4+2]; // R <- B 位置
106
+ frame[i*4+1] = data[i*4+1]; // G
107
+ frame[i*4+2] = data[i*4+0]; // B <- R 位置
108
+ frame[i*4+3] = 255;
109
+ }
110
+ buffer->Unlock();
111
+ return true;
112
+ }
113
+ };
114
+
115
+ void ReleaseVideo(const v8::WeakCallbackInfo<VideoDecoder>& info) { delete info.GetParameter(); }
116
+
117
+ VideoDecoder* Decoder(v8::Local<v8::Object> obj)
118
+ {
119
+ return static_cast<VideoDecoder*>(obj->GetInternalField(0).As<v8::External>()->Value());
120
+ }
121
+
122
+ void FireVideoEvent(v8::Isolate* isolate, v8::Local<v8::Object> self, const char* type)
123
+ {
124
+ v8::Local<v8::Object> ev = v8::Object::New(isolate);
125
+ SetValue(isolate, ev, "type", Str(isolate, type));
126
+ SetValue(isolate, ev, "target", self);
127
+ DispatchEvent(isolate, self, ev);
128
+ }
129
+
130
+ void LoadVideo(v8::Isolate* isolate, v8::Local<v8::Object> self, const std::string& url)
131
+ {
132
+ HostContext* host = HostContext::From(isolate);
133
+ std::vector<uint8_t> bytes;
134
+ std::string text;
135
+ if (url.rfind("blob:", 0) == 0 && ResolveObjectURL(url, &text)) {
136
+ bytes.assign(text.begin(), text.end());
137
+ } else if (host->assets) {
138
+ auto b = host->assets->ReadBinary(url);
139
+ if (b) bytes = std::move(*b);
140
+ }
141
+ VideoDecoder* dec = Decoder(self);
142
+ if (!bytes.empty() && dec->Open(bytes)) {
143
+ SetValue(isolate, self, "videoWidth", v8::Integer::NewFromUnsigned(isolate, dec->width));
144
+ SetValue(isolate, self, "videoHeight", v8::Integer::NewFromUnsigned(isolate, dec->height));
145
+ SetValue(isolate, self, "duration", v8::Number::New(isolate, dec->duration));
146
+ SetValue(isolate, self, "readyState", v8::Integer::New(isolate, 4));
147
+ FireVideoEvent(isolate, self, "loadedmetadata");
148
+ FireVideoEvent(isolate, self, "canplaythrough");
149
+ } else {
150
+ FireVideoEvent(isolate, self, "error");
151
+ }
152
+ }
153
+
154
+ } // namespace
155
+
156
+ // GetImageSourcePixels (Image.cpp) から呼ばれる: video の現在フレーム RGBA を返す。
157
+ bool GetVideoFramePixels(v8::Isolate* isolate, v8::Local<v8::Object> obj,
158
+ const uint8_t** out_rgba, uint32_t* out_w, uint32_t* out_h)
159
+ {
160
+ VideoDecoder* dec = Decoder(obj);
161
+ if (!dec || dec->width == 0) return false;
162
+ if (dec->playing && !dec->ended) {
163
+ dec->ReadNextFrame(); // 描画のたびに次フレームへ進める(簡易フレーム進行)
164
+ if (dec->ended) {
165
+ FireVideoEvent(isolate, obj, "ended");
166
+ }
167
+ }
168
+ *out_rgba = dec->frame.data();
169
+ *out_w = dec->width;
170
+ *out_h = dec->height;
171
+ return true;
172
+ }
173
+
174
+ v8::Local<v8::Object> CreateVideoElement(v8::Isolate* isolate, HostContext* /*host*/)
175
+ {
176
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
177
+ v8::Local<v8::ObjectTemplate> tmpl = v8::ObjectTemplate::New(isolate);
178
+ tmpl->SetInternalFieldCount(1);
179
+ v8::Local<v8::Object> self = tmpl->NewInstance(ctx).ToLocalChecked();
180
+
181
+ auto* dec = new VideoDecoder();
182
+ self->SetInternalField(0, v8::External::New(isolate, dec));
183
+ auto* handle = new v8::Global<v8::Object>(isolate, self);
184
+ handle->SetWeak(dec, ReleaseVideo, v8::WeakCallbackType::kParameter);
185
+
186
+ SetValue(isolate, self, "__isVideoElement", v8::Boolean::New(isolate, true));
187
+ SetValue(isolate, self, "tagName", Str(isolate, "VIDEO"));
188
+ SetValue(isolate, self, "videoWidth", v8::Integer::New(isolate, 0));
189
+ SetValue(isolate, self, "videoHeight", v8::Integer::New(isolate, 0));
190
+ SetValue(isolate, self, "currentTime", v8::Number::New(isolate, 0));
191
+ SetValue(isolate, self, "readyState", v8::Integer::New(isolate, 0));
192
+ SetValue(isolate, self, "loop", v8::Boolean::New(isolate, false));
193
+ SetValue(isolate, self, "style", v8::Object::New(isolate));
194
+ InstallEventTarget(isolate, self);
195
+
196
+ // src セッター: 代入で読み込み開始
197
+ self->SetNativeDataProperty(ctx, Str(isolate, "src"),
198
+ [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
199
+ v8::Local<v8::Value> v;
200
+ if (info.This()->Get(info.GetIsolate()->GetCurrentContext(),
201
+ v8util::Str(info.GetIsolate(), "__src")).ToLocal(&v)) {
202
+ info.GetReturnValue().Set(v);
203
+ }
204
+ },
205
+ [](v8::Local<v8::Name>, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {
206
+ v8::Isolate* iso = info.GetIsolate();
207
+ SetValue(iso, info.This(), "__src", value);
208
+ LoadVideo(iso, info.This(), ToStdString(iso, value));
209
+ }).Check();
210
+
211
+ SetMethod(isolate, self, "load", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
212
+ v8::Isolate* iso = a.GetIsolate();
213
+ v8::Local<v8::Value> src;
214
+ if (a.This()->Get(iso->GetCurrentContext(), Str(iso, "__src")).ToLocal(&src) && src->IsString()) {
215
+ LoadVideo(iso, a.This(), ToStdString(iso, src));
216
+ }
217
+ });
218
+ SetMethod(isolate, self, "play", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
219
+ v8::Isolate* iso = a.GetIsolate();
220
+ Decoder(a.This())->playing = true;
221
+ auto r = v8::Promise::Resolver::New(iso->GetCurrentContext()).ToLocalChecked();
222
+ r->Resolve(iso->GetCurrentContext(), v8::Undefined(iso)).Check();
223
+ a.GetReturnValue().Set(r->GetPromise());
224
+ });
225
+ SetMethod(isolate, self, "pause", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
226
+ Decoder(a.This())->playing = false;
227
+ });
228
+ SetMethod(isolate, self, "canPlayType", [](const v8::FunctionCallbackInfo<v8::Value>& a) {
229
+ a.GetReturnValue().Set(Str(a.GetIsolate(), "maybe"));
230
+ });
231
+
232
+ return self;
233
+ }
234
+
235
+ } // namespace next2d