Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qwasmaudiooutput.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qaudiodevice.h>
5#include <qaudiooutput.h>
7
8#include <QMimeDatabase>
9#include <QtCore/qloggingcategory.h>
10#include <QMediaDevices>
11#include <QUrl>
12#include <QFile>
13#include <QMimeDatabase>
14#include <QFileInfo>
15
17
18static Q_LOGGING_CATEGORY(qWasmMediaAudioOutput, "qt.multimedia.wasm.audiooutput")
19
21 : QPlatformAudioOutput(parent)
22{
23}
24
26
28{
29 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
30 device = audioDevice;
31}
32
33void QWasmAudioOutput::setVideoElement(emscripten::val videoElement)
34{
35 m_videoElement = videoElement;
36}
37
38emscripten::val QWasmAudioOutput::videoElement()
39{
40 return m_videoElement;
41}
42
44{
45 emscripten::val realElement = videoElement();
46 if (!realElement.isUndefined()) {
47 realElement.set("muted", muted);
48 return;
49 }
50 if (m_audio.isUndefined() || m_audio.isNull()) {
51 qCDebug(qWasmMediaAudioOutput) << "Error"
52 << "Audio element could not be created";
54 QStringLiteral("Media file could not be opened"));
55 return;
56 }
57 m_audio.set("mute", muted);
58}
59
61{
62 volume = qBound(qreal(0.0), volume, qreal(1.0));
63 emscripten::val realElement = videoElement();
64 if (!realElement.isUndefined()) {
65 realElement.set("volume", volume);
66 return;
67 }
68 if (m_audio.isUndefined() || m_audio.isNull()) {
69 qCDebug(qWasmMediaAudioOutput) << "Error"
70 << "Audio element not available";
72 QStringLiteral("Media file could not be opened"));
73 return;
74 }
75
76 m_audio.set("volume", volume);
77}
78
80{
81 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << url;
82 if (url.isEmpty()) {
83 stop();
84 return;
85 }
86
87 createAudioElement(device.id().toStdString());
88
89 if (m_audio.isUndefined() || m_audio.isNull()) {
90 qCDebug(qWasmMediaAudioOutput) << "Error"
91 << "Audio element could not be created";
93 QStringLiteral("Audio element could not be created"));
94 return;
95 }
96
97 emscripten::val document = emscripten::val::global("document");
98 emscripten::val body = document["body"];
99
100 m_audio.set("id", device.id().toStdString());
101
102 body.call<void>("appendChild", m_audio);
103
104
105 if (url.isLocalFile()) { // is localfile
106 qCDebug(qWasmMediaAudioOutput) << "is localfile";
107 m_source = url.toLocalFile();
108
109 QFile mediaFile(m_source);
110 if (!mediaFile.open(QIODevice::ReadOnly)) {
111 qCDebug(qWasmMediaAudioOutput) << "Error"
112 << "Media file could not be opened";
114 QStringLiteral("Media file could not be opened"));
115 return;
116 }
117
118 // local files are relatively small due to browser filesystem being restricted
119 QByteArray content = mediaFile.readAll();
120
122 qCDebug(qWasmMediaAudioOutput) << db.mimeTypeForData(content).name();
123
124 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content.constData(), content.size());
125 emscripten::val contentUrl =
126 qstdweb::window()["URL"].call<emscripten::val>("createObjectURL", contentBlob.val());
127
128 emscripten::val audioSourceElement =
129 document.call<emscripten::val>("createElement", std::string("source"));
130
131 audioSourceElement.set("src", contentUrl);
132
133 // work around Safari not being able to read audio from blob URLs.
134 QFileInfo info(m_source);
136
137 audioSourceElement.set("type", mimeType.name().toStdString());
138 m_audio.call<void>("appendChild", audioSourceElement);
139
140 m_audio.call<void>("setAttribute", emscripten::val("srcObject"), contentUrl);
141
142 } else {
143 m_source = url.toString();
144 m_audio.set("src", m_source.toStdString());
145 }
146 m_audio.set("id", device.id().toStdString());
147
148 body.call<void>("appendChild", m_audio);
149 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
150
151 doElementCallbacks();
152}
153
155{
156 m_audioIODevice = stream;
157}
158
160{
161 if (m_audio.isNull() || m_audio.isUndefined()) {
162 qCDebug(qWasmMediaAudioOutput) << "audio failed to start";
164 QStringLiteral("Audio element resource error"));
165 return;
166 }
167
168 m_audio.call<void>("play");
169}
170
172{
173 if (m_audio.isNull() || m_audio.isUndefined()) {
174 qCDebug(qWasmMediaAudioOutput) << "audio failed to start";
176 QStringLiteral("Audio element resource error"));
177 return;
178 }
179 if (!m_source.isEmpty()) {
180 pause();
181 m_audio.set("currentTime", emscripten::val(0));
182 }
183 if (m_audioIODevice) {
184 m_audioIODevice->close();
185 delete m_audioIODevice;
186 m_audioIODevice = 0;
187 }
188}
189
191{
192 if (m_audio.isNull() || m_audio.isUndefined()) {
193 qCDebug(qWasmMediaAudioOutput) << "audio failed to start";
195 QStringLiteral("Audio element resource error"));
196 return;
197 }
198 m_audio.call<emscripten::val>("pause");
199}
200
201void QWasmAudioOutput::createAudioElement(const std::string &id)
202{
203 emscripten::val document = emscripten::val::global("document");
204 m_audio = document.call<emscripten::val>("createElement", std::string("audio"));
205
206 // only works in chrome and firefox.
207 // Firefox this feature is behind media.setsinkid.enabled preferences
208 // allows user to choose audio output device
209
210 if (!m_audio.hasOwnProperty("sinkId") || m_audio["sinkId"].isUndefined()) {
211 return;
212 }
213
214 std::string usableId = id;
215 if (usableId.empty())
217
218 qstdweb::PromiseCallbacks sinkIdCallbacks{
219 .thenFunc = [](emscripten::val) { qCWarning(qWasmMediaAudioOutput) << "setSinkId ok"; },
220 .catchFunc =
221 [](emscripten::val) {
222 qCWarning(qWasmMediaAudioOutput) << "Error while trying to setSinkId";
223 }
224 };
225 qstdweb::Promise::make(m_audio, "setSinkId", std::move(sinkIdCallbacks), std::move(usableId));
226
227 m_audio.set("id", usableId.c_str());
228}
229
230void QWasmAudioOutput::doElementCallbacks()
231{
232 // error
233 auto errorCallback = [&](emscripten::val event) {
234 qCDebug(qWasmMediaAudioOutput) << "error";
235 if (event.isUndefined() || event.isNull())
236 return;
237 emit errorOccured(m_audio["error"]["code"].as<int>(),
238 QString::fromStdString(m_audio["error"]["message"].as<std::string>()));
239
241 QString::fromStdString(m_audio["error"]["message"].as<std::string>());
242 if (errorMessage.isEmpty()) {
243 switch (m_audio["error"]["code"].as<int>()) {
245 errorMessage = QStringLiteral("aborted by the user agent at the user's request.");
246 break;
248 errorMessage = QStringLiteral("network error.");
249 break;
251 errorMessage = QStringLiteral("decoding error.");
252 break;
254 errorMessage = QStringLiteral("src attribute not suitable.");
255 break;
256 };
257 }
258 qCDebug(qWasmMediaAudioOutput) << m_audio["error"]["code"].as<int>() << errorMessage;
259
260 emit errorOccured(m_audio["error"]["code"].as<int>(), errorMessage);
261 };
262 m_errorChangeEvent.reset(new qstdweb::EventCallback(m_audio, "error", errorCallback));
263
264 // loadeddata
265 auto loadedDataCallback = [&](emscripten::val event) {
267 qCDebug(qWasmMediaAudioOutput) << "loaded data";
268 qstdweb::window()["URL"].call<emscripten::val>("revokeObjectURL", m_audio["src"]);
269 };
270 m_loadedDataEvent.reset(new qstdweb::EventCallback(m_audio, "loadeddata", loadedDataCallback));
271
272 // canplay
273 auto canPlayCallback = [&](emscripten::val event) {
274 if (event.isUndefined() || event.isNull())
275 return;
276 qCDebug(qWasmMediaAudioOutput) << "can play";
277 emit readyChanged(true);
279 };
280 m_canPlayChangeEvent.reset(new qstdweb::EventCallback(m_audio, "canplay", canPlayCallback));
281
282 // canplaythrough
283 auto canPlayThroughCallback = [&](emscripten::val event) {
286 };
287 m_canPlayThroughChangeEvent.reset(
288 new qstdweb::EventCallback(m_audio, "canplaythrough", canPlayThroughCallback));
289
290 // play
291 auto playCallback = [&](emscripten::val event) {
293 qCDebug(qWasmMediaAudioOutput) << "play";
295 };
296 m_playEvent.reset(new qstdweb::EventCallback(m_audio, "play", playCallback));
297
298 // durationchange
299 auto durationChangeCallback = [&](emscripten::val event) {
300 qCDebug(qWasmMediaAudioOutput) << "durationChange";
301
302 // duration in ms
303 emit durationChanged(event["target"]["duration"].as<double>() * 1000);
304 };
305 m_durationChangeEvent.reset(
306 new qstdweb::EventCallback(m_audio, "durationchange", durationChangeCallback));
307
308 // ended
309 auto endedCallback = [&](emscripten::val event) {
311 qCDebug(qWasmMediaAudioOutput) << "ended";
312 m_currentMediaStatus = QMediaPlayer::EndOfMedia;
313 emit statusChanged(m_currentMediaStatus);
314 };
315 m_endedEvent.reset(new qstdweb::EventCallback(m_audio, "ended", endedCallback));
316
317 // progress (buffering progress)
318 auto progesssCallback = [&](emscripten::val event) {
319 if (event.isUndefined() || event.isNull())
320 return;
321 qCDebug(qWasmMediaAudioOutput) << "progress";
322 float duration = event["target"]["duration"].as<int>();
323 if (duration < 0) // track not exactly ready yet
324 return;
325
326 emscripten::val timeRanges = event["target"]["buffered"];
327
328 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
329 && timeRanges["length"].as<int>() == 1) {
330 emscripten::val dVal = timeRanges.call<emscripten::val>("end", 0);
331
332 if (!dVal.isNull() || !dVal.isUndefined()) {
333 double bufferedEnd = dVal.as<double>();
334
335 if (duration > 0 && bufferedEnd > 0) {
336 float bufferedValue = (bufferedEnd / duration * 100);
337 qCDebug(qWasmMediaAudioOutput) << "progress buffered" << bufferedValue;
338
339 emit bufferingChanged(m_currentBufferedValue);
340 if (bufferedEnd == duration)
341 m_currentMediaStatus = QMediaPlayer::BufferedMedia;
342 else
343 m_currentMediaStatus = QMediaPlayer::BufferingMedia;
344
345 emit statusChanged(m_currentMediaStatus);
346 }
347 }
348 }
349 };
350 m_progressChangeEvent.reset(new qstdweb::EventCallback(m_audio, "progress", progesssCallback));
351
352 // timupdate
353 auto timeUpdateCallback = [&](emscripten::val event) {
354 qCDebug(qWasmMediaAudioOutput)
355 << "timeupdate" << (event["target"]["currentTime"].as<double>() * 1000);
356
357 // qt progress is ms
358 emit progressChanged(event["target"]["currentTime"].as<double>() * 1000);
359 };
360 m_timeUpdateEvent.reset(new qstdweb::EventCallback(m_audio, "timeupdate", timeUpdateCallback));
361
362 // pause
363 auto pauseCallback = [&](emscripten::val event) {
365 qCDebug(qWasmMediaAudioOutput) << "pause";
366
367 int currentTime = m_audio["currentTime"].as<int>(); // in seconds
368 int duration = m_audio["duration"].as<int>(); // in seconds
369 if ((currentTime > 0 && currentTime < duration)) {
371 } else {
373 }
374 };
375 m_pauseChangeEvent.reset(new qstdweb::EventCallback(m_audio, "pause", pauseCallback));
376}
377
The QAudioDevice class provides an information about audio devices and their functionality.
QByteArray id
\qmlproperty string QtMultimedia::audioDevice::id
\qmltype AudioOutput \instantiates QAudioOutput
\inmodule QtCore
Definition qbytearray.h:57
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
std::string toStdString() const
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
\inmodule QtCore
Definition qfile.h:93
\inmodule QtCore \reentrant
Definition qiodevice.h:34
virtual void close()
First emits aboutToClose(), then closes the device and sets its OpenMode to NotOpen.
QAudioDevice defaultAudioOutput
\qmlproperty audioDevice QtMultimedia::MediaDevices::defaultAudioOutput Returns the default audio out...
\inmodule QtCore
QMimeType mimeTypeForData(const QByteArray &data) const
Returns a MIME type for data.
QMimeType mimeTypeForFile(const QString &fileName, MatchMode mode=MatchDefault) const
Returns a MIME type for the file named fileName using mode.
\inmodule QtCore
Definition qmimetype.h:25
QString name
the name of the MIME type
Definition qmimetype.h:29
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.
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromStdString(const std::string &s)
Definition qstring.h:1447
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
std::string toStdString() const
Returns a std::string object with the data contained in this QString.
Definition qstring.h:1444
\inmodule QtCore
Definition qurl.h:94
bool isLocalFile() const
Definition qurl.cpp:3445
bool isEmpty() const
Returns true if the URL has no data; otherwise returns false.
Definition qurl.cpp:1896
QString toString(FormattingOptions options=FormattingOptions(PrettyDecoded)) const
Returns a string representation of the URL.
Definition qurl.cpp:2831
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3425
void progressChanged(qint32 position)
void bufferingChanged(qint32 percent)
void errorOccured(qint32 code, const QString &message)
void setMuted(bool muted) override
void stateChanged(QWasmMediaPlayer::QWasmMediaPlayerState newState)
void statusChanged(QMediaPlayer::MediaStatus status)
void setVideoElement(emscripten::val videoElement)
void readyChanged(bool)
void durationChanged(qint64 duration)
void setVolume(float volume) override
void setAudioDevice(const QAudioDevice &device) final
void setSource(const QUrl &url)
static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType)
Definition qstdweb.cpp:425
Combined button and popup list for selecting options.
void make(emscripten::val target, QString methodName, PromiseCallbacks callbacks, Args... args)
Definition qstdweb_p.h:221
emscripten::val window()
Definition qstdweb_p.h:278
#define Q_FUNC_INFO
EGLStreamKHR stream
const char * mimeType
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
GLenum GLuint id
[7]
struct _cl_event * event
#define QStringLiteral(str)
#define emit
#define Q_UNUSED(x)
double qreal
Definition qtypes.h:187
static QString errorMessage(QUrlPrivate::ErrorCode errorCode, const QString &errorSource, qsizetype errorPosition)
Definition qurl.cpp:3517
static double currentTime()
QUrl url("example.com")
[constructor-url-reference]
QMimeDatabase db
[0]
QHostInfo info
[0]
std::function< void(emscripten::val)> thenFunc
Definition qstdweb_p.h:212