4#include <qaudiodevice.h>
5#include <qaudiooutput.h>
6#include <qwasmaudiooutput_p.h>
8#include <QMimeDatabase>
9#include <QtCore/qloggingcategory.h>
10#include <QMediaDevices>
13#include <QMimeDatabase>
18using namespace Qt::Literals;
22QWasmAudioOutput::QWasmAudioOutput(QAudioOutput *parent)
23 : QPlatformAudioOutput(parent)
27QWasmAudioOutput::~QWasmAudioOutput() =
default;
29void QWasmAudioOutput::setAudioDevice(
const QAudioDevice &audioDevice)
31 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
35void QWasmAudioOutput::setVideoElement(emscripten::val videoElement)
37 m_videoElement = videoElement;
40emscripten::val QWasmAudioOutput::videoElement()
42 return m_videoElement;
45void QWasmAudioOutput::setMuted(
bool muted)
47 emscripten::val realElement = videoElement();
48 if (!realElement.isUndefined()) {
49 realElement.set(
"muted", muted);
52 if (m_audio.isUndefined() || m_audio.isNull()) {
53 qCDebug(qWasmMediaAudioOutput) <<
"Error"
54 <<
"Audio element could not be created";
55 emit errorOccured(QMediaPlayer::ResourceError,
56 QStringLiteral(
"Media file could not be opened"));
59 m_audio.set(
"mute", muted);
62void QWasmAudioOutput::setVolume(
float volume)
64 volume = qBound(qreal(0.0), volume, qreal(1.0));
65 emscripten::val realElement = videoElement();
66 if (!realElement.isUndefined()) {
67 realElement.set(
"volume", volume);
70 if (m_audio.isUndefined() || m_audio.isNull()) {
71 qCDebug(qWasmMediaAudioOutput) <<
"Error"
72 <<
"Audio element not available";
73 emit errorOccured(QMediaPlayer::ResourceError,
74 QStringLiteral(
"Media file could not be opened"));
78 m_audio.set(
"volume", volume);
81void QWasmAudioOutput::setSource(
const QUrl &url)
83 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << url;
89 createAudioElement(device.id().toStdString());
91 if (m_audio.isUndefined() || m_audio.isNull()) {
92 qCDebug(qWasmMediaAudioOutput) <<
"Error"
93 <<
"Audio element could not be created";
94 emit errorOccured(QMediaPlayer::ResourceError,
95 QStringLiteral(
"Audio element could not be created"));
99 emscripten::val document = emscripten::val::global(
"document");
100 emscripten::val body = document[
"body"];
102 m_audio.set(
"id", device.id().toStdString());
104 body.call<
void>(
"appendChild", m_audio);
107 if (url.isLocalFile()) {
108 qCDebug(qWasmMediaAudioOutput) <<
"is localfile";
109 m_source = url.toLocalFile();
111 QFile mediaFile(m_source);
112 if (!mediaFile.exists()) {
113 qCDebug(qWasmMediaAudioOutput) <<
"Error"
114 <<
"Media file does not exist";
115 QMetaObject::invokeMethod(
this, &QWasmAudioOutput::errorOccured, Qt::QueuedConnection,
116 QMediaPlayer::ResourceError,
117 QStringLiteral(
"Media file does not exist"));
122 if (!mediaFile.open(QIODevice::ReadOnly)) {
123 qCDebug(qWasmMediaAudioOutput) <<
"Error"
124 <<
"Media file could not be opened";
125 emit errorOccured(QMediaPlayer::ResourceError,
126 QStringLiteral(
"Media file could not be opened"));
131 QByteArray content = mediaFile.readAll();
134 qCDebug(qWasmMediaAudioOutput) << db.mimeTypeForData(content).name();
136 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content.constData(), content.size());
137 emscripten::val contentUrl =
138 qstdweb::window()[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
140 emscripten::val audioSourceElement =
141 document.call<emscripten::val>(
"createElement", std::string(
"source"));
143 audioSourceElement.set(
"src", contentUrl);
146 QFileInfo info(m_source);
147 QMimeType mimeType = db.mimeTypeForFile(info);
149 audioSourceElement.set(
"type", mimeType.name().toStdString());
150 m_audio.call<
void>(
"appendChild", audioSourceElement);
152 m_audio.call<
void>(
"setAttribute", emscripten::val(
"srcObject"), contentUrl);
155 m_source = url.toString();
156 m_audio.set(
"src", m_source.toStdString());
158 m_audio.set(
"id", device.id().toStdString());
160 body.call<
void>(
"appendChild", m_audio);
161 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
163 doElementCallbacks();
166void QWasmAudioOutput::setSource(QIODevice *stream)
168 m_audioIODevice = stream;
171void QWasmAudioOutput::start()
173 if (m_audio.isNull() || m_audio.isUndefined()) {
174 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
175 emit errorOccured(QMediaPlayer::ResourceError,
176 QStringLiteral(
"Audio element resource error"));
180 m_audio.call<
void>(
"play");
183void QWasmAudioOutput::stop()
185 if (m_audio.isNull() || m_audio.isUndefined()) {
186 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
187 emit errorOccured(QMediaPlayer::ResourceError,
188 QStringLiteral(
"Audio element resource error"));
191 if (!m_source.isEmpty()) {
193 m_audio.set(
"currentTime", emscripten::val(0));
195 if (m_audioIODevice) {
196 m_audioIODevice->close();
197 delete m_audioIODevice;
202void QWasmAudioOutput::pause()
204 if (m_audio.isNull() || m_audio.isUndefined()) {
205 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
206 emit errorOccured(QMediaPlayer::ResourceError,
207 QStringLiteral(
"Audio element resource error"));
210 m_audio.call<emscripten::val>(
"pause");
213void QWasmAudioOutput::createAudioElement(
const std::string &id)
215 emscripten::val document = emscripten::val::global(
"document");
216 m_audio = document.call<emscripten::val>(
"createElement", std::string(
"audio"));
218 if (id ==
"System output") {
219 emscripten::val audioContext = emscripten::val::global(
"window")[
"AudioContext"].new_();
220 emscripten::val sourceNode = audioContext.call<emscripten::val>(
"createMediaElementSource", m_audio);
222 sourceNode.call<
void>(
"connect", audioContext[
"destination"]);
233 if (!m_audio.hasOwnProperty(
"sinkId") || m_audio[
"sinkId"].isUndefined()) {
237 std::string usableId = id;
238 if (usableId.empty() || usableId ==
"System output")
239 usableId = QMediaDevices::defaultAudioOutput().id();
241 qstdweb::PromiseCallbacks sinkIdCallbacks{
242 .thenFunc = [](emscripten::val) { qCWarning(qWasmMediaAudioOutput) <<
"setSinkId ok"; },
244 [](emscripten::val) {
245 qCWarning(qWasmMediaAudioOutput) <<
"Error while trying to setSinkId";
248 qstdweb::Promise::make(m_audio, u"setSinkId"_s, std::move(sinkIdCallbacks), std::move(usableId));
250 m_audio.set(
"id", usableId.c_str());
253void QWasmAudioOutput::doElementCallbacks()
256 auto errorCallback = [&](emscripten::val event) {
257 qCDebug(qWasmMediaAudioOutput) <<
"error";
258 if (event.isUndefined() || event.isNull())
260 emit errorOccured(m_audio[
"error"][
"code"].as<
int>(),
261 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>()));
263 QString errorMessage =
264 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>());
265 if (errorMessage.isEmpty()) {
266 switch (m_audio[
"error"][
"code"].as<
int>()) {
267 case AudioElementError::MEDIA_ERR_ABORTED:
268 errorMessage = QStringLiteral(
"aborted by the user agent at the user's request.");
270 case AudioElementError::MEDIA_ERR_NETWORK:
271 errorMessage = QStringLiteral(
"network error.");
273 case AudioElementError::MEDIA_ERR_DECODE:
274 errorMessage = QStringLiteral(
"decoding error.");
276 case AudioElementError::MEDIA_ERR_SRC_NOT_SUPPORTED:
277 errorMessage = QStringLiteral(
"src attribute not suitable.");
281 qCDebug(qWasmMediaAudioOutput) << m_audio[
"error"][
"code"].as<
int>() << errorMessage;
283 emit errorOccured(m_audio[
"error"][
"code"].as<
int>(), errorMessage);
285 m_errorChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"error", errorCallback));
288 auto loadedDataCallback = [&](emscripten::val event) {
290 qCDebug(qWasmMediaAudioOutput) <<
"loaded data";
291 qstdweb::window()[
"URL"].call<emscripten::val>(
"revokeObjectURL", m_audio[
"src"]);
293 m_loadedDataEvent.reset(
new qstdweb::EventCallback(m_audio,
"loadeddata", loadedDataCallback));
296 auto canPlayCallback = [&](emscripten::val event) {
297 if (event.isUndefined() || event.isNull())
299 qCDebug(qWasmMediaAudioOutput) <<
"can play";
300 emit readyChanged(
true);
301 emit stateChanged(QWasmMediaPlayer::Preparing);
303 m_canPlayChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"canplay", canPlayCallback));
306 auto canPlayThroughCallback = [&](emscripten::val event) {
308 emit stateChanged(QWasmMediaPlayer::Prepared);
310 m_canPlayThroughChangeEvent.reset(
311 new qstdweb::EventCallback(m_audio,
"canplaythrough", canPlayThroughCallback));
314 auto playCallback = [&](emscripten::val event) {
316 qCDebug(qWasmMediaAudioOutput) <<
"play";
317 emit stateChanged(QWasmMediaPlayer::Started);
319 m_playEvent.reset(
new qstdweb::EventCallback(m_audio,
"play", playCallback));
322 auto durationChangeCallback = [&](emscripten::val event) {
323 qCDebug(qWasmMediaAudioOutput) <<
"durationChange";
326 emit durationChanged(event[
"target"][
"duration"].as<
double>() * 1000);
328 m_durationChangeEvent.reset(
329 new qstdweb::EventCallback(m_audio,
"durationchange", durationChangeCallback));
332 auto endedCallback = [&](emscripten::val event) {
334 qCDebug(qWasmMediaAudioOutput) <<
"ended";
335 m_currentMediaStatus = QMediaPlayer::EndOfMedia;
336 emit statusChanged(m_currentMediaStatus);
338 m_endedEvent.reset(
new qstdweb::EventCallback(m_audio,
"ended", endedCallback));
341 auto progesssCallback = [&](emscripten::val event) {
342 if (event.isUndefined() || event.isNull())
344 qCDebug(qWasmMediaAudioOutput) <<
"progress";
345 float duration = event[
"target"][
"duration"].as<
int>();
349 emscripten::val timeRanges = event[
"target"][
"buffered"];
351 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
352 && timeRanges[
"length"].as<
int>() == 1) {
353 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
355 if (!dVal.isNull() || !dVal.isUndefined()) {
356 double bufferedEnd = dVal.as<
double>();
358 if (duration > 0 && bufferedEnd > 0) {
359 float bufferedValue = (bufferedEnd / duration * 100);
360 qCDebug(qWasmMediaAudioOutput) <<
"progress buffered" << bufferedValue;
362 emit bufferingChanged(m_currentBufferedValue);
363 if (bufferedEnd == duration)
364 m_currentMediaStatus = QMediaPlayer::BufferedMedia;
366 m_currentMediaStatus = QMediaPlayer::BufferingMedia;
368 emit statusChanged(m_currentMediaStatus);
373 m_progressChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"progress", progesssCallback));
376 auto timeUpdateCallback = [&](emscripten::val event) {
377 qCDebug(qWasmMediaAudioOutput)
378 <<
"timeupdate" << (event[
"target"][
"currentTime"].as<
double>() * 1000);
381 emit progressChanged(event[
"target"][
"currentTime"].as<
double>() * 1000);
383 m_timeUpdateEvent.reset(
new qstdweb::EventCallback(m_audio,
"timeupdate", timeUpdateCallback));
386 auto pauseCallback = [&](emscripten::val event) {
388 qCDebug(qWasmMediaAudioOutput) <<
"pause";
390 int currentTime = m_audio[
"currentTime"].as<
int>();
391 int duration = m_audio[
"duration"].as<
int>();
392 if ((currentTime > 0 && currentTime < duration)) {
393 emit stateChanged(QWasmMediaPlayer::Paused);
395 emit stateChanged(QWasmMediaPlayer::Stopped);
398 m_pauseChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"pause", pauseCallback));
Combined button and popup list for selecting options.
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)