150 if (m_video.isUndefined() || m_video.isNull()
153 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
157 switch (m_currentVideoMode) {
159 emscripten::val sourceObj = m_video[
"src"];
160 if ((sourceObj.isUndefined() || sourceObj.isNull()) && !m_source.isEmpty()) {
161 m_video.set(
"src", m_source);
164 m_video.call<
void>(
"load");
167 m_video.call<
void>(
"play");
168 emit readyChanged(
true);
172 emscripten::val document = emscripten::val::global(
"document");
173 if (m_video[
"parentNode"].isNull() || m_video[
"parentNode"].isUndefined())
174 document[
"body"].call<
void>(
"appendChild", m_video);
176 if (!m_cameraIsReady) {
177 m_shouldBeStarted =
true;
181 m_connection = connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
183 m_video.set(
"srcObject", m_mediaInputStream->getMediaStream());
185 emscripten::val stream = m_video[
"srcObject"];
186 if (stream.isNull() || stream.isUndefined()) {
187 qCDebug(qWasmMediaVideoOutput) <<
"srcObject ERROR";
188 emit errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
191 emscripten::val videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
192 if (videoTracks.isNull() || videoTracks.isUndefined()) {
193 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks is null";
194 emit errorOccured(QMediaPlayer::ResourceError,
195 QStringLiteral(
"video surface error"));
198 if (videoTracks[
"length"].as<
int>() == 0) {
199 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks count is 0";
200 emit errorOccured(QMediaPlayer::ResourceError,
201 QStringLiteral(
"video surface error"));
204 emscripten::val videoSettings = videoTracks[0].call<emscripten::val>(
"getSettings");
205 if (!videoSettings.isNull() && !videoSettings.isUndefined()) {
206 const int width = videoSettings[
"width"].as<
int>();
207 const int height = videoSettings[
"height"].as<
int>();
208 updateVideoElementGeometry(QRect(0, 0, width, height));
209 if (!videoSettings[
"frameRate"].isUndefined())
210 m_streamFrameRate = videoSettings[
"frameRate"].as<
double>();
214 m_video.call<
void>(
"play");
216 emit readyChanged(
true);
219 m_mediaInputStream->setUseAudio(
false);
220 m_shouldBeStarted =
true;
221 m_mediaInputStream->setVideoConstraints(m_videoResolution, m_minFrameRate, m_maxFrameRate);
222 m_mediaInputStream->setStreamDevice(m_cameraId);
231 m_video.call<
void>(
"play");
573 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO;
577 auto timeUpdateCallback = [=](emscripten::val event) {
578 qCDebug(qWasmMediaVideoOutput) <<
"timeupdate";
581 emit progressChanged(event[
"target"][
"currentTime"].as<
double>() * 1000);
583 m_timeUpdateEvent.reset(
new QWasmEventHandler(m_video,
"timeupdate", timeUpdateCallback));
586 auto playCallback = [=](emscripten::val event) {
588 qCDebug(qWasmMediaVideoOutput) <<
"play" << m_video[
"src"].as<std::string>();
592 m_playEvent.reset(
new QWasmEventHandler(m_video,
"play", playCallback));
595 auto endedCallback = [=](emscripten::val event) {
597 qCDebug(qWasmMediaVideoOutput) <<
"ended";
598 m_currentMediaStatus = MediaStatus::EndOfMedia;
599 emit statusChanged(m_currentMediaStatus);
601 m_endedEvent.reset(
new QWasmEventHandler(m_video,
"ended", endedCallback));
604 auto durationChangeCallback = [=](emscripten::val event) {
605 qCDebug(qWasmMediaVideoOutput) <<
"durationChange";
608 qint64 dur = event[
"target"][
"duration"].as<
double>() * 1000;
609 emit durationChanged(dur);
611 m_durationChangeEvent.reset(
612 new QWasmEventHandler(m_video,
"durationchange", durationChangeCallback));
615 auto loadedDataCallback = [=](emscripten::val event) {
617 qCDebug(qWasmMediaVideoOutput) <<
"loaded data";
622 emit seekableChanged(m_isSeekable);
625 m_loadedDataEvent.reset(
new QWasmEventHandler(m_video,
"loadeddata", loadedDataCallback));
628 auto errorCallback = [=](emscripten::val event) {
629 qCDebug(qWasmMediaVideoOutput) <<
"error";
630 if (event.isUndefined() || event.isNull())
632 emit errorOccured(m_video[
"error"][
"code"].as<
int>(),
633 QString::fromStdString(m_video[
"error"][
"message"].as<std::string>()));
635 m_errorChangeEvent.reset(
new QWasmEventHandler(m_video,
"error", errorCallback));
638 auto resizeCallback = [=](emscripten::val event) {
640 qCDebug(qWasmMediaVideoOutput) <<
"resize";
642 updateVideoElementGeometry(
643 QRect(0, 0, m_video[
"videoWidth"].as<
int>(), m_video[
"videoHeight"].as<
int>()));
644 emit sizeChange(m_video[
"videoWidth"].as<
int>(), m_video[
"videoHeight"].as<
int>());
647 m_resizeChangeEvent.reset(
new QWasmEventHandler(m_video,
"resize", resizeCallback));
650 auto loadedMetadataCallback = [=](emscripten::val event) {
652 qCDebug(qWasmMediaVideoOutput) <<
"loaded meta data";
654 emit metaDataLoaded();
656 m_loadedMetadataChangeEvent.reset(
657 new QWasmEventHandler(m_video,
"loadedmetadata", loadedMetadataCallback));
660 auto loadStartCallback = [=](emscripten::val event) {
662 qCDebug(qWasmMediaVideoOutput) <<
"load started";
663 m_currentMediaStatus = MediaStatus::LoadingMedia;
664 emit statusChanged(m_currentMediaStatus);
667 m_loadStartChangeEvent.reset(
new QWasmEventHandler(m_video,
"loadstart", loadStartCallback));
671 auto canPlayCallback = [=](emscripten::val event) {
672 if (event.isUndefined() || event.isNull())
674 qCDebug(qWasmMediaVideoOutput) <<
"can play"
675 <<
"m_requestedPosition" << m_requestedPosition;
678 emit readyChanged(
true);
680 m_canPlayChangeEvent.reset(
new QWasmEventHandler(m_video,
"canplay", canPlayCallback));
683 auto canPlayThroughCallback = [=](emscripten::val event) {
685 qCDebug(qWasmMediaVideoOutput) <<
"can play through"
686 <<
"m_isStopped" << m_isStopped;
688 if (m_currentMediaStatus == MediaStatus::EndOfMedia)
691 if (m_isSeekable != seekable) {
692 m_isSeekable = seekable;
693 emit seekableChanged(m_isSeekable);
695 if (!m_isSeeking && !m_isStopped) {
696 emscripten::val timeRanges = m_video[
"buffered"];
697 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
698 && timeRanges[
"length"].as<
int>() == 1) {
699 double buffered = m_video[
"buffered"].call<emscripten::val>(
"end", 0).as<
double>();
700 const double duration = m_video[
"duration"].as<
double>();
702 if (duration == buffered) {
703 m_currentBufferedValue = 100;
704 emit bufferingChanged(m_currentBufferedValue);
707 constexpr int hasEnoughData = 4;
708 if (m_video[
"readyState"].as<
int>() == hasEnoughData) {
709 m_currentMediaStatus = MediaStatus::LoadedMedia;
710 emit statusChanged(m_currentMediaStatus);
717 m_canPlayThroughChangeEvent.reset(
718 new QWasmEventHandler(m_video,
"canplaythrough", canPlayThroughCallback));
721 auto seekingCallback = [=](emscripten::val event) {
723 qCDebug(qWasmMediaVideoOutput)
724 <<
"seeking started" << (m_video[
"currentTime"].as<
double>() * 1000);
727 m_seekingChangeEvent.reset(
new QWasmEventHandler(m_video,
"seeking", seekingCallback));
730 auto seekedCallback = [=](emscripten::val event) {
732 qCDebug(qWasmMediaVideoOutput) <<
"seeked" << (m_video[
"currentTime"].as<
double>() * 1000);
733 emit progressChanged(m_video[
"currentTime"].as<
double>() * 1000);
736 m_seekedChangeEvent.reset(
new QWasmEventHandler(m_video,
"seeked", seekedCallback));
739 auto emptiedCallback = [=](emscripten::val event) {
741 qCDebug(qWasmMediaVideoOutput) <<
"emptied";
742 emit readyChanged(
false);
743 m_currentMediaStatus = MediaStatus::EndOfMedia;
744 emit statusChanged(m_currentMediaStatus);
746 m_emptiedChangeEvent.reset(
new QWasmEventHandler(m_video,
"emptied", emptiedCallback));
749 auto stalledCallback = [=](emscripten::val event) {
751 qCDebug(qWasmMediaVideoOutput) <<
"stalled";
752 m_currentMediaStatus = MediaStatus::StalledMedia;
753 emit statusChanged(m_currentMediaStatus);
755 m_stalledChangeEvent.reset(
new QWasmEventHandler(m_video,
"stalled", stalledCallback));
758 auto waitingCallback = [=](emscripten::val event) {
761 qCDebug(qWasmMediaVideoOutput) <<
"waiting";
764 m_waitingChangeEvent.reset(
new QWasmEventHandler(m_video,
"waiting", waitingCallback));
769 auto playingCallback = [=](emscripten::val event) {
771 qCDebug(qWasmMediaVideoOutput) <<
"playing";
776 m_toBePaused =
false;
780 m_playingChangeEvent.reset(
new QWasmEventHandler(m_video,
"playing", playingCallback));
783 auto progesssCallback = [=](emscripten::val event) {
784 if (event.isUndefined() || event.isNull())
787 const double duration = event[
"target"][
"duration"].as<
double>();
791 emscripten::val timeRanges = event[
"target"][
"buffered"];
793 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
794 && timeRanges[
"length"].as<
int>() == 1) {
795 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
796 if (!dVal.isNull() || !dVal.isUndefined()) {
797 double bufferedEnd = dVal.as<
double>();
799 if (duration > 0 && bufferedEnd > 0) {
800 const double bufferedValue = (bufferedEnd / duration * 100);
801 qCDebug(qWasmMediaVideoOutput) <<
"progress buffered";
802 m_currentBufferedValue = bufferedValue;
803 emit bufferingChanged(m_currentBufferedValue);
804 if (bufferedEnd == duration)
805 m_currentMediaStatus = MediaStatus::BufferedMedia;
807 m_currentMediaStatus = MediaStatus::BufferingMedia;
808 emit statusChanged(m_currentMediaStatus);
813 m_progressChangeEvent.reset(
new QWasmEventHandler(m_video,
"progress", progesssCallback));
816 auto pauseCallback = [=](emscripten::val event) {
818 qCDebug(qWasmMediaVideoOutput) <<
"pause";
820 const double currentTime = m_video[
"currentTime"].as<
double>();
821 const double duration = m_video[
"duration"].as<
double>();
822 if ((currentTime > 0 && currentTime < duration) && (!m_isStopped)) {
826 m_video.set(
"currentTime", emscripten::val(0));
830 m_pauseChangeEvent.reset(
new QWasmEventHandler(m_video,
"pause", pauseCallback));
835 emscripten::val window = emscripten::val::global(
"window");
837 auto beforeUnloadCallback = [=](emscripten::val event) {
841 m_video.call<
void>(
"removeAttribute", emscripten::val(
"src"));
842 m_video.call<
void>(
"load");
844 m_beforeUnloadEvent.reset(
new QWasmEventHandler(window,
"beforeunload", beforeUnloadCallback));
969 emscripten::val videoElement = videoOutput->currentVideoElement();
975 emscripten::val oneVideoFrame = emscripten::val::take_ownership(
978 return Emval.toHandle(
new VideoFrame(Emval.toValue($0)));
980 return Emval.toHandle(null);
982 }, videoElement.as_handle()));
984 if (oneVideoFrame.isNull() || oneVideoFrame.isUndefined()) {
985 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"VideoFrame not ready yet, skipping";
989 emscripten::val options = emscripten::val::object();
990 emscripten::val rectOptions = emscripten::val::object();
992 int displayWidth = oneVideoFrame[
"displayWidth"].as<
int>();
993 int displayHeight = oneVideoFrame[
"displayHeight"].as<
int>();
995 rectOptions.set(
"width", displayWidth);
996 rectOptions.set(
"height", displayHeight);
997 options.set(
"rect", rectOptions);
999 emscripten::val frameBytesAllocationSize = oneVideoFrame.call<emscripten::val>(
"allocationSize", options);
1000 emscripten::val frameBuffer =
1001 emscripten::val::global(
"Uint8Array").new_(frameBytesAllocationSize);
1003 reinterpret_cast<QWasmVideoOutput*>(videoElement[
"data-qvideocontext"].as<quintptr>());
1005 qstdweb::PromiseCallbacks copyToCallback;
1006 copyToCallback.thenFunc = [
this, wasmVideoOutput, oneVideoFrame, frameBuffer,
1007 displayWidth, displayHeight]
1008 (emscripten::val frameLayout)
1010 if (frameLayout.isNull() || frameLayout.isUndefined()) {
1011 qCDebug(qWasmMediaVideoOutput) <<
"theres no frameLayout";
1016 const QSize frameSize(displayWidth,
1019 QByteArray frameBytes = QByteArray::fromEcmaUint8Array(frameBuffer);
1021 QVideoFrameFormat::PixelFormat pixelFormat = fromJsPixelFormat(oneVideoFrame[
"format"].as<std::string>());
1022 if (pixelFormat == QVideoFrameFormat::Format_Invalid) {
1023 pixelFormat = QVideoFrameFormat::Format_RGBA8888;
1025 QVideoFrameFormat frameFormat = QVideoFrameFormat(frameSize, pixelFormat);
1027 if (m_useCameraRotation)
1028 frameFormat.setRotation(wasmVideoOutput->m_rotateBy);
1029 if (m_streamFrameRate > 0)
1030 frameFormat.setStreamFrameRate(m_streamFrameRate);
1031 auto buffer = std::make_unique<QMemoryVideoBuffer>(
1032 std::move(frameBytes),
1033 frameLayout[0][
"stride"].as<
int>());
1035 QVideoFrame vFrame =
1036 QVideoFramePrivate::createFrame(std::move(buffer), std::move(frameFormat));
1038 if (!wasmVideoOutput) {
1039 qCDebug(qWasmMediaVideoOutput) <<
"ERROR:"
1040 <<
"data-qvideocontext not found";
1043 if (!wasmVideoOutput->m_wasmSink) {
1044 qWarning() <<
"ERROR ALERT!! video sink not set";
1047 wasmVideoOutput->m_wasmSink->setVideoFrame(vFrame);
1048 oneVideoFrame.call<emscripten::val>(
"close");
1050 copyToCallback.catchFunc = [oneVideoFrame](emscripten::val error)
1052 qCDebug(qWasmMediaVideoOutput) <<
"copyTo error"
1053 << QString::fromStdString(error[
"name"].as<std::string>())
1054 << QString::fromStdString(error[
"message"].as<std::string>());
1055 oneVideoFrame.call<emscripten::val>(
"close");
1058 qstdweb::Promise::make(oneVideoFrame, u"copyTo"_s, std::move(copyToCallback), frameBuffer, options);
1114 if (!wasmVideoOutput)
1117 emscripten_webgl_make_context_current(wasmVideoOutput->m_glContextHandle);
1119 GLuint rawTexId = 0;
1120 glGenTextures(1, &rawTexId);
1121 QGlTextureHandle texHandle{ rawTexId };
1123 glBindTexture(GL_TEXTURE_2D, texHandle.get());
1126 em_texImage2DFromVideo(wasmVideoOutput->m_videoSurfaceId.c_str(), &w, &h);
1128 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1131 glBindTexture(GL_TEXTURE_2D, 0);
1133 if (!texHandle || w == 0 || h == 0) {
1134 qCWarning(qWasmMediaVideoOutput) <<
"VideoFrame upload failed";
1138 std::unique_ptr<QHwVideoBuffer> hwBuffer =
1139 std::make_unique<QWasmGLTextureVideoBuffer>(
1140 std::move(texHandle), QSize(w, h),
1141 wasmVideoOutput->m_glContextHandle,
1142 wasmVideoOutput->m_wasmSink ? wasmVideoOutput->m_wasmSink->rhi() :
nullptr);
1144 QVideoFrameFormat frameFormat(QSize(w, h), QVideoFrameFormat::Format_RGBA8888);
1145 if (wasmVideoOutput->m_streamFrameRate > 0)
1146 frameFormat.setStreamFrameRate(wasmVideoOutput->m_streamFrameRate);
1147 QVideoFrame vFrame =
1148 QVideoFramePrivate::createFrame(std::move(hwBuffer), std::move(frameFormat));
1150 wasmVideoOutput->m_wasmSink->setVideoFrame(vFrame);
1161 if (isPlatformiOs()) {
1162 m_useCameraRotation =
true;
1163 emscripten::val stream = m_video[
"srcObject"];
1164 emscripten::val vTraks = stream.call<emscripten::val>(
"getVideoTracks");
1166 if (!vTraks.isUndefined() && vTraks[
"length"].as<
int>() > 0) {
1167 emscripten::val trak = vTraks[0];
1168 emscripten::val settings = trak.call<emscripten::val>(
"getSettings");
1170 if (settings[
"facingMode"].as<std::string>() ==
"user")
1182 static EM_BOOL (*frame)(
double,
void *) = [](
double frameTime,
void *context) -> EM_BOOL {
1184 Q_UNUSED(frameTime);
1186 QWasmVideoOutput *videoOutput =
reinterpret_cast<QWasmVideoOutput *>(context);
1187 if (!videoOutput || videoOutput->m_isStopped) {
1188 qCWarning(qWasmMediaVideoOutput) <<
"frame loop exit: isStopped=" << (videoOutput ? videoOutput->m_isStopped :
true)
1189 <<
"mode=" << (videoOutput ? videoOutput->m_currentVideoMode : -1);
1193 if (videoOutput->m_currentVideoMode == QWasmVideoOutput::VideoDisplay
1194 && videoOutput->m_currentMediaStatus != MediaStatus::LoadedMedia) {
1195 emscripten_request_animation_frame(frame, context);
1199 emscripten::val videoElement = videoOutput->currentVideoElement();
1200 if (videoElement.isNull() || videoElement.isUndefined()) {
1201 qCWarning(qWasmMediaVideoOutput) <<
"frame loop exit: video element null, mode=" << videoOutput->m_currentVideoMode;
1205 if (videoElement[
"paused"].as<
bool>() || videoElement[
"ended"].as<
bool>()
1206 || videoElement[
"readyState"].as<
int>() < 2) {
1207 qCDebug(qWasmMediaVideoOutput) <<
"frame loop waiting: mode=" << videoOutput->m_currentVideoMode
1208 <<
"paused=" << videoElement[
"paused"].as<
bool>()
1209 <<
"ended=" << videoElement[
"ended"].as<
bool>()
1210 <<
"readyState=" << videoElement[
"readyState"].as<
int>();
1211 emscripten_request_animation_frame(frame, context);
1215 qCDebug(qWasmMediaVideoOutput) <<
"frame loop render: mode=" << videoOutput->m_currentVideoMode
1216 <<
"glHandle=" << videoOutput->m_glContextHandle;
1218 if (videoOutput->m_hasVideoFrame) {
1219 if (videoOutput->m_glContextHandle)
1220 videoOutput->webglVideoFrameCallback(context);
1222 videoOutput->videoFrameCallback(context);
1224 videoOutput->videoComputeFrame(context);
1227 emscripten_request_animation_frame(frame, context);
1230 if ((!m_isStopped && m_video[
"className"].as<std::string>() ==
"Camera" && m_cameraIsReady)
1231 || (!m_isStopped && m_currentVideoMode == QWasmVideoOutput::SurfaceCapture)
1233 emscripten_request_animation_frame(frame,
this);