File indexing completed on 2025-01-05 04:29:50
0001 /** 0002 * SPDX-FileCopyrightText: 2022-2023 Bart De Vries <bart@mogwai.be> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "gstmediabackend.h" 0008 #include "gstmediabackendlogging.h" 0009 #include "gstsignalslogging.h" 0010 0011 #include <QAudio> 0012 #include <QDebug> 0013 #include <QImage> 0014 #include <QString> 0015 #include <QTemporaryDir> 0016 #include <QTimer> 0017 0018 class GstMediaBackendPrivate 0019 { 0020 private: 0021 friend class GstMediaBackend; 0022 0023 const qint64 m_notifyInterval = 500; // interval for position updates (in ms) 0024 0025 KMediaSession *m_kMediaSession = nullptr; 0026 GstElement *m_pipeline = nullptr; 0027 GstElement *m_playbin = nullptr; 0028 GstElement *m_audioBin = nullptr; 0029 GstElement *m_audioSink = nullptr; 0030 GstElement *m_videoBin = nullptr; 0031 GstElement *m_scaleTempo = nullptr; 0032 GstElement *m_audioConvert = nullptr; 0033 GstElement *m_audioResample = nullptr; 0034 GstBus *m_bus = nullptr; 0035 GstMessage *m_msg = nullptr; 0036 GstStateChangeReturn m_ret; 0037 0038 QTimer *m_timer = nullptr; 0039 QUrl m_source; 0040 qint64 m_position = 0; 0041 qint64 m_duration = 0; 0042 qreal m_rate = 1.0; 0043 qreal m_volume = 100; 0044 bool m_muted = false; 0045 bool m_seekable = false; 0046 KMediaSession::MediaStatus m_mediaStatus = KMediaSession::MediaStatus::NoMedia; 0047 KMediaSession::Error m_error = KMediaSession::Error::NoError; 0048 KMediaSession::PlaybackState m_playbackState = KMediaSession::PlaybackState::StoppedState; 0049 std::unique_ptr<QTemporaryDir> m_imageCacheDir = nullptr; 0050 0051 bool m_seekPending = false; 0052 qint64 m_positionBeforeSeek = 0; 0053 qint64 m_positionAfterSeek = 0; 0054 0055 void parseMetaData(GstTagList *tags); 0056 0057 void playerSeekableChanged(GstMediaBackend *backend); 0058 static void playerSignalVolumeChanged(GObject *o, GParamSpec *p, gpointer d); 0059 static void playerSignalMutedChanged(GObject *o, GParamSpec *p, gpointer d); 0060 static void playerSignalPlaybackRateChanged(GObject *o, GParamSpec *p, gpointer d); 0061 0062 static gboolean busCallback(GstBus *, GstMessage *message, gpointer pointer) 0063 { 0064 gst_message_ref(message); 0065 reinterpret_cast<GstMediaBackend *>(pointer)->handleMessage(message); 0066 return TRUE; 0067 }; 0068 }; 0069 0070 GstMediaBackend::GstMediaBackend(QObject *parent) 0071 : AbstractMediaBackend(parent) 0072 , d(std::make_unique<GstMediaBackendPrivate>()) 0073 { 0074 qCDebug(GstMediaBackendLog) << "GstMediaBackend::GstMediaBackend()"; 0075 d->m_kMediaSession = static_cast<KMediaSession *>(parent); 0076 0077 // create timer to get position updates 0078 d->m_timer = new QTimer(this); 0079 connect(d->m_timer, &QTimer::timeout, this, &GstMediaBackend::timerUpdate); 0080 0081 // gstreamer initialization 0082 gst_init(nullptr, nullptr); 0083 0084 // building pipeline 0085 d->m_pipeline = gst_element_factory_make("playbin", "myplaybin"); 0086 d->m_scaleTempo = gst_element_factory_make("scaletempo", "scale_tempo"); 0087 d->m_audioConvert = gst_element_factory_make("audioconvert", "convert"); 0088 d->m_audioSink = gst_element_factory_make("autoaudiosink", "audio_sink"); 0089 if (!d->m_scaleTempo || !d->m_audioConvert || !d->m_audioSink) { 0090 qCDebug(GstMediaBackendLog) << "Not all elements could be created."; 0091 } 0092 0093 /* Create the audio sink bin, add the elements and link them */ 0094 d->m_audioBin = gst_bin_new("audio_sink_bin"); 0095 gst_bin_add_many(GST_BIN(d->m_audioBin), d->m_scaleTempo, d->m_audioConvert, d->m_audioSink, NULL); 0096 gst_element_link_many(d->m_scaleTempo, d->m_audioConvert, d->m_audioSink, nullptr); 0097 GstPad *pad_audio = gst_element_get_static_pad(d->m_scaleTempo, "sink"); 0098 GstPad *ghost_pad_audio = gst_ghost_pad_new("sink", pad_audio); 0099 gst_pad_set_active(ghost_pad_audio, TRUE); 0100 gst_element_add_pad(d->m_audioBin, ghost_pad_audio); 0101 gst_object_unref(pad_audio); 0102 0103 /* Set playbin's audio sink to be our sink bin */ 0104 g_object_set(GST_OBJECT(d->m_pipeline), "audio-sink", d->m_audioBin, nullptr); 0105 0106 // get bus and connect to get messages sent through callbacks 0107 // TODO: implement fallback in case gmainloop support is not available in qt 0108 d->m_bus = gst_element_get_bus(d->m_pipeline); 0109 gst_bus_add_watch_full(d->m_bus, G_PRIORITY_DEFAULT, d->busCallback, this, nullptr); 0110 0111 // connect to other signals 0112 g_signal_connect(G_OBJECT(d->m_pipeline), "notify::volume", G_CALLBACK(GstMediaBackendPrivate::playerSignalVolumeChanged), this); 0113 g_signal_connect(G_OBJECT(d->m_pipeline), "notify::mute", G_CALLBACK(GstMediaBackendPrivate::playerSignalMutedChanged), this); 0114 g_signal_connect(G_OBJECT(d->m_scaleTempo), "notify::rate", G_CALLBACK(GstMediaBackendPrivate::playerSignalPlaybackRateChanged), this); 0115 } 0116 0117 GstMediaBackend::~GstMediaBackend() 0118 { 0119 qCDebug(GstMediaBackendLog) << "GstMediaBackend::~GstMediaBackend()"; 0120 0121 // free memory 0122 if (d->m_msg != nullptr) { 0123 gst_message_unref(d->m_msg); 0124 } 0125 if (d->m_bus != nullptr) { 0126 gst_bus_remove_watch(d->m_bus); 0127 gst_object_unref(d->m_bus); 0128 } 0129 gst_element_set_state(d->m_pipeline, GST_STATE_NULL); 0130 if (d->m_pipeline != nullptr) { 0131 gst_object_unref(d->m_pipeline); 0132 } 0133 } 0134 0135 KMediaSession::MediaBackends GstMediaBackend::backend() const 0136 { 0137 qCDebug(GstMediaBackendLog) << "GstMediaBackend::backend()"; 0138 return KMediaSession::MediaBackends::Gst; 0139 } 0140 0141 bool GstMediaBackend::muted() const 0142 { 0143 qCDebug(GstMediaBackendLog) << "GstMediaBackend::muted()"; 0144 bool isMuted = false; 0145 g_object_get(G_OBJECT(d->m_pipeline), "mute", &isMuted, nullptr); 0146 if (isMuted != d->m_muted) { 0147 d->m_muted = isMuted; 0148 } 0149 return d->m_muted; 0150 } 0151 0152 qreal GstMediaBackend::volume() const 0153 { 0154 qCDebug(GstMediaBackendLog) << "GstMediaBackend::volume()"; 0155 gdouble rawVolume = 1.0; 0156 g_object_get(G_OBJECT(d->m_pipeline), "volume", &rawVolume, nullptr); 0157 qreal volume = static_cast<qreal>(QAudio::convertVolume(rawVolume, QAudio::LinearVolumeScale, QAudio::LogarithmicVolumeScale)) * 100.0; 0158 0159 if (volume < 0.01) { 0160 volume = 100.0; 0161 } 0162 0163 if (abs(volume - d->m_volume) > 0.01) { 0164 d->m_volume = volume; 0165 } 0166 return d->m_volume; 0167 } 0168 0169 QUrl GstMediaBackend::source() const 0170 { 0171 qCDebug(GstMediaBackendLog) << "GstMediaBackend::source()"; 0172 return d->m_source; 0173 } 0174 0175 KMediaSession::MediaStatus GstMediaBackend::mediaStatus() const 0176 { 0177 qCDebug(GstMediaBackendLog) << "GstMediaBackend::mediaStatus()"; 0178 return d->m_mediaStatus; 0179 } 0180 0181 KMediaSession::PlaybackState GstMediaBackend::playbackState() const 0182 { 0183 qCDebug(GstMediaBackendLog) << "GstMediaBackend::playbackState()"; 0184 GstState state, pending_state; 0185 gst_element_get_state(d->m_pipeline, &state, &pending_state, static_cast<GstClockTime>(1e9)); 0186 qCDebug(GstMediaBackendLog) << "Current state is" << gst_element_state_get_name(state); 0187 switch (state) { 0188 case GST_STATE_PLAYING: 0189 if (!d->m_timer->isActive()) { 0190 d->m_timer->start(d->m_notifyInterval); 0191 } 0192 return KMediaSession::PlaybackState::PlayingState; 0193 break; 0194 case GST_STATE_PAUSED: 0195 if (d->m_timer->isActive()) { 0196 d->m_timer->stop(); 0197 } 0198 return KMediaSession::PlaybackState::PausedState; 0199 break; 0200 case GST_STATE_VOID_PENDING: 0201 case GST_STATE_NULL: 0202 case GST_STATE_READY: 0203 default: 0204 if (d->m_timer->isActive()) { 0205 d->m_timer->stop(); 0206 } 0207 d->m_rate = 1.0; 0208 return KMediaSession::PlaybackState::StoppedState; 0209 break; 0210 } 0211 return KMediaSession::PlaybackState::StoppedState; 0212 } 0213 0214 qreal GstMediaBackend::playbackRate() const 0215 { 0216 qCDebug(GstMediaBackendLog) << "GstMediaBackend::playbackRate()"; 0217 0218 return d->m_rate; 0219 } 0220 0221 KMediaSession::Error GstMediaBackend::error() const 0222 { 0223 qCDebug(GstMediaBackendLog) << "GstMediaBackend::error()"; 0224 return d->m_error; 0225 } 0226 0227 qint64 GstMediaBackend::duration() const 0228 { 0229 qCDebug(GstMediaBackendLog) << "GstMediaBackend::duration()"; 0230 gint64 rawDuration = 0; 0231 0232 if (d->m_pipeline) { 0233 gst_element_query_duration(d->m_pipeline, GST_FORMAT_TIME, &rawDuration); 0234 } 0235 d->m_duration = static_cast<qint64>(rawDuration) / 1000000; 0236 qCDebug(GstMediaBackendLog) << "duration: " << d->m_duration; 0237 0238 return d->m_duration; 0239 } 0240 0241 qint64 GstMediaBackend::position() const 0242 { 0243 qCDebug(GstMediaBackendLog) << "GstMediaBackend::position()"; 0244 0245 gint64 position = 0; 0246 0247 if (d->m_pipeline && d->m_playbackState != KMediaSession::PlaybackState::StoppedState) { 0248 gst_element_query_position(d->m_pipeline, GST_FORMAT_TIME, &position); 0249 } 0250 qCDebug(GstMediaBackendLog) << "position:" << position / 1000000; 0251 0252 if (d->m_seekPending) { 0253 if (abs(position / 1000000 - d->m_positionAfterSeek) < 1000) { 0254 d->m_seekPending = false; 0255 d->m_positionBeforeSeek = 0; 0256 d->m_positionAfterSeek = 0; 0257 } else { 0258 qCDebug(GstMediaBackendLog) << "but reporting position:" << d->m_positionBeforeSeek << "due to pending seek"; 0259 return d->m_positionBeforeSeek; 0260 } 0261 } 0262 0263 return position / 1000000; 0264 } 0265 0266 bool GstMediaBackend::seekable() const 0267 { 0268 qCDebug(GstMediaBackendLog) << "GstMediaBackend::seekable()"; 0269 switch (d->m_playbackState) { 0270 case KMediaSession::PlaybackState::PlayingState: 0271 case KMediaSession::PlaybackState::PausedState: 0272 d->m_seekable = true; 0273 break; 0274 case KMediaSession::PlaybackState::StoppedState: 0275 default: 0276 d->m_seekable = false; 0277 break; 0278 } 0279 return d->m_seekable; 0280 } 0281 0282 void GstMediaBackend::setMuted(bool muted) 0283 { 0284 qCDebug(GstMediaBackendLog) << "GstMediaBackend::setMuted(" << muted << ")"; 0285 g_object_set(G_OBJECT(d->m_pipeline), "mute", muted, nullptr); 0286 if (d->m_muted != muted) { 0287 d->m_muted = muted; 0288 QTimer::singleShot(0, this, [this]() { 0289 Q_EMIT mutedChanged(d->m_muted); 0290 }); 0291 } 0292 } 0293 0294 void GstMediaBackend::setVolume(qreal volume) 0295 { 0296 qCDebug(GstMediaBackendLog) << "GstMediaBackend::setVolume(" << volume << ")"; 0297 if (abs(d->m_volume - volume) > 0.01) { 0298 g_object_set(G_OBJECT(d->m_pipeline), 0299 "volume", 0300 static_cast<gdouble>(QAudio::convertVolume(volume / 100.0, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale)), 0301 nullptr); 0302 d->m_volume = volume; 0303 QTimer::singleShot(0, this, [this]() { 0304 Q_EMIT volumeChanged(d->m_volume); 0305 }); 0306 } 0307 } 0308 0309 void GstMediaBackend::setSource(const QUrl &source) 0310 { 0311 qCDebug(GstMediaBackendLog) << "GstMediaBackend::setSource(" << source << ")"; 0312 0313 if (playbackState() != KMediaSession::PlaybackState::StoppedState) { 0314 stop(); 0315 } 0316 0317 gst_element_set_state(d->m_pipeline, GST_STATE_NULL); 0318 d->m_seekPending = false; 0319 d->m_positionBeforeSeek = 0; 0320 d->m_positionAfterSeek = 0; 0321 d->m_duration = 0; 0322 // TODO: restore playbackRate of previous source? 0323 0324 d->m_mediaStatus = KMediaSession::MediaStatus::LoadingMedia; 0325 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0326 0327 g_object_set(G_OBJECT(d->m_pipeline), "uri", source.toEncoded().constData(), nullptr); 0328 0329 if (d->m_error != KMediaSession::Error::NoError) { 0330 d->m_error = KMediaSession::Error::NoError; 0331 Q_EMIT errorChanged(d->m_error); 0332 } 0333 0334 if (source.isEmpty()) { 0335 d->m_mediaStatus = KMediaSession::MediaStatus::NoMedia; 0336 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0337 } else { 0338 d->m_mediaStatus = KMediaSession::MediaStatus::LoadedMedia; 0339 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0340 0341 gst_element_set_state(d->m_pipeline, GST_STATE_PAUSED); 0342 0343 d->m_mediaStatus = KMediaSession::MediaStatus::BufferedMedia; 0344 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0345 } 0346 d->m_source = source; 0347 Q_EMIT sourceChanged(source); 0348 0349 QTimer::singleShot(0, this, [this]() { 0350 Q_EMIT volumeChanged(volume()); 0351 Q_EMIT mutedChanged(muted()); 0352 }); 0353 } 0354 0355 void GstMediaBackend::setPosition(qint64 position) 0356 { 0357 qCDebug(GstMediaBackendLog) << "GstMediaBackend::setPosition(" << position << ")"; 0358 0359 d->m_positionBeforeSeek = this->position(); 0360 d->m_positionAfterSeek = position; 0361 d->m_seekPending = true; 0362 0363 qint64 from = d->m_rate > 0 ? position : 0; 0364 qint64 to = d->m_rate > 0 ? duration() : position; 0365 0366 GstSeekFlags seekFlags = GstSeekFlags(GST_SEEK_FLAG_FLUSH); 0367 0368 gst_element_seek(d->m_pipeline, d->m_rate, GST_FORMAT_TIME, seekFlags, GST_SEEK_TYPE_SET, from * 1000000, GST_SEEK_TYPE_SET, to * 1000000); 0369 0370 qCDebug(GstMediaBackendLog) << "Seeking: " << from << to; 0371 } 0372 0373 void GstMediaBackend::setPlaybackRate(qreal rate) 0374 { 0375 qCDebug(GstMediaBackendLog) << "GstMediaBackend::setPlaybackRate(" << rate << ")"; 0376 qint64 currentPosition = position(); 0377 qint64 from = rate > 0 ? currentPosition : 0; 0378 qint64 to = rate > 0 ? duration() : currentPosition; 0379 gst_element_seek(d->m_pipeline, 0380 rate, 0381 GST_FORMAT_TIME, 0382 GstSeekFlags(GST_SEEK_FLAG_FLUSH), 0383 GST_SEEK_TYPE_SET, 0384 from * 1000000, 0385 GST_SEEK_TYPE_SET, 0386 to * 1000000); 0387 if (!qFuzzyCompare(rate, d->m_rate)) { 0388 d->m_rate = rate; 0389 QTimer::singleShot(0, this, [this]() { 0390 Q_EMIT playbackRateChanged(d->m_rate); 0391 }); 0392 } 0393 } 0394 0395 void GstMediaBackend::pause() 0396 { 0397 qCDebug(GstMediaBackendLog) << "GstMediaBackend::pause()"; 0398 gst_element_set_state(d->m_pipeline, GST_STATE_PAUSED); 0399 d->m_timer->stop(); 0400 } 0401 0402 void GstMediaBackend::play() 0403 { 0404 qCDebug(GstMediaBackendLog) << "GstMediaBackend::play()"; 0405 gst_element_set_state(d->m_pipeline, GST_STATE_PLAYING); 0406 d->m_timer->start(d->m_notifyInterval); 0407 QTimer::singleShot(0, this, [this]() { 0408 d->m_mediaStatus = KMediaSession::MediaStatus::BufferedMedia; 0409 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0410 }); 0411 } 0412 0413 void GstMediaBackend::stop() 0414 { 0415 qCDebug(GstMediaBackendLog) << "GstMediaBackend::stop()"; 0416 d->m_seekPending = false; 0417 d->m_positionBeforeSeek = 0; 0418 d->m_positionAfterSeek = 0; 0419 0420 gst_element_set_state(d->m_pipeline, GST_STATE_READY); 0421 d->m_timer->stop(); 0422 } 0423 0424 void GstMediaBackend::handleMessage(GstMessage *message) 0425 { 0426 qCDebug(GstSignalsLog) << "GstMediaBackend::handleMessage(" << message << ")"; 0427 qCDebug(GstSignalsLog) << "message type" << gst_message_type_get_name(message->type); 0428 if (message->type == GST_MESSAGE_ASYNC_DONE) { 0429 QTimer::singleShot(0, this, [this]() { 0430 Q_EMIT durationChanged(duration()); 0431 }); 0432 } else if (message->type == GST_MESSAGE_STATE_CHANGED) { 0433 GstState rawOldState, rawState, rawPendingState; 0434 gst_message_parse_state_changed(message, &rawOldState, &rawState, &rawPendingState); 0435 KMediaSession::PlaybackState newState; 0436 switch (rawState) { 0437 case GST_STATE_PLAYING: 0438 newState = KMediaSession::PlaybackState::PlayingState; 0439 break; 0440 case GST_STATE_PAUSED: 0441 newState = KMediaSession::PlaybackState::PausedState; 0442 break; 0443 case GST_STATE_VOID_PENDING: 0444 case GST_STATE_NULL: 0445 case GST_STATE_READY: 0446 default: 0447 newState = KMediaSession::PlaybackState::StoppedState; 0448 d->m_seekPending = false; 0449 d->m_positionBeforeSeek = 0; 0450 d->m_positionAfterSeek = 0; 0451 break; 0452 } 0453 if (newState != d->m_playbackState) { 0454 d->m_playbackState = newState; 0455 QTimer::singleShot(0, this, [this, newState]() { 0456 Q_EMIT playbackStateChanged(newState); 0457 Q_EMIT positionChanged(position()); 0458 d->playerSeekableChanged(this); 0459 }); 0460 } 0461 } else if (message->type == GST_MESSAGE_BUFFERING) { 0462 gint bufferPercent; 0463 gst_message_parse_buffering(message, &bufferPercent); 0464 KMediaSession::MediaStatus newStatus; 0465 if (bufferPercent < 100) { 0466 newStatus = KMediaSession::MediaStatus::BufferingMedia; 0467 } else { 0468 newStatus = KMediaSession::MediaStatus::BufferedMedia; 0469 } 0470 if (d->m_mediaStatus != newStatus) { 0471 QTimer::singleShot(0, this, [this]() { 0472 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0473 }); 0474 } 0475 } else if (message->type == GST_MESSAGE_EOS) { 0476 KMediaSession::MediaStatus newStatus = KMediaSession::MediaStatus::EndOfMedia; 0477 if (newStatus != d->m_mediaStatus) { 0478 stop(); 0479 d->m_mediaStatus = newStatus; 0480 QTimer::singleShot(0, this, [this]() { 0481 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0482 }); 0483 } 0484 } else if (message->type == GST_MESSAGE_DURATION_CHANGED) { 0485 if (d->m_duration != duration()) { 0486 // calling duration will already update d->m_duration as side-effect 0487 QTimer::singleShot(0, this, [this]() { 0488 Q_EMIT durationChanged(d->m_duration); 0489 }); 0490 } 0491 } else if (message->type == GST_MESSAGE_TAG) { 0492 GstTagList *tags = nullptr; 0493 gst_message_parse_tag(message, &tags); 0494 0495 d->parseMetaData(tags); 0496 gst_tag_list_unref(tags); 0497 } else if (message->type == GST_MESSAGE_ERROR) { 0498 GError *err = nullptr; 0499 gchar *dbg_info = nullptr; 0500 0501 gst_message_parse_error(message, &err, &dbg_info); 0502 // TODO: implement more error parsing 0503 if (err->domain == GST_STREAM_ERROR && err->code == GST_STREAM_ERROR_CODEC_NOT_FOUND) { 0504 d->m_error = KMediaSession::Error::FormatError; 0505 } else { 0506 d->m_error = KMediaSession::Error::ResourceError; 0507 } 0508 d->m_mediaStatus = KMediaSession::MediaStatus::InvalidMedia; 0509 QTimer::singleShot(0, this, [this]() { 0510 Q_EMIT mediaStatusChanged(d->m_mediaStatus); 0511 Q_EMIT errorChanged(d->m_error); 0512 }); 0513 g_error_free(err); 0514 g_free(dbg_info); 0515 } 0516 gst_message_unref(message); 0517 } 0518 0519 void GstMediaBackend::timerUpdate() 0520 { 0521 QTimer::singleShot(0, this, [this]() { 0522 Q_EMIT positionChanged(position()); 0523 }); 0524 } 0525 0526 void GstMediaBackendPrivate::playerSignalVolumeChanged(GObject *o, GParamSpec *p, gpointer d) 0527 { 0528 qCDebug(GstSignalsLog) << "GstMediaBackendPrivate::playerSignalVolumeChanged()"; 0529 Q_UNUSED(o); 0530 Q_UNUSED(p); 0531 GstMediaBackend *gstMediaBackend = reinterpret_cast<GstMediaBackend *>(d); 0532 QTimer::singleShot(0, gstMediaBackend, [gstMediaBackend]() { 0533 Q_EMIT gstMediaBackend->volumeChanged(gstMediaBackend->volume()); 0534 }); 0535 } 0536 0537 void GstMediaBackendPrivate::playerSignalMutedChanged(GObject *o, GParamSpec *p, gpointer d) 0538 { 0539 qCDebug(GstSignalsLog) << "GstMediaBackendPrivate::playerSignalMutedChanged()"; 0540 Q_UNUSED(o); 0541 Q_UNUSED(p); 0542 GstMediaBackend *gstMediaBackend = reinterpret_cast<GstMediaBackend *>(d); 0543 QTimer::singleShot(0, gstMediaBackend, [gstMediaBackend]() { 0544 Q_EMIT gstMediaBackend->mutedChanged(gstMediaBackend->muted()); 0545 }); 0546 } 0547 0548 void GstMediaBackendPrivate::playerSignalPlaybackRateChanged(GObject *o, GParamSpec *p, gpointer d) 0549 { 0550 qCDebug(GstSignalsLog) << "GstMediaBackendPrivate::playerSignalPlaybackRateChanged()"; 0551 Q_UNUSED(o); 0552 Q_UNUSED(p); 0553 GstMediaBackend *gstMediaBackend = reinterpret_cast<GstMediaBackend *>(d); 0554 QTimer::singleShot(0, gstMediaBackend, [gstMediaBackend]() { 0555 gdouble rawRate; 0556 g_object_get(gstMediaBackend->d->m_scaleTempo, "rate", &rawRate, nullptr); 0557 0558 qreal rate = static_cast<qreal>(rawRate); 0559 if (qFuzzyCompare(rate, 0.0)) { 0560 rate = 1.0; 0561 } 0562 0563 if (!qFuzzyCompare(rate, gstMediaBackend->d->m_rate)) { 0564 gstMediaBackend->d->m_rate = rate; 0565 Q_EMIT gstMediaBackend->playbackRateChanged(gstMediaBackend->playbackRate()); 0566 } 0567 }); 0568 } 0569 0570 void GstMediaBackendPrivate::playerSeekableChanged(GstMediaBackend *backend) 0571 { 0572 qCDebug(GstMediaBackendLog) << "GstMediaBackendPrivate::playerSeekableChanged()"; 0573 bool oldSeekable = m_seekable; 0574 bool newSeekable = backend->seekable(); // this will change m_seekable 0575 if (newSeekable != oldSeekable) { 0576 Q_EMIT backend->seekableChanged(m_seekable); 0577 if (newSeekable && !qFuzzyCompare(m_rate, 1.0)) { 0578 backend->setPlaybackRate(m_rate); 0579 } 0580 } 0581 } 0582 0583 void GstMediaBackendPrivate::parseMetaData(GstTagList *tags) 0584 { 0585 qCDebug(GstMediaBackendLog) << "GstMediaBackendPrivate::parseMetaData()"; 0586 // qCDebug(GstSignalsLog) << "dump of all tag info:" << gst_tag_list_to_string(tags); 0587 0588 char *rawString; 0589 GstSample *sample; 0590 0591 if (gst_tag_list_get_string(tags, GST_TAG_TITLE, &rawString)) { 0592 QString title = QString::fromUtf8(rawString); 0593 if (m_kMediaSession->metaData()->title().isEmpty()) { 0594 m_kMediaSession->metaData()->setTitle(title); 0595 } 0596 g_free(rawString); 0597 } 0598 if (gst_tag_list_get_string(tags, GST_TAG_ARTIST, &rawString)) { 0599 QString artist = QString::fromUtf8(rawString); 0600 if (m_kMediaSession->metaData()->artist().isEmpty()) { 0601 m_kMediaSession->metaData()->setArtist(artist); 0602 } 0603 g_free(rawString); 0604 } 0605 if (gst_tag_list_get_string(tags, GST_TAG_ALBUM, &rawString)) { 0606 QString album = QString::fromUtf8(rawString); 0607 if (m_kMediaSession->metaData()->album().isEmpty()) { 0608 m_kMediaSession->metaData()->setAlbum(album); 0609 } 0610 g_free(rawString); 0611 } 0612 if (gst_tag_list_get_sample(tags, GST_TAG_IMAGE, &sample)) { 0613 GstBuffer *buffer = gst_sample_get_buffer(sample); 0614 gsize offset = 0; 0615 gsize dest_size; 0616 gpointer dest; 0617 QByteArray rawImage; 0618 gst_buffer_extract_dup(buffer, offset, gst_buffer_get_size(buffer), &dest, &dest_size); 0619 for (int i = 0; i < static_cast<int>(dest_size); i++) { 0620 rawImage.append(reinterpret_cast<const char *>(dest)[i]); 0621 } 0622 m_imageCacheDir = std::make_unique<QTemporaryDir>(); 0623 if (m_imageCacheDir->isValid()) { 0624 QString filePath = m_imageCacheDir->path() + QStringLiteral("/coverimage"); 0625 0626 bool success = QImage::fromData(rawImage).save(filePath, "PNG"); 0627 0628 if (success) { 0629 QString localFilePath = QStringLiteral("file://") + filePath; 0630 m_kMediaSession->metaData()->setArtworkUrl(QUrl(localFilePath)); 0631 } 0632 } 0633 gst_sample_unref(sample); 0634 } 0635 }