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>
23#include <emscripten/bind.h>
24#include <emscripten/html5.h>
25#include <emscripten/val.h>
43 emscripten::val document = emscripten::val::global(
"document");
44 emscripten::val videoElement =
45 document.call<emscripten::val>(
"getElementById", std::string(
m_videoSurfaceId));
46 videoElement.call<
void>(
"removeAttribute", emscripten::val(
"src"));
47 videoElement.call<
void>(
"load");
57 emscripten::val videoFrame = emscripten::val::global(
"VideoFrame");
58 return (!videoFrame.isNull() && !videoFrame.isUndefined());
69 if (m_pendingVideoSize == newSize)
72 m_pendingVideoSize = newSize;
78 m_currentVideoMode =
mode;
83 if (m_video.isUndefined() || m_video.isNull()
90 switch (m_currentVideoMode) {
92 emscripten::val sourceObj = m_video[
"src"];
93 if ((sourceObj.isUndefined() || sourceObj.isNull()) && !m_source.
isEmpty()) {
95 m_video.set(
"src", m_source);
96 m_video.call<
void>(
"load");
100 if (!m_cameraIsReady) {
101 m_shouldBeStarted =
true;
104 emscripten::val
stream = m_video[
"srcObject"];
106 qCDebug(qWasmMediaVideoOutput) <<
"ERROR";
110 emscripten::val videoTracks =
stream.call<emscripten::val>(
"getVideoTracks");
111 if (videoTracks.isNull() || videoTracks.isUndefined()) {
117 if (videoTracks[
"length"].as<int>() == 0) {
123 emscripten::val videoSettings = videoTracks[0].call<emscripten::val>(
"getSettings");
124 if (!videoSettings.isNull() || !videoSettings.isUndefined()) {
126 const int width = videoSettings[
"width"].as<
int>();
127 const int height = videoSettings[
"height"].as<
int>();
138 m_shouldStop =
false;
139 m_toBePaused =
false;
140 m_video.call<
void>(
"play");
143 if (m_hasVideoFrame) {
144 m_video.call<emscripten::val>(
"requestVideoFrameCallback",
145 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
156 if (m_video.isUndefined() || m_video.isNull()) {
164 m_toBePaused =
false;
165 m_video.call<
void>(
"load");
167 m_video.call<
void>(
"pause");
175 if (m_video.isUndefined() || m_video.isNull()) {
180 m_shouldStop =
false;
182 m_video.call<
void>(
"pause");
192 m_video.set(
"currentTime", emscripten::val(0));
193 m_video.call<
void>(
"load");
213 if (m_video.isUndefined() || m_video.isNull()) {
218 constexpr int hasCurrentData = 2;
219 if (!m_video.isUndefined() || !m_video.isNull())
220 return m_video[
"readyState"].as<
int>() >= hasCurrentData;
229 if (m_video.isUndefined() || m_video.isNull()) {
244 qWarning() <<
"Failed to open file";
254 m_video.set(
"src",
src.toStdString());
255 m_video.call<
void>(
"load");
260 m_cameraIsReady =
false;
261 emscripten::val navigator = emscripten::val::global(
"navigator");
262 emscripten::val mediaDevices = navigator[
"mediaDevices"];
264 if (mediaDevices.isNull() || mediaDevices.isUndefined()) {
265 qWarning() <<
"No media devices found";
272 [
this](emscripten::val
stream) {
273 qCDebug(qWasmMediaVideoOutput) <<
"getUserMediaSuccess";
275 m_video.set(
"srcObject",
stream);
276 m_cameraIsReady =
true;
277 if (m_shouldBeStarted) {
279 m_shouldBeStarted =
false;
283 [](emscripten::val
error) {
285 <<
"getUserMedia fail"
291 emscripten::val constraints = emscripten::val::object();
293 constraints.set(
"audio", m_hasAudio);
295 emscripten::val videoContraints = emscripten::val::object();
296 videoContraints.set(
"exact",
id);
297 videoContraints.set(
"deviceId",
id);
298 constraints.set(
"video", videoContraints);
302 std::move(getUserMediaCallback), constraints);
307 if (
stream->bytesAvailable() == 0) {
312 if (m_video.isUndefined() || m_video.isNull()) {
326 if (
window[
"safari"].isUndefined()) {
327 emscripten::val contentUrl =
window[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
328 m_video.set(
"src", contentUrl);
332 m_video.set(
"srcObject", contentBlob.val());
339 m_video.set(
"volume", volume);
344 if (m_video.isUndefined() || m_video.isNull()) {
349 m_video.set(
"muted", muted);
354 return (!m_video.isUndefined() || !m_video.isNull())
355 ? (m_video[
"currentTime"].as<
double>() * 1000)
362 float positionToSetInSeconds = float(positionMSecs) / 1000;
363 emscripten::val seekableTimeRange = m_video[
"seekable"];
364 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
366 if (seekableTimeRange[
"length"].as<int>() < 1)
368 if (positionToSetInSeconds
369 >= seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>()
370 && positionToSetInSeconds
371 <= seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>()) {
372 m_requestedPosition = positionToSetInSeconds;
374 m_video.set(
"currentTime", m_requestedPosition);
378 qCDebug(qWasmMediaVideoOutput) <<
"m_requestedPosition" << m_requestedPosition;
383 if (m_video.isUndefined() || m_video.isNull()) {
389 emscripten::val seekableTimeRange = m_video[
"seekable"];
390 if (seekableTimeRange[
"length"].as<int>() < 1)
392 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
393 bool isit = !
qFuzzyCompare(seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>(),
394 seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>());
405 emscripten::val document = emscripten::val::global(
"document");
406 emscripten::val body = document[
"body"];
408 emscripten::val oldVideo = document.call<emscripten::val>(
"getElementsByClassName",
410 ? std::string(
"Camera")
411 : std::string(
"Video")));
415 if (oldVideo[
"length"].as<int>() > 0)
416 oldVideo[0].call<
void>(
"remove");
419 m_video = document.call<emscripten::val>(
"createElement", std::string(
"video"));
422 m_video.call<
void>(
"setAttribute", std::string(
"class"),
424 : std::string(
"Video")));
425 m_video.set(
"data-qvideocontext",
426 emscripten::val(
quintptr(
reinterpret_cast<void *
>(
this))));
429 m_video.set(
"preload",
"metadata");
435 std::string originString =
"anonymous";
439 m_video.call<
void>(
"setAttribute", std::string(
"crossorigin"), originString);
440 body.call<
void>(
"appendChild", m_video);
443 document.call<emscripten::val>(
"createElement",
449 emscripten::val style = m_video[
"style"];
450 style.set(
"position",
"absolute");
451 style.set(
"display",
"none");
468 emscripten::val document = emscripten::val::global(
"document");
472 m_offscreen = emscripten::val::global(
"OffscreenCanvas");
474 if (m_offscreen.isUndefined()) {
476 m_offscreen = document.call<emscripten::val>(
"createElement", std::string(
"canvas"));
478 m_offscreen.set(
"style",
479 "position:absolute;left:-1000px;top:-1000px");
480 m_offscreen.set(
"width", offscreenSize.width());
481 m_offscreen.set(
"height", offscreenSize.height());
482 m_offscreenContext = m_offscreen.call<emscripten::val>(
"getContext", std::string(
"2d"));
484 m_offscreen = emscripten::val::global(
"OffscreenCanvas")
485 .new_(offscreenSize.width(), offscreenSize.height());
486 emscripten::val offscreenAttributes = emscripten::val::array();
487 offscreenAttributes.set(
"willReadFrequently",
true);
488 m_offscreenContext = m_offscreen.call<emscripten::val>(
"getContext", std::string(
"2d"),
489 offscreenAttributes);
492 m_offscreen.set(
"id", offscreenId.c_str());
501 auto timeUpdateCallback = [=](emscripten::val
event) {
502 qCDebug(qWasmMediaVideoOutput) <<
"timeupdate";
510 auto playCallback = [=](emscripten::val
event) {
512 qCDebug(qWasmMediaVideoOutput) <<
"play" << m_video[
"src"].as<std::string>();
519 auto endedCallback = [=](emscripten::val
event) {
521 qCDebug(qWasmMediaVideoOutput) <<
"ended";
530 auto durationChangeCallback = [=](emscripten::val
event) {
531 qCDebug(qWasmMediaVideoOutput) <<
"durationChange";
534 qint64 dur =
event[
"target"][
"duration"].as<
double>() * 1000;
537 m_durationChangeEvent.
reset(
541 auto loadedDataCallback = [=](emscripten::val
event) {
543 qCDebug(qWasmMediaVideoOutput) <<
"loaded data";
550 auto errorCallback = [=](emscripten::val
event) {
551 qCDebug(qWasmMediaVideoOutput) <<
"error";
560 auto resizeCallback = [=](emscripten::val
event) {
562 qCDebug(qWasmMediaVideoOutput) <<
"resize";
565 QRect(0, 0, m_video[
"videoWidth"].as<int>(), m_video[
"videoHeight"].as<int>()));
566 emit sizeChange(m_video[
"videoWidth"].as<int>(), m_video[
"videoHeight"].as<int>());
572 auto loadedMetadataCallback = [=](emscripten::val
event) {
574 qCDebug(qWasmMediaVideoOutput) <<
"loaded meta data";
578 m_loadedMetadataChangeEvent.
reset(
582 auto loadStartCallback = [=](emscripten::val
event) {
584 qCDebug(qWasmMediaVideoOutput) <<
"load started";
587 m_shouldStop =
false;
593 auto canPlayCallback = [=](emscripten::val
event) {
594 if (
event.isUndefined() ||
event.isNull())
596 qCDebug(qWasmMediaVideoOutput) <<
"can play"
597 <<
"m_requestedPosition" << m_requestedPosition;
605 auto canPlayThroughCallback = [=](emscripten::val
event) {
607 qCDebug(qWasmMediaVideoOutput) <<
"can play through"
608 <<
"m_shouldStop" << m_shouldStop;
612 if (!m_isSeeking && !m_shouldStop) {
613 emscripten::val timeRanges = m_video[
"buffered"];
614 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
615 && timeRanges[
"length"].as<
int>() == 1) {
616 double buffered = m_video[
"buffered"].call<emscripten::val>(
"end", 0).as<double>();
617 const double duration = m_video[
"duration"].as<
double>();
619 if (duration == buffered) {
620 m_currentBufferedValue = 100;
626 if (m_hasVideoFrame) {
627 m_video.call<emscripten::val>(
"requestVideoFrameCallback",
628 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
633 m_shouldStop =
false;
636 m_canPlayThroughChangeEvent.
reset(
640 auto seekingCallback = [=](emscripten::val
event) {
643 <<
"seeking started" << (m_video[
"currentTime"].as<
double>() * 1000);
649 auto seekedCallback = [=](emscripten::val
event) {
651 qCDebug(qWasmMediaVideoOutput) <<
"seeked" << (m_video[
"currentTime"].as<
double>() * 1000);
658 auto emptiedCallback = [=](emscripten::val
event) {
660 qCDebug(qWasmMediaVideoOutput) <<
"emptied";
668 auto stalledCallback = [=](emscripten::val
event) {
670 qCDebug(qWasmMediaVideoOutput) <<
"stalled";
677 auto waitingCallback = [=](emscripten::val
event) {
680 qCDebug(qWasmMediaVideoOutput) <<
"waiting";
688 auto playingCallback = [=](emscripten::val
event) {
690 qCDebug(qWasmMediaVideoOutput) <<
"playing";
694 if (m_toBePaused || !m_shouldStop) {
695 m_toBePaused =
false;
697 if (m_hasVideoFrame) {
698 m_video.call<emscripten::val>(
"requestVideoFrameCallback",
699 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
708 auto progesssCallback = [=](emscripten::val
event) {
709 if (
event.isUndefined() ||
event.isNull())
712 const double duration =
event[
"target"][
"duration"].as<
double>();
716 emscripten::val timeRanges =
event[
"target"][
"buffered"];
718 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
719 && timeRanges[
"length"].as<int>() == 1) {
720 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
721 if (!dVal.isNull() || !dVal.isUndefined()) {
722 double bufferedEnd = dVal.as<
double>();
724 if (duration > 0 && bufferedEnd > 0) {
725 const double bufferedValue = (bufferedEnd / duration * 100);
726 qCDebug(qWasmMediaVideoOutput) <<
"progress buffered";
727 m_currentBufferedValue = bufferedValue;
729 if (bufferedEnd == duration)
741 auto pauseCallback = [=](emscripten::val
event) {
743 qCDebug(qWasmMediaVideoOutput) <<
"pause";
745 const double currentTime = m_video[
"currentTime"].as<
double>();
746 const double duration = m_video[
"duration"].as<
double>();
751 m_video.set(
"currentTime", emscripten::val(0));
760 emscripten::val
window = emscripten::val::global(
"window");
761 window.call<
void>(
"addEventListener", std::string(
"beforeunload"),
762 emscripten::val::module_property(
"mbeforeUnload"));
768 QRect m_videoElementSource(windowGeometry.
topLeft(), windowGeometry.
size());
770 emscripten::val style = m_video[
"style"];
771 style.set(
"left",
QString(
"%1px").
arg(m_videoElementSource.
left()).toStdString());
772 style.set(
"top",
QString(
"%1px").
arg(m_videoElementSource.
top()).toStdString());
773 style.set(
"width",
QString(
"%1px").
arg(m_videoElementSource.
width()).toStdString());
774 style.set(
"height",
QString(
"%1px").
arg(m_videoElementSource.
height()).toStdString());
775 style.set(
"z-index",
"999");
777 if (!m_hasVideoFrame) {
779 m_offscreen.set(
"width", m_videoElementSource.
width());
780 m_offscreen.set(
"height", m_videoElementSource.
height());
789 if (m_video.isUndefined() || m_video.isNull())
791 return m_video[
"duration"].as<
double>() * 1000;
801 m_video.set(
"playbackRate", emscripten::val(
rate));
806 return (m_video.isUndefined() || m_video.isNull()) ? 0 : m_video[
"playbackRate"].as<
float>();
809void QWasmVideoOutput::checkNetworkState()
811 int netState = m_video[
"networkState"].as<
int>();
813 qCDebug(qWasmMediaVideoOutput) << netState;
828void QWasmVideoOutput::videoComputeFrame(
void *
context)
830 if (m_offscreenContext.isUndefined() || m_offscreenContext.isNull()) {
831 qCDebug(qWasmMediaVideoOutput) <<
"offscreen canvas context could not be found";
834 emscripten::val
document = emscripten::val::global(
"document");
836 emscripten::val videoElement =
839 if (videoElement.isUndefined() || videoElement.isNull()) {
840 qCDebug(qWasmMediaVideoOutput) <<
"video element could not be found";
844 const int videoWidth = videoElement[
"videoWidth"].as<
int>();
845 const int videoHeight = videoElement[
"videoHeight"].as<
int>();
847 if (videoWidth == 0 || videoHeight == 0)
850 m_offscreenContext.call<
void>(
"drawImage", videoElement, 0, 0, videoWidth, videoHeight);
852 emscripten::val
frame =
853 m_offscreenContext.call<emscripten::val>(
"getImageData", 0, 0, videoWidth, videoHeight);
855 const QSize frameBytesAllocationSize(videoWidth, videoHeight);
871 if (!wasmVideoOutput->m_wasmSink) {
872 qWarning() <<
"ERROR ALERT!! video sink not set";
874 wasmVideoOutput->m_wasmSink->setVideoFrame(vFrame);
883 emscripten::val videoElement =
884 emscripten::val::global(
"document").
885 call<emscripten::val>(
"getElementById",
888 emscripten::val oneVideoFrame = val::global(
"VideoFrame").new_(videoElement);
890 if (oneVideoFrame.isNull() || oneVideoFrame.isUndefined()) {
892 <<
"ERROR" <<
"failed to construct VideoFrame";
895 emscripten::val frameBytesAllocationSize = oneVideoFrame.call<emscripten::val>(
"allocationSize");
897 emscripten::val frameBuffer =
898 emscripten::val::global(
"Uint8Array").new_(frameBytesAllocationSize);
903 copyToCallback.
thenFunc = [wasmVideoOutput, oneVideoFrame, frameBuffer, videoElement]
904 (emscripten::val frameLayout)
906 if (frameLayout.isNull() || frameLayout.isUndefined()) {
907 qCDebug(qWasmMediaVideoOutput) <<
"theres no frameLayout";
913 oneVideoFrame[
"displayHeight"].as<int>());
916 QByteArray frameBytes = QByteArray::fromEcmaUint8Array(frameBuffer);
920 qWarning() <<
"Invalid pixel format";
932 if (!wasmVideoOutput) {
933 qCDebug(qWasmMediaVideoOutput) <<
"ERROR:"
934 <<
"data-qvideocontext not found";
937 if (!wasmVideoOutput->m_wasmSink) {
938 qWarning() <<
"ERROR ALERT!! video sink not set";
941 wasmVideoOutput->m_wasmSink->setVideoFrame(vFrame);
942 oneVideoFrame.call<emscripten::val>(
"close");
944 copyToCallback.catchFunc = [&, wasmVideoOutput, oneVideoFrame, videoElement](emscripten::val
error)
946 qCDebug(qWasmMediaVideoOutput) <<
"Error"
950 oneVideoFrame.call<emscripten::val>(
"close");
951 wasmVideoOutput->stop();
957 videoElement.call<emscripten::val>(
"requestVideoFrameCallback",
958 emscripten::val::module_property(
"qtVideoFrameTimerCallback"));
964 static auto frame = [](
double frameTime,
void *
context) ->
int {
968 emscripten::val document = emscripten::val::global(
"document");
969 emscripten::val videoElement =
970 document.call<emscripten::val>(
"getElementById", std::string(
m_videoSurfaceId));
972 if (videoElement[
"paused"].as<bool>() || videoElement[
"ended"].as<
bool>())
975 videoOutput->videoComputeFrame(
context);
980 emscripten_request_animation_frame_loop(
frame,
this);
987 if (videoFormat ==
"I420")
991 else if (videoFormat ==
"I422")
995 else if (videoFormat ==
"NV12")
997 else if (videoFormat ==
"RGBA")
999 else if (videoFormat ==
"I420")
1001 else if (videoFormat ==
"RGBX")
1003 else if (videoFormat ==
"BGRA")
1005 else if (videoFormat ==
"BGRX")
1014 emscripten::val
stream = m_video[
"srcObject"];
1015 if (!
stream.isUndefined() || !
stream[
"getVideoTracks"].isUndefined()) {
1016 emscripten::val tracks =
stream.call<emscripten::val>(
"getVideoTracks");
1017 if (!tracks.isUndefined()) {
1018 if (tracks[
"length"].as<int>() == 0)
1019 return emscripten::val::undefined();
1021 emscripten::val track = tracks[0];
1022 if (!track.isUndefined()) {
1023 emscripten::val trackCaps = emscripten::val::undefined();
1024 if (!track[
"getCapabilities"].isUndefined())
1025 trackCaps = track.call<emscripten::val>(
"getCapabilities");
1027 trackCaps = track.call<emscripten::val>(
"getSettings");
1029 if (!trackCaps.isUndefined())
1038 return emscripten::val::undefined();
1043 emscripten::val
stream = m_video[
"srcObject"];
1045 ||
stream[
"getVideoTracks"].isUndefined())
1048 emscripten::val tracks =
stream.call<emscripten::val>(
"getVideoTracks");
1049 if (!tracks.isNull() || !tracks.isUndefined()) {
1050 if (tracks[
"length"].as<int>() == 0)
1053 emscripten::val track = tracks[0];
1054 emscripten::val contraint = emscripten::val::object();
1055 contraint.set(std::move(
key),
value);
1056 track.call<emscripten::val>(
"applyConstraints", contraint);
1069#include "moc_qwasmvideooutput_p.cpp"
\inmodule QtCore\reentrant
\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)
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
std::string toStdString() const
Returns a std::string object with the data contained in this QString.
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 updateVideoElementSource(const QString &src)
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 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)
QFuture< QSet< QChar > > set
[10]
QUrl url("example.com")
[constructor-url-reference]
application x qt windows mime
[2]
int strideForWidth(int width) const
std::function< void(emscripten::val)> thenFunc