6#include <QtMultimedia/qmediaplayer.h>
7#include <QtMultimedia/qvideoframe.h>
8#include <QtMultimedia/qvideosink.h>
9#include <QtMultimedia/private/qplatformvideosink_p.h>
10#include <QtGui/qguiapplication.h>
11#include <QtGui/qopenglcontext.h>
12#include <QtGui/qpa/qplatformwindow_p.h>
13#include <QtGui/rhi/qrhi_platform.h>
14#include <QtCore/qbuffer.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qfile.h>
17#include <QtCore/qmimedatabase.h>
18#include <QtCore/qpoint.h>
19#include <QtCore/qpointer.h>
20#include <QtCore/qrect.h>
21#include <QtCore/qtimer.h>
22#include <QtCore/qurl.h>
23#include <QtCore/private/qstdweb_p.h>
25#include <emscripten/bind.h>
26#include <emscripten/val.h>
33using namespace emscripten;
34using namespace Qt::Literals;
40 emscripten::val platformObject = emscripten::val::global(
"navigator")[
"platform"];
41 if (platformObject.call<
bool>(
"includes", emscripten::val(
"iPhone"))
42 || platformObject.call<
bool>(
"includes", emscripten::val(
"iPad")))
48 : QObject{ parent }, m_frameGrabber(
this)
50 if (isPlatformiOs()) {
54 emscripten_set_orientationchange_callback(
this,
false, &QWasmVideoOutput::orientationchangeCallback);
60 if (m_mediaInputStream) {
61 if (m_streamStarted) {
62 m_streamStarted =
false;
63 m_mediaInputStream->unregisterConsumer();
65 JsMediaInputStream::releaseInstance(m_cameraId);
71 if (m_pendingVideoSize == newSize)
74 m_pendingVideoSize = newSize;
75 updateVideoElementGeometry(QRect(0, 0, m_pendingVideoSize.width(), m_pendingVideoSize.height()));
80 m_currentVideoMode = mode;
94 emscripten::val promise = m_video.call<emscripten::val>(
"play");
97 if (promise.isUndefined() || promise.isNull() || promise[
"then"].isUndefined())
100 QPointer<QWasmVideoOutput> videoOutput(
this);
101 qstdweb::Promise::adoptPromise(promise, {
102 .catchFunc = [videoOutput](emscripten::val error) {
103 if (!videoOutput || videoOutput->m_isStopped)
108 const auto stringProperty = [&error](
const char *key) {
109 return error[key].isUndefined() || error[key].isNull()
111 : QString::fromStdString(error[key].as<std::string>());
113 const QString errorName = stringProperty(
"name");
114 const QString errorMessage = stringProperty(
"message");
117 const QString errorString =
118 "video.play(): "_L1 + (errorMessage.isEmpty() ? errorName : errorMessage);
124 if (errorName ==
"NotAllowedError"_L1) {
125 qCWarning(qWasmMediaVideoOutput) <<
"video.play() not allowed:" << errorMessage;
126 Q_EMIT videoOutput->readyChanged(
false);
127 Q_EMIT videoOutput->stateChanged(QWasmMediaPlayer::Stopped);
128 Q_EMIT videoOutput->errorOccured(QMediaPlayer::AccessDeniedError, errorString);
135 if (!videoOutput->m_video[
"paused"].as<
bool>()) {
136 qCDebug(qWasmMediaVideoOutput) <<
"video.play() rejected with" << errorName
137 <<
"but playback resumed, ignoring";
141 qCWarning(qWasmMediaVideoOutput) <<
"video.play() rejected:" << errorName
143 Q_EMIT videoOutput->readyChanged(
false);
144 Q_EMIT videoOutput->stateChanged(QWasmMediaPlayer::Stopped);
145 Q_EMIT videoOutput->errorOccured(QMediaPlayer::ResourceError, errorString);
152 if (m_video.isUndefined() || m_video.isNull()
155 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
159 switch (m_currentVideoMode) {
161 emscripten::val sourceObj = m_video[
"src"];
162 if ((sourceObj.isUndefined() || sourceObj.isNull()) && !m_source.isEmpty()) {
163 m_video.set(
"src", m_source);
166 m_video.call<
void>(
"load");
170 Q_EMIT readyChanged(
true);
174 emscripten::val document = emscripten::val::global(
"document");
175 if (m_video[
"parentNode"].isNull() || m_video[
"parentNode"].isUndefined())
176 document[
"body"].call<
void>(
"appendChild", m_video);
178 if (!m_cameraIsReady) {
179 m_shouldBeStarted =
true;
183 m_connection = connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
185 emscripten::val newStream = m_mediaInputStream->getMediaStream();
190 if (m_video[
"srcObject"].equals(newStream)) {
191 Q_EMIT readyChanged(
true);
194 m_video.set(
"srcObject", newStream);
196 emscripten::val stream = m_video[
"srcObject"];
197 if (stream.isNull() || stream.isUndefined()) {
198 qCDebug(qWasmMediaVideoOutput) <<
"srcObject ERROR";
199 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
202 emscripten::val videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
203 if (videoTracks.isNull() || videoTracks.isUndefined()) {
204 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks is null";
205 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
206 QStringLiteral(
"video surface error"));
209 if (videoTracks[
"length"].as<
int>() == 0) {
210 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks count is 0";
211 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
212 QStringLiteral(
"video surface error"));
215 emscripten::val videoSettings = videoTracks[0].call<emscripten::val>(
"getSettings");
216 if (!videoSettings.isNull() && !videoSettings.isUndefined()) {
217 const int width = videoSettings[
"width"].as<
int>();
218 const int height = videoSettings[
"height"].as<
int>();
219 updateVideoElementGeometry(QRect(0, 0, width, height));
220 if (!videoSettings[
"frameRate"].isUndefined())
221 m_streamFrameRate = videoSettings[
"frameRate"].as<
double>();
227 Q_EMIT readyChanged(
true);
230 m_mediaInputStream->setUseAudio(
false);
231 m_shouldBeStarted =
true;
232 m_mediaInputStream->setVideoConstraints(m_videoResolution, m_minFrameRate, m_maxFrameRate);
233 if (!m_streamStarted) {
234 m_streamStarted =
true;
235 m_mediaInputStream->registerConsumer();
237 m_mediaInputStream->setStreamDevice(m_cameraId);
254 qCWarning(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"mode=" << m_currentVideoMode;
255 if (m_video.isUndefined() || m_video.isNull()) {
256 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"Resource error"));
262 emscripten::val stream = m_video[
"srcObject"];
263 if (!stream.isNull() && !stream.isUndefined()) {
264 emscripten::val tracks = stream.call<emscripten::val>(
"getTracks");
265 const int count = tracks[
"length"].as<
int>();
266 for (
int i = 0; i < count; ++i)
267 tracks[i].call<
void>(
"stop");
269 }
else if (m_mediaInputStream && m_streamStarted) {
272 m_streamStarted =
false;
273 m_mediaInputStream->unregisterConsumer();
277 m_video.set(
"srcObject", emscripten::val::null());
278 disconnect(m_connection);
280 m_video.call<
void>(
"remove");
282 m_video.call<
void>(
"pause");
288 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO;
290 if (m_video.isUndefined() || m_video.isNull()) {
292 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
297 m_video.call<
void>(
"pause");
304 m_wasmSink->platformVideoSink()->setVideoFrame(QVideoFrame());
307 m_video.set(
"currentTime", emscripten::val(0));
308 m_video.call<
void>(
"load");
318 if (!surface || surface == m_wasmSink) {
322 m_wasmSink = surface;
327 if (m_video.isUndefined() || m_video.isNull()) {
332 return m_currentMediaStatus == MediaStatus::LoadedMedia;
337 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO << url;
339 m_source = url.toString();
341 if (m_video.isUndefined() || m_video.isNull()) {
349 if (url.isLocalFile()) {
350 QFile localFile(url.toLocalFile());
351 if (localFile.open(QIODevice::ReadOnly)) {
352 setSource(&localFile);
354 qWarning() <<
"Failed to open file";
359 updateVideoElementSource(m_source);
364 m_video.set(
"src", src.toStdString());
365 m_video.call<
void>(
"load");
370 m_cameraIsReady =
false;
371 if (m_mediaInputStream)
372 JsMediaInputStream::releaseInstance(m_cameraId);
373 m_mediaInputStream = JsMediaInputStream::instance(id);
375 m_mediaInputStream->setUseAudio(m_hasAudio);
376 m_mediaInputStream->setUseVideo(
true);
378 connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
380 qCDebug(qWasmMediaVideoOutput) <<
"mediaVideoStreamReady" << m_shouldBeStarted;
382 m_cameraIsReady =
true;
383 if (m_shouldBeStarted) {
385 m_shouldBeStarted =
false;
394 m_videoResolution = resolution;
395 m_minFrameRate = minFrameRate;
396 m_maxFrameRate = maxFrameRate;
401 if (stream->bytesAvailable() == 0) {
402 qWarning() <<
"data not available";
403 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"data not available"));
406 if (m_video.isUndefined() || m_video.isNull()) {
407 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
412 QMimeType mime = db.mimeTypeForData(stream);
414 QByteArray buffer = stream->readAll();
416 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(buffer.data(), buffer.size(), mime.name().toStdString());
418 emscripten::val window = qstdweb::window();
420 if (window[
"safari"].isUndefined()) {
421 emscripten::val contentUrl = window[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
422 m_video.set(
"src", contentUrl);
423 m_source = QString::fromStdString(contentUrl.as<std::string>());
426 m_video.set(
"srcObject", contentBlob.val());
432 volume = qBound(qreal(0.0), volume, qreal(1.0));
433 m_video.set(
"volume", volume);
438 if (m_video.isUndefined() || m_video.isNull()) {
440 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
443 m_video.set(
"muted", muted);
448 return (!m_video.isUndefined() || !m_video.isNull())
449 ? (m_video[
"currentTime"].as<
double>() * 1000)
456 float positionToSetInSeconds =
float(positionMSecs) / 1000;
457 emscripten::val seekableTimeRange = m_video[
"seekable"];
458 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
460 if (seekableTimeRange[
"length"].as<
int>() < 1)
462 if (positionToSetInSeconds
463 >= seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>()
464 && positionToSetInSeconds
465 <= seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>()) {
466 m_requestedPosition = positionToSetInSeconds;
468 m_video.set(
"currentTime", m_requestedPosition);
472 qCDebug(qWasmMediaVideoOutput) <<
"m_requestedPosition" << m_requestedPosition;
477 if (m_video.isUndefined() || m_video.isNull()) {
479 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
483 emscripten::val seekableTimeRange = m_video[
"seekable"];
484 if (seekableTimeRange[
"length"].as<
int>() < 1)
486 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
487 bool isit = !QtPrivate::fuzzyCompare(
488 seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>(),
489 seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>());
497 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
this << id;
500 emscripten::val document = emscripten::val::global(
"document");
501 emscripten::val body = document[
"body"];
504 if (!m_video.isUndefined() && !m_video.isNull())
505 m_video.call<
void>(
"remove");
508 m_video = document.call<emscripten::val>(
"createElement", std::string(
"video"));
510 m_video.set(
"id", m_videoSurfaceId.c_str());
511 m_video.call<
void>(
"setAttribute", std::string(
"class"),
512 (m_currentVideoMode == QWasmVideoOutput::Camera ? std::string(
"Camera")
513 : std::string(
"Video")));
514 m_video.set(
"preload",
"metadata");
520 std::string originString =
"anonymous";
524 m_video.call<
void>(
"setAttribute", std::string(
"crossorigin"), originString);
525 body.call<
void>(
"appendChild", m_video);
528 document.call<emscripten::val>(
"createElement",
529 std::string(
"source")).set(
"src", m_source.toStdString());
534 emscripten::val style = m_video[
"style"];
535 style.set(
"position",
"absolute");
536 style.set(
"display",
"none");
538 if (!m_source.isEmpty())
539 updateVideoElementSource(m_source);
544 if (!m_video.isUndefined() && !m_video.isNull())
545 m_video.call<
void>(
"remove");
550 QRect videoElementRect(windowGeometry.topLeft(), windowGeometry.size());
552 emscripten::val style = m_video[
"style"];
553 style.set(
"left", QStringLiteral(
"%1px").arg(videoElementRect.left()).toStdString());
554 style.set(
"top", QStringLiteral(
"%1px").arg(videoElementRect.top()).toStdString());
555 m_video.set(
"width", videoElementRect.width());
556 m_video.set(
"height", videoElementRect.height());
557 style.set(
"z-index",
"999");
565 if (m_video.isUndefined() || m_video.isNull())
567 return m_video[
"duration"].as<
double>() * 1000;
572 m_video.set(
"playbackRate", emscripten::val(rate));
577 return (m_video.isUndefined() || m_video.isNull()) ? 0 : m_video[
"playbackRate"].as<
float>();
582 emscripten::val stream = m_video[
"srcObject"];
583 if ((!stream.isNull() && !stream.isUndefined()) && stream[
"active"].as<
bool>()) {
584 emscripten::val tracks = stream.call<emscripten::val>(
"getVideoTracks");
585 if (!tracks.isUndefined()) {
586 if (tracks[
"length"].as<
int>() == 0)
587 return emscripten::val::undefined();
589 emscripten::val track = tracks[0];
590 if (!track.isUndefined()) {
591 emscripten::val trackCaps = emscripten::val::undefined();
592 if (!track[
"getCapabilities"].isUndefined())
593 trackCaps = track.call<emscripten::val>(
"getCapabilities");
595 trackCaps = track.call<emscripten::val>(
"getSettings");
597 if (!trackCaps.isUndefined())
603 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"capabilities not available"));
606 return emscripten::val::undefined();
611 emscripten::val stream = m_video[
"srcObject"];
612 if (stream.isNull() || stream.isUndefined()
613 || stream[
"getVideoTracks"].isUndefined())
616 emscripten::val tracks = stream.call<emscripten::val>(
"getVideoTracks");
617 if (!tracks.isNull() || !tracks.isUndefined()) {
618 if (tracks[
"length"].as<
int>() == 0)
621 emscripten::val track = tracks[0];
622 emscripten::val contraint = emscripten::val::object();
623 contraint.set(
std::move(key), value);
624 track.call<emscripten::val>(
"applyConstraints", contraint);
633#include "moc_qwasmvideooutput_p.cpp"
void addCameraSourceElement(const std::string &id)
void updateVideoElementGeometry(const QRect &windowGeometry)
bool setDeviceSetting(const std::string &key, emscripten::val value)
emscripten::val surfaceElement()
emscripten::val getDeviceCapabilities()
void setVideoSize(const QSize &)
void setMuted(bool muted)
void setSource(const QUrl &url)
void setVideoMode(QWasmVideoOutput::WasmVideoMode mode)
void setVideoConstraints(QSize resolution, float minFrameRate, float maxFrameRate)
void seekTo(qint64 position)
void orientationChanged(int rotationIndex)
void setVolume(qreal volume)
void createVideoElement(const std::string &id)
void setSurface(QVideoSink *surface)
void removeCurrentVideoElement()
void updateVideoElementSource(const QString &src)
std::string m_videoSurfaceId
void setSource(QIODevice *stream)
qint64 getCurrentPosition()
void setPlaybackRate(qreal rate)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")