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.open(QIODevice::ReadOnly)) {
113 qCDebug(qWasmMediaAudioOutput) <<
"Error"
114 <<
"Media file could not be opened";
115 emit errorOccured(QMediaPlayer::ResourceError,
116 QStringLiteral(
"Media file could not be opened"));
121 QByteArray content = mediaFile.readAll();
124 qCDebug(qWasmMediaAudioOutput) << db.mimeTypeForData(content).name();
126 qstdweb::Blob contentBlob = qstdweb::Blob::copyFrom(content.constData(), content.size());
127 emscripten::val contentUrl =
128 qstdweb::window()[
"URL"].call<emscripten::val>(
"createObjectURL", contentBlob.val());
130 emscripten::val audioSourceElement =
131 document.call<emscripten::val>(
"createElement", std::string(
"source"));
133 audioSourceElement.set(
"src", contentUrl);
136 QFileInfo info(m_source);
137 QMimeType mimeType = db.mimeTypeForFile(info);
139 audioSourceElement.set(
"type", mimeType.name().toStdString());
140 m_audio.call<
void>(
"appendChild", audioSourceElement);
142 m_audio.call<
void>(
"setAttribute", emscripten::val(
"srcObject"), contentUrl);
145 m_source = url.toString();
146 m_audio.set(
"src", m_source.toStdString());
148 m_audio.set(
"id", device.id().toStdString());
150 body.call<
void>(
"appendChild", m_audio);
151 qCDebug(qWasmMediaAudioOutput) << Q_FUNC_INFO << device.id();
153 doElementCallbacks();
156void QWasmAudioOutput::setSource(QIODevice *stream)
158 m_audioIODevice = stream;
161void QWasmAudioOutput::start()
163 if (m_audio.isNull() || m_audio.isUndefined()) {
164 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
165 emit errorOccured(QMediaPlayer::ResourceError,
166 QStringLiteral(
"Audio element resource error"));
170 m_audio.call<
void>(
"play");
173void QWasmAudioOutput::stop()
175 if (m_audio.isNull() || m_audio.isUndefined()) {
176 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
177 emit errorOccured(QMediaPlayer::ResourceError,
178 QStringLiteral(
"Audio element resource error"));
181 if (!m_source.isEmpty()) {
183 m_audio.set(
"currentTime", emscripten::val(0));
185 if (m_audioIODevice) {
186 m_audioIODevice->close();
187 delete m_audioIODevice;
192void QWasmAudioOutput::pause()
194 if (m_audio.isNull() || m_audio.isUndefined()) {
195 qCDebug(qWasmMediaAudioOutput) <<
"audio failed to start";
196 emit errorOccured(QMediaPlayer::ResourceError,
197 QStringLiteral(
"Audio element resource error"));
200 m_audio.call<emscripten::val>(
"pause");
203void QWasmAudioOutput::createAudioElement(
const std::string &id)
205 emscripten::val document = emscripten::val::global(
"document");
206 m_audio = document.call<emscripten::val>(
"createElement", std::string(
"audio"));
212 if (!m_audio.hasOwnProperty(
"sinkId") || m_audio[
"sinkId"].isUndefined()) {
216 std::string usableId = id;
217 if (usableId.empty())
218 usableId = QMediaDevices::defaultAudioOutput().id();
220 qstdweb::PromiseCallbacks sinkIdCallbacks{
221 .thenFunc = [](emscripten::val) { qCWarning(qWasmMediaAudioOutput) <<
"setSinkId ok"; },
223 [](emscripten::val) {
224 qCWarning(qWasmMediaAudioOutput) <<
"Error while trying to setSinkId";
227 qstdweb::Promise::make(m_audio, u"setSinkId"_s, std::move(sinkIdCallbacks), std::move(usableId));
229 m_audio.set(
"id", usableId.c_str());
232void QWasmAudioOutput::doElementCallbacks()
235 auto errorCallback = [&](emscripten::val event) {
236 qCDebug(qWasmMediaAudioOutput) <<
"error";
237 if (event.isUndefined() || event.isNull())
239 emit errorOccured(m_audio[
"error"][
"code"].as<
int>(),
240 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>()));
242 QString errorMessage =
243 QString::fromStdString(m_audio[
"error"][
"message"].as<std::string>());
244 if (errorMessage.isEmpty()) {
245 switch (m_audio[
"error"][
"code"].as<
int>()) {
246 case AudioElementError::MEDIA_ERR_ABORTED:
247 errorMessage = QStringLiteral(
"aborted by the user agent at the user's request.");
249 case AudioElementError::MEDIA_ERR_NETWORK:
250 errorMessage = QStringLiteral(
"network error.");
252 case AudioElementError::MEDIA_ERR_DECODE:
253 errorMessage = QStringLiteral(
"decoding error.");
255 case AudioElementError::MEDIA_ERR_SRC_NOT_SUPPORTED:
256 errorMessage = QStringLiteral(
"src attribute not suitable.");
260 qCDebug(qWasmMediaAudioOutput) << m_audio[
"error"][
"code"].as<
int>() << errorMessage;
262 emit errorOccured(m_audio[
"error"][
"code"].as<
int>(), errorMessage);
264 m_errorChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"error", errorCallback));
267 auto loadedDataCallback = [&](emscripten::val event) {
269 qCDebug(qWasmMediaAudioOutput) <<
"loaded data";
270 qstdweb::window()[
"URL"].call<emscripten::val>(
"revokeObjectURL", m_audio[
"src"]);
272 m_loadedDataEvent.reset(
new qstdweb::EventCallback(m_audio,
"loadeddata", loadedDataCallback));
275 auto canPlayCallback = [&](emscripten::val event) {
276 if (event.isUndefined() || event.isNull())
278 qCDebug(qWasmMediaAudioOutput) <<
"can play";
279 emit readyChanged(
true);
280 emit stateChanged(QWasmMediaPlayer::Preparing);
282 m_canPlayChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"canplay", canPlayCallback));
285 auto canPlayThroughCallback = [&](emscripten::val event) {
287 emit stateChanged(QWasmMediaPlayer::Prepared);
289 m_canPlayThroughChangeEvent.reset(
290 new qstdweb::EventCallback(m_audio,
"canplaythrough", canPlayThroughCallback));
293 auto playCallback = [&](emscripten::val event) {
295 qCDebug(qWasmMediaAudioOutput) <<
"play";
296 emit stateChanged(QWasmMediaPlayer::Started);
298 m_playEvent.reset(
new qstdweb::EventCallback(m_audio,
"play", playCallback));
301 auto durationChangeCallback = [&](emscripten::val event) {
302 qCDebug(qWasmMediaAudioOutput) <<
"durationChange";
305 emit durationChanged(event[
"target"][
"duration"].as<
double>() * 1000);
307 m_durationChangeEvent.reset(
308 new qstdweb::EventCallback(m_audio,
"durationchange", durationChangeCallback));
311 auto endedCallback = [&](emscripten::val event) {
313 qCDebug(qWasmMediaAudioOutput) <<
"ended";
314 m_currentMediaStatus = QMediaPlayer::EndOfMedia;
315 emit statusChanged(m_currentMediaStatus);
317 m_endedEvent.reset(
new qstdweb::EventCallback(m_audio,
"ended", endedCallback));
320 auto progesssCallback = [&](emscripten::val event) {
321 if (event.isUndefined() || event.isNull())
323 qCDebug(qWasmMediaAudioOutput) <<
"progress";
324 float duration = event[
"target"][
"duration"].as<
int>();
328 emscripten::val timeRanges = event[
"target"][
"buffered"];
330 if ((!timeRanges.isNull() || !timeRanges.isUndefined())
331 && timeRanges[
"length"].as<
int>() == 1) {
332 emscripten::val dVal = timeRanges.call<emscripten::val>(
"end", 0);
334 if (!dVal.isNull() || !dVal.isUndefined()) {
335 double bufferedEnd = dVal.as<
double>();
337 if (duration > 0 && bufferedEnd > 0) {
338 float bufferedValue = (bufferedEnd / duration * 100);
339 qCDebug(qWasmMediaAudioOutput) <<
"progress buffered" << bufferedValue;
341 emit bufferingChanged(m_currentBufferedValue);
342 if (bufferedEnd == duration)
343 m_currentMediaStatus = QMediaPlayer::BufferedMedia;
345 m_currentMediaStatus = QMediaPlayer::BufferingMedia;
347 emit statusChanged(m_currentMediaStatus);
352 m_progressChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"progress", progesssCallback));
355 auto timeUpdateCallback = [&](emscripten::val event) {
356 qCDebug(qWasmMediaAudioOutput)
357 <<
"timeupdate" << (event[
"target"][
"currentTime"].as<
double>() * 1000);
360 emit progressChanged(event[
"target"][
"currentTime"].as<
double>() * 1000);
362 m_timeUpdateEvent.reset(
new qstdweb::EventCallback(m_audio,
"timeupdate", timeUpdateCallback));
365 auto pauseCallback = [&](emscripten::val event) {
367 qCDebug(qWasmMediaAudioOutput) <<
"pause";
369 int currentTime = m_audio[
"currentTime"].as<
int>();
370 int duration = m_audio[
"duration"].as<
int>();
371 if ((currentTime > 0 && currentTime < duration)) {
372 emit stateChanged(QWasmMediaPlayer::Paused);
374 emit stateChanged(QWasmMediaPlayer::Stopped);
377 m_pauseChangeEvent.reset(
new qstdweb::EventCallback(m_audio,
"pause", pauseCallback));
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")