12#include <QMimeDatabase>
16#include <private/qabstractvideobuffer_p.h>
17#include <private/qplatformvideosink_p.h>
18#include <private/qmemoryvideobuffer_p.h>
19#include <private/qvideotexturehelper_p.h>
20#include <private/qstdweb_p.h>
22#include <emscripten/bind.h>
23#include <emscripten/html5.h>
24#include <emscripten/val.h>
42 emscripten::val document = emscripten::val::global(
"document");
43 emscripten::val videoElement =
44 document.call<emscripten::val>(
"getElementById", std::string(
m_videoSurfaceId));
45 videoElement.call<
void>(
"removeAttribute", emscripten::val(
"src"));
46 videoElement.call<
void>(
"load");
56 emscripten::val videoFrame = emscripten::val::global(
"VideoFrame");
57 return (!videoFrame.isNull() && !videoFrame.isUndefined());
68 if (m_pendingVideoSize == newSize)
71 m_pendingVideoSize = newSize;
77 m_currentVideoMode =
mode;
82 if (m_video.isUndefined() || m_video.isNull()
89 switch (m_currentVideoMode) {
91 emscripten::val sourceObj = m_video[
"src"];
92 if ((sourceObj.isUndefined() || sourceObj.isNull()) && !m_source.
isEmpty()) {
94 m_video.set(
"src", m_source);
95 m_video.call<
void>(
"load");
99 emscripten::val
stream = m_video[
"srcObject"];
104 emscripten::val videoTracks =
stream.call<emscripten::val>(
"getVideoTracks");
105 if (videoTracks.isNull() || videoTracks.isUndefined()) {
111 if (videoTracks[
"length"].as<int>() == 0) {
117 emscripten::val videoSettings = videoTracks[0].call<emscripten::val>(
"getSettings");
118 if (!videoSettings.isNull() || !videoSettings.isUndefined()) {
120 const int width = videoSettings[
"width"].as<
int>();
121 const int height = videoSettings[
"height"].as<
int>();
132 m_shouldStop =
false;
133 m_toBePaused =
false;
134 m_video.call<
void>(
"play");
137 if (m_hasVideoFrame) {
138 m_video.call<emscripten::val>(
"requestVideoFrameCallback",
139 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
150 if (m_video.isUndefined() || m_video.isNull()) {
158 m_toBePaused =
false;
159 m_video.call<
void>(
"load");
161 m_video.call<
void>(
"pause");
169 if (m_video.isUndefined() || m_video.isNull()) {
174 m_shouldStop =
false;
176 m_video.call<
void>(
"pause");
186 m_video.set(
"currentTime", emscripten::val(0));
187 m_video.call<
void>(
"load");
206 if (m_video.isUndefined() || m_video.isNull()) {
211 constexpr int hasCurrentData = 2;
212 if (!m_video.isUndefined() || !m_video.isNull())
213 return m_video[
"readyState"].as<
int>() >= hasCurrentData;
222 if (m_video.isUndefined() || m_video.isNull()) {
238 qWarning() <<
"Failed to open file";
251 if (m_video.isUndefined() || m_video.isNull()) {
256 emscripten::val document = emscripten::val::global(
"document");
262 m_video.call<
void>(
"load");
267 m_cameraIsReady =
false;
268 emscripten::val navigator = emscripten::val::global(
"navigator");
269 emscripten::val mediaDevices = navigator[
"mediaDevices"];
271 if (mediaDevices.isNull() || mediaDevices.isUndefined()) {
272 qWarning() <<
"No media devices found";
279 [
this](emscripten::val
stream) {
280 qCDebug(qWasmMediaVideoOutput) <<
"getUserMediaSuccess";
282 m_video.set(
"srcObject",
stream);
283 m_cameraIsReady =
true;
286 [](emscripten::val
error) {
288 <<
"getUserMedia fail"
294 emscripten::val constraints = emscripten::val::object();
296 constraints.set(
"audio", m_hasAudio);
298 emscripten::val videoContraints = emscripten::val::object();
299 videoContraints.set(
"exact",
id);
300 videoContraints.set(
"deviceId",
id);
301 constraints.set(
"video", videoContraints);
305 std::move(getUserMediaCallback), constraints);
310 if (
stream->bytesAvailable() == 0) {
315 if (m_video.isUndefined() || m_video.isNull()) {
329 if (
window[
"safari"].isUndefined()) {
330 emscripten::val contentUrl =
window[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.
val());
331 m_video.set(
"src", contentUrl);
335 m_video.set(
"srcObject", contentBlob.
val());
338 m_video.call<
void>(
"load");
344 m_video.set(
"volume", volume);
349 if (m_video.isUndefined() || m_video.isNull()) {
354 m_video.set(
"muted", muted);
359 return (!m_video.isUndefined() || !m_video.isNull())
360 ? (m_video[
"currentTime"].as<
double>() * 1000)
367 float positionToSetInSeconds = float(positionMSecs) / 1000;
368 emscripten::val seekableTimeRange = m_video[
"seekable"];
369 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
371 if (seekableTimeRange[
"length"].as<int>() < 1)
373 if (positionToSetInSeconds
374 >= seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>()
375 && positionToSetInSeconds
376 <= seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>()) {
377 m_requestedPosition = positionToSetInSeconds;
379 m_video.set(
"currentTime", m_requestedPosition);
383 qCDebug(qWasmMediaVideoOutput) <<
"m_requestedPosition" << m_requestedPosition;
388 if (m_video.isUndefined() || m_video.isNull()) {
394 emscripten::val seekableTimeRange = m_video[
"seekable"];
395 if (seekableTimeRange[
"length"].as<int>() < 1)
397 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
398 bool isit = !
qFuzzyCompare(seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>(),
399 seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>());
410 emscripten::val document = emscripten::val::global(
"document");
411 emscripten::val body = document[
"body"];
413 emscripten::val oldVideo = document.call<emscripten::val>(
"getElementsByClassName",
415 ? std::string(
"Camera")
416 : std::string(
"Video")));
420 if (oldVideo[
"length"].as<int>() > 0)
421 oldVideo[0].call<
void>(
"remove");
424 m_video = document.call<emscripten::val>(
"createElement", std::string(
"video"));
427 m_video.call<
void>(
"setAttribute", std::string(
"class"),
429 : std::string(
"Video")));
430 m_video.set(
"data-qvideocontext",
431 emscripten::val(
quintptr(
reinterpret_cast<void *
>(
this))));
434 m_video.set(
"preload",
"metadata");
440 std::string originString =
"anonymous";
444 m_video.call<
void>(
"setAttribute", std::string(
"crossorigin"), originString);
445 body.call<
void>(
"appendChild", m_video);
448 emscripten::val videoElementGeometry =
449 document.call<emscripten::val>(
"createElement", std::string(
"source"));
451 videoElementGeometry.set(
"src", m_source.
toStdString());
456 emscripten::val style = m_video[
"style"];
457 style.set(
"position",
"absolute");
458 style.set(
"display",
"none");
472 emscripten::val document = emscripten::val::global(
"document");
476 m_offscreen = emscripten::val::global(
"OffscreenCanvas");
478 if (m_offscreen.isUndefined()) {
480 m_offscreen = document.call<emscripten::val>(
"createElement", std::string(
"canvas"));
482 m_offscreen.set(
"style",
483 "position:absolute;left:-1000px;top:-1000px");
484 m_offscreen.set(
"width", offscreenSize.
width());
485 m_offscreen.set(
"height", offscreenSize.
height());
486 m_offscreenContext = m_offscreen.call<emscripten::val>(
"getContext", std::string(
"2d"));
488 m_offscreen = emscripten::val::global(
"OffscreenCanvas")
489 .new_(offscreenSize.
width(), offscreenSize.
height());
490 emscripten::val offscreenAttributes = emscripten::val::array();
491 offscreenAttributes.set(
"willReadFrequently",
true);
492 m_offscreenContext = m_offscreen.call<emscripten::val>(
"getContext", std::string(
"2d"),
493 offscreenAttributes);
496 m_offscreen.set(
"id", offscreenId.c_str());
505 auto timeUpdateCallback = [=](emscripten::val
event) {
506 qCDebug(qWasmMediaVideoOutput) <<
"timeupdate";
514 auto playCallback = [=](emscripten::val
event) {
516 qCDebug(qWasmMediaVideoOutput) <<
"play";
523 auto endedCallback = [=](emscripten::val
event) {
525 qCDebug(qWasmMediaVideoOutput) <<
"ended";
534 auto durationChangeCallback = [=](emscripten::val
event) {
535 qCDebug(qWasmMediaVideoOutput) <<
"durationChange";
538 qint64 dur =
event[
"target"][
"duration"].as<
double>() * 1000;
541 m_durationChangeEvent.
reset(
545 auto loadedDataCallback = [=](emscripten::val
event) {
547 qCDebug(qWasmMediaVideoOutput) <<
"loaded data";
554 auto errorCallback = [=](emscripten::val
event) {
555 qCDebug(qWasmMediaVideoOutput) <<
"error";
564 auto resizeCallback = [=](emscripten::val
event) {
566 qCDebug(qWasmMediaVideoOutput) <<
"resize";
569 QRect(0, 0, m_video[
"videoWidth"].as<int>(), m_video[
"videoHeight"].as<int>()));
570 emit sizeChange(m_video[
"videoWidth"].as<int>(), m_video[
"videoHeight"].as<int>());
576 auto loadedMetadataCallback = [=](emscripten::val
event) {
578 qCDebug(qWasmMediaVideoOutput) <<
"loaded meta data";
582 m_loadedMetadataChangeEvent.
reset(
586 auto loadStartCallback = [=](emscripten::val
event) {
588 qCDebug(qWasmMediaVideoOutput) <<
"load started";
591 m_shouldStop =
false;
597 auto canPlayCallback = [=](emscripten::val
event) {
598 if (
event.isUndefined() ||
event.isNull())
600 qCDebug(qWasmMediaVideoOutput) <<
"can play"
601 <<
"m_requestedPosition" << m_requestedPosition;
609 auto canPlayThroughCallback = [=](emscripten::val
event) {
611 qCDebug(qWasmMediaVideoOutput) <<
"can play through"
612 <<
"m_shouldStop" << m_shouldStop;
616 if (!m_isSeeking && !m_shouldStop) {
617 emscripten::val timeRanges = m_video[
"buffered"];
618 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
619 && timeRanges[
"length"].as<
int>() == 1) {
620 double buffered = m_video[
"buffered"].call<emscripten::val>(
"end", 0).as<double>();
621 const double duration = m_video[
"duration"].as<
double>();
623 if (duration == buffered) {
624 m_currentBufferedValue = 100;
630 if (m_hasVideoFrame) {
631 m_video.call<emscripten::val>(
"requestVideoFrameCallback",
632 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
637 m_shouldStop =
false;
640 m_canPlayThroughChangeEvent.
reset(
644 auto seekingCallback = [=](emscripten::val
event) {
647 <<
"seeking started" << (m_video[
"currentTime"].as<
double>() * 1000);
653 auto seekedCallback = [=](emscripten::val
event) {
655 qCDebug(qWasmMediaVideoOutput) <<
"seeked" << (m_video[
"currentTime"].as<
double>() * 1000);
662 auto emptiedCallback = [=](emscripten::val
event) {
665 qCDebug(qWasmMediaVideoOutput) <<
"emptied";
673 auto stalledCallback = [=](emscripten::val
event) {
675 qCDebug(qWasmMediaVideoOutput) <<
"stalled";
682 auto waitingCallback = [=](emscripten::val
event) {
685 qCDebug(qWasmMediaVideoOutput) <<
"waiting";
693 auto playingCallback = [=](emscripten::val
event) {
695 qCDebug(qWasmMediaVideoOutput) <<
"playing";
699 if (m_toBePaused || !m_shouldStop) {
700 m_toBePaused =
false;
702 if (m_hasVideoFrame) {
703 m_video.call<emscripten::val>(
"requestVideoFrameCallback",
704 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
713 auto progesssCallback = [=](emscripten::val
event) {
714 if (
event.isUndefined() ||
event.isNull())
717 const double duration =
event[
"target"][
"duration"].as<
double>();
721 emscripten::val timeRanges =
event[
"target"][
"buffered"];
723 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
724 && timeRanges[
"length"].as<int>() == 1) {
725 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
726 if (!dVal.isNull() || !dVal.isUndefined()) {
727 double bufferedEnd = dVal.as<
double>();
729 if (duration > 0 && bufferedEnd > 0) {
730 const double bufferedValue = (bufferedEnd / duration * 100);
731 qCDebug(qWasmMediaVideoOutput) <<
"progress buffered";
732 m_currentBufferedValue = bufferedValue;
734 if (bufferedEnd == duration)
746 auto pauseCallback = [=](emscripten::val
event) {
748 qCDebug(qWasmMediaVideoOutput) <<
"pause";
750 const double currentTime = m_video[
"currentTime"].as<
double>();
751 const double duration = m_video[
"duration"].as<
double>();
756 m_video.set(
"currentTime", emscripten::val(0));
765 emscripten::val
window = emscripten::val::global(
"window");
766 window.call<
void>(
"addEventListener", std::string(
"beforeunload"),
767 emscripten::val::module_property(
"mbeforeUnload"));
773 QRect m_videoElementSource(windowGeometry.
topLeft(), windowGeometry.
size());
775 emscripten::val style = m_video[
"style"];
776 style.set(
"left",
QString(
"%1px").
arg(m_videoElementSource.
left()).toStdString());
777 style.set(
"top",
QString(
"%1px").
arg(m_videoElementSource.
top()).toStdString());
778 style.set(
"width",
QString(
"%1px").
arg(m_videoElementSource.
width()).toStdString());
779 style.set(
"height",
QString(
"%1px").
arg(m_videoElementSource.
height()).toStdString());
780 style.set(
"z-index",
"999");
782 if (!m_hasVideoFrame) {
784 m_offscreen.set(
"width", m_videoElementSource.
width());
785 m_offscreen.set(
"height", m_videoElementSource.
height());
794 if (m_video.isUndefined() || m_video.isNull())
796 return m_video[
"duration"].as<
double>() * 1000;
806 m_video.set(
"playbackRate", emscripten::val(
rate));
811 return (m_video.isUndefined() || m_video.isNull()) ? 0 : m_video[
"playbackRate"].as<
float>();
814void QWasmVideoOutput::checkNetworkState()
816 int netState = m_video[
"networkState"].as<
int>();
818 qCDebug(qWasmMediaVideoOutput) << netState;
833void QWasmVideoOutput::videoComputeFrame(
void *
context)
835 if (m_offscreenContext.isUndefined() || m_offscreenContext.isNull()) {
836 qCDebug(qWasmMediaVideoOutput) <<
"canvas context could not be found";
839 emscripten::val
document = emscripten::val::global(
"document");
841 emscripten::val videoElement =
844 if (videoElement.isUndefined() || videoElement.isNull()) {
845 qCDebug(qWasmMediaVideoOutput) <<
"video element could not be found";
849 const int videoWidth = videoElement[
"videoWidth"].as<
int>();
850 const int videoHeight = videoElement[
"videoHeight"].as<
int>();
852 if (videoWidth == 0 || videoHeight == 0)
855 m_offscreenContext.call<
void>(
"drawImage", videoElement, 0, 0, videoWidth, videoHeight);
857 emscripten::val
frame =
858 m_offscreenContext.call<emscripten::val>(
"getImageData", 0, 0, videoWidth, videoHeight);
860 const QSize frameBytesAllocationSize(videoWidth, videoHeight);
877 qWarning() <<
"ERROR ALERT!! video sink not set";
888 emscripten::val videoElement =
889 emscripten::val::global(
"document").
890 call<emscripten::val>(
"getElementById",
893 emscripten::val oneVideoFrame = val::global(
"VideoFrame").new_(videoElement);
895 if (oneVideoFrame.isNull() || oneVideoFrame.isUndefined()) {
897 <<
"ERROR" <<
"failed to construct VideoFrame";
901 emscripten::val frameBytesAllocationSize = oneVideoFrame.call<emscripten::val>(
"allocationSize");
903 emscripten::val frameBuffer =
904 emscripten::val::global(
"Uint8Array").new_(frameBytesAllocationSize);
907 copyToCallback.
thenFunc = [oneVideoFrame, frameBuffer, videoElement]
908 (emscripten::val frameLayout)
910 if (frameLayout.isNull() || frameLayout.isUndefined()) {
911 qCDebug(qWasmMediaVideoOutput) <<
"theres no frameLayout";
917 oneVideoFrame[
"displayHeight"].as<int>());
920 QByteArray frameBytes = QByteArray::fromEcmaUint8Array(frameBuffer);
924 qWarning() <<
"Invalid pixel format";
939 if (!wasmVideoOutput) {
940 qCDebug(qWasmMediaVideoOutput) <<
"ERROR:"
941 <<
"data-qvideocontext not found";
945 qWarning() <<
"ERROR ALERT!! video sink not set";
949 oneVideoFrame.call<emscripten::val>(
"close");
951 copyToCallback.
catchFunc = [oneVideoFrame, videoElement](emscripten::val
error)
953 qCDebug(qWasmMediaVideoOutput) <<
"Error"
957 oneVideoFrame.call<emscripten::val>(
"close");
958 videoElement.call<emscripten::val>(
"stop");
964 videoElement.call<emscripten::val>(
"requestVideoFrameCallback",
965 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
971 static auto frame = [](
double frameTime,
void *
context) ->
int {
975 emscripten::val document = emscripten::val::global(
"document");
976 emscripten::val videoElement =
977 document.call<emscripten::val>(
"getElementById", std::string(
m_videoSurfaceId));
979 if (videoElement[
"paused"].as<bool>() || videoElement[
"ended"].as<
bool>())
982 videoOutput->videoComputeFrame(
context);
987 emscripten_request_animation_frame_loop(
frame,
this);
994 if (videoFormat ==
"I420")
998 else if (videoFormat ==
"I422")
1002 else if (videoFormat ==
"NV12")
1004 else if (videoFormat ==
"RGBA")
1006 else if (videoFormat ==
"I420")
1008 else if (videoFormat ==
"RGBX")
1010 else if (videoFormat ==
"BGRA")
1012 else if (videoFormat ==
"BGRX")
1021 emscripten::val
stream = m_video[
"srcObject"];
1022 if (!
stream.isUndefined() || !
stream[
"getVideoTracks"].isUndefined()) {
1023 emscripten::val tracks =
stream.call<emscripten::val>(
"getVideoTracks");
1024 if (!tracks.isUndefined()) {
1025 if (tracks[
"length"].as<int>() == 0)
1026 return emscripten::val::undefined();
1028 emscripten::val track = tracks[0];
1029 if (!track.isUndefined()) {
1030 emscripten::val trackCaps = emscripten::val::undefined();
1031 if (!track[
"getCapabilities"].isUndefined())
1032 trackCaps = track.call<emscripten::val>(
"getCapabilities");
1034 trackCaps = track.call<emscripten::val>(
"getSettings");
1036 if (!trackCaps.isUndefined())
1045 return emscripten::val::undefined();
1050 emscripten::val
stream = m_video[
"srcObject"];
1052 ||
stream[
"getVideoTracks"].isUndefined())
1055 emscripten::val tracks =
stream.call<emscripten::val>(
"getVideoTracks");
1056 if (!tracks.isNull() || !tracks.isUndefined()) {
1057 if (tracks[
"length"].as<int>() == 0)
1060 emscripten::val track = tracks[0];
1061 emscripten::val contraint = emscripten::val::object();
1062 contraint.set(std::move(
key),
value);
1063 track.call<emscripten::val>(
"applyConstraints", contraint);
1076#include "moc_qwasmvideooutput_p.cpp"
\inmodule QtCore\reentrant
bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
\inmodule QtCore \reentrant
The QMemoryVideoBuffer class provides a system memory allocated video data buffer.
QMimeType mimeTypeForData(const QByteArray &data) const
Returns a MIME type for data.
\inmodule QtCore\reentrant
constexpr int height() const noexcept
Returns the height of the rectangle.
constexpr QPoint topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
constexpr int top() const noexcept
Returns the y-coordinate of the rectangle's top edge.
constexpr int left() const noexcept
Returns the x-coordinate of the rectangle's left edge.
constexpr QSize size() const noexcept
Returns the size of the rectangle.
constexpr int width() const noexcept
Returns the width of the rectangle.
void reset(T *other=nullptr) noexcept(noexcept(Cleanup::cleanup(std::declval< T * >())))
Deletes the existing object it is pointing to (if any), and sets its pointer to other.
constexpr int height() const noexcept
Returns the height.
constexpr int width() const noexcept
Returns the width.
\macro QT_RESTRICTED_CAST_FROM_ASCII
static QString fromStdString(const std::string &s)
std::string toStdString() const
Returns a std::string object with the data contained in this QString.
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
The QVideoFrame class represents a frame of video data.
The QVideoSink class represents a generic sink for video data.
void setVideoFrame(const QVideoFrame &frame)
Sets the current video frame.
QPlatformVideoSink * platformVideoSink() const
void addCameraSourceElement(const std::string &id)
void updateVideoElementGeometry(const QRect &windowGeometry)
bool setDeviceSetting(const std::string &key, emscripten::val value)
void sizeChange(qint32 width, qint32 height)
void newFrame(const QVideoFrame &newFrame)
emscripten::val surfaceElement()
void durationChanged(qint64 duration)
emscripten::val getDeviceCapabilities()
static void videoFrameCallback(emscripten::val now, emscripten::val metadata)
void setVideoSize(const QSize &)
void setMuted(bool muted)
void setSource(const QUrl &url)
void setVideoMode(QWasmVideoOutput::WasmVideoMode mode)
void bufferingChanged(qint32 percent)
void seekTo(qint64 position)
void setVolume(qreal volume)
void createVideoElement(const std::string &id)
void stateChanged(QWasmMediaPlayer::QWasmMediaPlayerState newState)
void setSurface(QVideoSink *surface)
void statusChanged(QMediaPlayer::MediaStatus status)
void progressChanged(qint32 position)
void errorOccured(qint32 code, const QString &message)
qint64 getCurrentPosition()
void setPlaybackRate(qreal rate)
void doElementCallbacks()
void addSourceElement(const QString &urlString)
void createOffscreenElement(const QSize &offscreenSize)
void videoFrameTimerCallback()
static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType)
QByteArray copyToQByteArray() const
Combined button and popup list for selecting options.
const TextureDescription * textureDescription(QVideoFrameFormat::PixelFormat format)
emscripten::val document()
void make(emscripten::val target, QString methodName, PromiseCallbacks callbacks, Args... args)
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
bool qFuzzyCompare(qfloat16 p1, qfloat16 p2) noexcept
#define Q_GLOBAL_STATIC_WITH_ARGS(TYPE, NAME, ARGS)
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qBound(const T &min, const T &val, const T &max)
GLint GLsizei GLsizei height
static constexpr QSize frameSize(const T &frame)
#define QStringLiteral(str)
static double currentTime()
static bool checkForVideoFrame()
EMSCRIPTEN_BINDINGS(video_module)
static std::string m_videoSurfaceId
void qtVideoBeforeUnload(emscripten::val event)
QUrl url("example.com")
[constructor-url-reference]
application x qt windows mime
[2]
int strideForWidth(int width) const
std::function< void(emscripten::val)> thenFunc
std::function< void(emscripten::val)> catchFunc
IUIAutomationTreeWalker __RPC__deref_out_opt IUIAutomationElement ** parent