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")))
54 if (isPlatformiOs()) {
58 emscripten_set_orientationchange_callback(
this,
false, &QWasmVideoOutput::orientationchangeCallback);
66 emscripten_set_orientationchange_callback(
nullptr,
false,
nullptr);
70 if (m_mediaInputStream) {
71 if (m_streamStarted) {
72 m_streamStarted =
false;
73 m_mediaInputStream->unregisterConsumer();
75 JsMediaInputStream::releaseInstance(m_cameraId);
79 if (!m_video.isUndefined() && !m_video.isNull()) {
80 m_video.call<
void>(
"pause");
81 m_video.call<
void>(
"remove");
87 if (m_pendingVideoSize == newSize)
90 m_pendingVideoSize = newSize;
91 updateVideoElementGeometry(QRect(0, 0, m_pendingVideoSize.width(), m_pendingVideoSize.height()));
96 m_currentVideoMode = mode;
110 emscripten::val promise = m_video.call<emscripten::val>(
"play");
113 if (promise.isUndefined() || promise.isNull() || promise[
"then"].isUndefined())
116 QPointer<QWasmVideoOutput> videoOutput(
this);
117 qstdweb::Promise::adoptPromise(promise, {
118 .catchFunc = [videoOutput](emscripten::val error) {
119 if (!videoOutput || videoOutput->m_isStopped)
124 const auto stringProperty = [&error](
const char *key) {
125 return error[key].isUndefined() || error[key].isNull()
127 : QString::fromStdString(error[key].as<std::string>());
129 const QString errorName = stringProperty(
"name");
130 const QString errorMessage = stringProperty(
"message");
133 const QString errorString =
134 "video.play(): "_L1 + (errorMessage.isEmpty() ? errorName : errorMessage);
140 if (errorName ==
"NotAllowedError"_L1) {
141 qCWarning(qWasmMediaVideoOutput) <<
"video.play() not allowed:" << errorMessage;
142 Q_EMIT videoOutput->readyChanged(
false);
143 Q_EMIT videoOutput->stateChanged(QWasmMediaPlayer::Stopped);
144 Q_EMIT videoOutput->errorOccured(QMediaPlayer::AccessDeniedError, errorString);
151 if (!videoOutput->m_video[
"paused"].as<
bool>()) {
152 qCDebug(qWasmMediaVideoOutput) <<
"video.play() rejected with" << errorName
153 <<
"but playback resumed, ignoring";
157 qCWarning(qWasmMediaVideoOutput) <<
"video.play() rejected:" << errorName
159 Q_EMIT videoOutput->readyChanged(
false);
160 Q_EMIT videoOutput->stateChanged(QWasmMediaPlayer::Stopped);
161 Q_EMIT videoOutput->errorOccured(QMediaPlayer::ResourceError, errorString);
168 if (m_video.isUndefined() || m_video.isNull()
171 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
175 switch (m_currentVideoMode) {
177 emscripten::val sourceObj = m_video[
"src"];
178 if ((sourceObj.isUndefined() || sourceObj.isNull()) && !m_source.isEmpty()) {
179 m_video.set(
"src", m_source);
182 m_video.call<
void>(
"load");
186 Q_EMIT readyChanged(
true);
190 emscripten::val document = emscripten::val::global(
"document");
191 if (m_video[
"parentNode"].isNull() || m_video[
"parentNode"].isUndefined())
192 document[
"body"].call<
void>(
"appendChild", m_video);
194 if (!m_cameraIsReady) {
195 m_shouldBeStarted =
true;
199 m_connection = connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
201 emscripten::val newStream = m_mediaInputStream->getMediaStream();
206 if (m_video[
"srcObject"].equals(newStream)) {
207 Q_EMIT readyChanged(
true);
210 m_video.set(
"srcObject", newStream);
212 emscripten::val stream = m_video[
"srcObject"];
213 if (stream.isNull() || stream.isUndefined()) {
214 qCDebug(qWasmMediaVideoOutput) <<
"srcObject ERROR";
215 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
218 emscripten::val videoTracks = stream.call<emscripten::val>(
"getVideoTracks");
219 if (videoTracks.isNull() || videoTracks.isUndefined()) {
220 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks is null";
221 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
222 QStringLiteral(
"video surface error"));
225 if (videoTracks[
"length"].as<
int>() == 0) {
226 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"videoTracks count is 0";
227 Q_EMIT errorOccured(QMediaPlayer::ResourceError,
228 QStringLiteral(
"video surface error"));
231 emscripten::val videoSettings = videoTracks[0].call<emscripten::val>(
"getSettings");
232 if (!videoSettings.isNull() && !videoSettings.isUndefined()) {
233 const int width = videoSettings[
"width"].as<
int>();
234 const int height = videoSettings[
"height"].as<
int>();
235 updateVideoElementGeometry(QRect(0, 0, width, height));
236 if (!videoSettings[
"frameRate"].isUndefined())
237 m_streamFrameRate = videoSettings[
"frameRate"].as<
double>();
243 Q_EMIT readyChanged(
true);
246 m_mediaInputStream->setUseAudio(
false);
247 m_shouldBeStarted =
true;
248 m_mediaInputStream->setVideoConstraints(m_videoResolution, m_minFrameRate, m_maxFrameRate);
249 if (!m_streamStarted) {
250 m_streamStarted =
true;
251 m_mediaInputStream->registerConsumer();
253 m_mediaInputStream->setStreamDevice(m_cameraId);
270 qCWarning(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
"mode=" << m_currentVideoMode;
271 if (m_video.isUndefined() || m_video.isNull()) {
272 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"Resource error"));
278 emscripten::val stream = m_video[
"srcObject"];
279 if (!stream.isNull() && !stream.isUndefined()) {
280 emscripten::val tracks = stream.call<emscripten::val>(
"getTracks");
281 const int count = tracks[
"length"].as<
int>();
282 for (
int i = 0; i < count; ++i)
283 tracks[i].call<
void>(
"stop");
285 }
else if (m_mediaInputStream && m_streamStarted) {
288 m_streamStarted =
false;
289 m_mediaInputStream->unregisterConsumer();
293 m_video.set(
"srcObject", emscripten::val::null());
294 disconnect(m_connection);
296 m_video.call<
void>(
"remove");
298 m_video.call<
void>(
"pause");
304 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO;
306 if (m_video.isUndefined() || m_video.isNull()) {
308 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
313 m_video.call<
void>(
"pause");
320 m_wasmSink->platformVideoSink()->setVideoFrame(QVideoFrame());
323 m_video.set(
"currentTime", emscripten::val(0));
324 m_video.call<
void>(
"load");
334 if (!surface || surface == m_wasmSink) {
338 m_wasmSink = surface;
343 if (m_video.isUndefined() || m_video.isNull()) {
348 return m_currentMediaStatus == MediaStatus::LoadedMedia;
353 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO << url;
355 m_source = url.toString();
357 if (m_video.isUndefined() || m_video.isNull()) {
365 if (url.isLocalFile()) {
366 QFile localFile(url.toLocalFile());
367 if (localFile.open(QIODevice::ReadOnly)) {
368 setSource(&localFile);
370 qWarning() <<
"Failed to open file";
375 updateVideoElementSource(m_source);
380 m_video.set(
"src", src.toStdString());
381 m_video.call<
void>(
"load");
386 m_cameraIsReady =
false;
387 if (m_mediaInputStream)
388 JsMediaInputStream::releaseInstance(m_cameraId);
389 m_mediaInputStream = JsMediaInputStream::instance(id);
391 m_mediaInputStream->setUseAudio(m_hasAudio);
392 m_mediaInputStream->setUseVideo(
true);
394 connect(m_mediaInputStream, &JsMediaInputStream::mediaVideoStreamReady,
this,
396 qCDebug(qWasmMediaVideoOutput) <<
"mediaVideoStreamReady" << m_shouldBeStarted;
398 m_cameraIsReady =
true;
399 if (m_shouldBeStarted) {
401 m_shouldBeStarted =
false;
410 m_videoResolution = resolution;
411 m_minFrameRate = minFrameRate;
412 m_maxFrameRate = maxFrameRate;
417 if (stream->bytesAvailable() == 0) {
418 qWarning() <<
"data not available";
419 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"data not available"));
422 if (m_video.isUndefined() || m_video.isNull()) {
423 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
428 QMimeType mime = db.mimeTypeForData(stream);
430 QByteArray buffer = stream->readAll();
432 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(buffer.data(), buffer.size(), mime.name().toStdString());
434 emscripten::val window = qstdweb::window();
436 if (window[
"safari"].isUndefined()) {
437 emscripten::val contentUrl = window[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
438 m_video.set(
"src", contentUrl);
439 m_source = QString::fromStdString(contentUrl.as<std::string>());
442 m_video.set(
"srcObject", contentBlob.val());
448 volume = qBound(qreal(0.0), volume, qreal(1.0));
449 m_video.set(
"volume", volume);
454 if (m_video.isUndefined() || m_video.isNull()) {
456 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
459 m_video.set(
"muted", muted);
464 return (!m_video.isUndefined() || !m_video.isNull())
465 ? (m_video[
"currentTime"].as<
double>() * 1000)
472 float positionToSetInSeconds =
float(positionMSecs) / 1000;
473 emscripten::val seekableTimeRange = m_video[
"seekable"];
474 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
476 if (seekableTimeRange[
"length"].as<
int>() < 1)
478 if (positionToSetInSeconds
479 >= seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>()
480 && positionToSetInSeconds
481 <= seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>()) {
482 m_requestedPosition = positionToSetInSeconds;
484 m_video.set(
"currentTime", m_requestedPosition);
488 qCDebug(qWasmMediaVideoOutput) <<
"m_requestedPosition" << m_requestedPosition;
493 if (m_video.isUndefined() || m_video.isNull()) {
495 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"video surface error"));
499 emscripten::val seekableTimeRange = m_video[
"seekable"];
500 if (seekableTimeRange[
"length"].as<
int>() < 1)
502 if (!seekableTimeRange.isNull() || !seekableTimeRange.isUndefined()) {
503 bool isit = !QtPrivate::fuzzyCompare(
504 seekableTimeRange.call<emscripten::val>(
"start", 0).as<
double>(),
505 seekableTimeRange.call<emscripten::val>(
"end", 0).as<
double>());
513 qCDebug(qWasmMediaVideoOutput) << Q_FUNC_INFO <<
this << id;
516 emscripten::val document = emscripten::val::global(
"document");
517 emscripten::val body = document[
"body"];
520 if (!m_video.isUndefined() && !m_video.isNull())
521 m_video.call<
void>(
"remove");
524 m_video = document.call<emscripten::val>(
"createElement", std::string(
"video"));
526 m_video.set(
"id", m_videoSurfaceId.c_str());
527 m_video.call<
void>(
"setAttribute", std::string(
"class"),
528 (m_currentVideoMode == QWasmVideoOutput::Camera ? std::string(
"Camera")
529 : std::string(
"Video")));
530 m_video.set(
"preload",
"metadata");
536 std::string originString =
"anonymous";
540 m_video.call<
void>(
"setAttribute", std::string(
"crossorigin"), originString);
541 body.call<
void>(
"appendChild", m_video);
546 emscripten::val style = m_video[
"style"];
547 style.set(
"position",
"absolute");
548 style.set(
"display",
"none");
550 if (!m_source.isEmpty())
551 updateVideoElementSource(m_source);
556 if (!m_video.isUndefined() && !m_video.isNull())
557 m_video.call<
void>(
"remove");
562 QRect videoElementRect(windowGeometry.topLeft(), windowGeometry.size());
564 emscripten::val style = m_video[
"style"];
565 style.set(
"left", QStringLiteral(
"%1px").arg(videoElementRect.left()).toStdString());
566 style.set(
"top", QStringLiteral(
"%1px").arg(videoElementRect.top()).toStdString());
567 m_video.set(
"width", videoElementRect.width());
568 m_video.set(
"height", videoElementRect.height());
569 style.set(
"z-index",
"999");
577 if (m_video.isUndefined() || m_video.isNull())
579 return m_video[
"duration"].as<
double>() * 1000;
584 m_video.set(
"playbackRate", emscripten::val(rate));
589 return (m_video.isUndefined() || m_video.isNull()) ? 0 : m_video[
"playbackRate"].as<
float>();
594 emscripten::val stream = m_video[
"srcObject"];
595 if ((!stream.isNull() && !stream.isUndefined()) && stream[
"active"].as<
bool>()) {
596 emscripten::val tracks = stream.call<emscripten::val>(
"getVideoTracks");
597 if (!tracks.isUndefined()) {
598 if (tracks[
"length"].as<
int>() == 0)
599 return emscripten::val::undefined();
601 emscripten::val track = tracks[0];
602 if (!track.isUndefined()) {
603 emscripten::val trackCaps = emscripten::val::undefined();
604 if (!track[
"getCapabilities"].isUndefined())
605 trackCaps = track.call<emscripten::val>(
"getCapabilities");
607 trackCaps = track.call<emscripten::val>(
"getSettings");
609 if (!trackCaps.isUndefined())
615 Q_EMIT errorOccured(QMediaPlayer::ResourceError, QStringLiteral(
"capabilities not available"));
618 return emscripten::val::undefined();
623 emscripten::val stream = m_video[
"srcObject"];
624 if (stream.isNull() || stream.isUndefined()
625 || stream[
"getVideoTracks"].isUndefined())
628 emscripten::val tracks = stream.call<emscripten::val>(
"getVideoTracks");
629 if (!tracks.isNull() || !tracks.isUndefined()) {
630 if (tracks[
"length"].as<
int>() == 0)
633 emscripten::val track = tracks[0];
634 emscripten::val contraint = emscripten::val::object();
635 contraint.set(
std::move(key), value);
636 track.call<emscripten::val>(
"applyConstraints", contraint);
645#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)
QWasmVideoOutput(QObject *parent=nullptr)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
static QWasmVideoOutput * s_orientationCallbackOwner