2018-02-27 18:06:05 +01:00
/***************************************************************************
* Copyright ( C ) 2003 - 2005 by Mark Kretschmann < markey @ web . de > *
* Copyright ( C ) 2005 by Jakub Stachowski < qbast @ go2 . pl > *
* Copyright ( C ) 2006 Paul Cifarelli < paul @ cifarelli . net > *
2021-03-20 21:14:47 +01:00
* Copyright ( C ) 2017 - 2021 Jonas Kvinge < jonas @ jkvinge . net > *
2018-02-27 18:06:05 +01:00
* *
* This program is free software ; you can redistribute it and / or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation ; either version 2 of the License , or *
* ( at your option ) any later version . *
* *
* This program is distributed in the hope that it will be useful , *
* but WITHOUT ANY WARRANTY ; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the *
* GNU General Public License for more details . *
* *
* You should have received a copy of the GNU General Public License *
* along with this program ; if not , write to the *
* Free Software Foundation , Inc . , *
* 51 Franklin Steet , Fifth Floor , Boston , MA 02111 - 1307 , USA . *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "config.h"
2023-07-21 05:25:57 +02:00
# include <cmath>
# include <algorithm>
# include <optional>
2024-04-23 17:15:42 +02:00
# include <utility>
2023-07-21 05:25:57 +02:00
# include <memory>
2018-05-01 00:41:33 +02:00
# include <glib.h>
# include <glib-object.h>
2018-02-27 18:06:05 +01:00
# include <gst/gst.h>
2020-10-21 00:07:58 +02:00
# include <gst/pbutils/pbutils.h>
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
# include <QtGlobal>
# include <QFuture>
2021-01-30 21:53:53 +01:00
# include <QFutureWatcher>
2024-09-02 22:27:45 +02:00
# include <QMutexLocker>
2018-04-06 22:13:11 +02:00
# include <QTimer>
2018-05-01 00:41:33 +02:00
# include <QList>
# include <QByteArray>
# include <QChar>
# include <QString>
# include <QStringList>
# include <QUrl>
2018-04-06 22:13:11 +02:00
# include <QTimeLine>
2020-07-18 17:53:14 +02:00
# include <QEasingCurve>
2018-05-01 00:41:33 +02:00
# include <QMetaObject>
2020-02-09 02:29:35 +01:00
# include <QTimerEvent>
2018-02-27 18:06:05 +01:00
2023-07-21 05:55:24 +02:00
# include "core/shared_ptr.h"
2018-02-27 18:06:05 +01:00
# include "core/logging.h"
# include "core/taskmanager.h"
2020-10-21 00:07:58 +02:00
# include "core/signalchecker.h"
2022-12-28 03:12:00 +01:00
# include "utilities/timeconstants.h"
2018-05-01 00:41:33 +02:00
# include "enginebase.h"
# include "gstengine.h"
# include "gstenginepipeline.h"
# include "gstbufferconsumer.h"
2023-04-22 19:13:42 +02:00
# include "enginemetadata.h"
2019-04-18 15:03:01 +02:00
2024-09-07 04:24:14 +02:00
using namespace Qt : : StringLiterals ;
2023-07-21 05:55:24 +02:00
using std : : make_shared ;
2024-09-22 13:15:19 +02:00
# ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wunused-const-variable"
# endif
2018-02-27 18:06:05 +01:00
const char * GstEngine : : kAutoSink = " autoaudiosink " ;
const char * GstEngine : : kALSASink = " alsasink " ;
2024-08-07 00:52:58 +02:00
namespace {
constexpr char kOpenALSASink [ ] = " openalsink " ;
constexpr char kOSSSink [ ] = " osssink " ;
constexpr char kOSS4Sink [ ] = " oss4sink " ;
constexpr char kJackAudioSink [ ] = " jackaudiosink " ;
constexpr char kPulseSink [ ] = " pulsesink " ;
constexpr char kA2DPSink [ ] = " a2dpsink " ;
constexpr char kAVDTPSink [ ] = " avdtpsink " ;
constexpr char InterAudiosink [ ] = " interaudiosink " ;
constexpr char kDirectSoundSink [ ] = " directsoundsink " ;
constexpr char kOSXAudioSink [ ] = " osxaudiosink " ;
constexpr char kWASAPISink [ ] = " wasapisink " ;
constexpr int kDiscoveryTimeoutS = 10 ;
constexpr qint64 kTimerIntervalNanosec = 1000 * kNsecPerMsec ; // 1s
constexpr qint64 kPreloadGapNanosec = 8000 * kNsecPerMsec ; // 8s
constexpr qint64 kSeekDelayNanosec = 100 * kNsecPerMsec ; // 100msec
} // namespace
2018-02-27 18:06:05 +01:00
2024-09-22 13:15:19 +02:00
# ifdef __clang_
# pragma clang diagnostic pop
# endif
2023-07-21 05:55:24 +02:00
GstEngine : : GstEngine ( SharedPtr < TaskManager > task_manager , QObject * parent )
2023-04-22 19:13:42 +02:00
: EngineBase ( parent ) ,
2021-06-20 19:04:08 +02:00
task_manager_ ( task_manager ) ,
2020-10-21 00:07:58 +02:00
gst_startup_ ( nullptr ) ,
discoverer_ ( nullptr ) ,
2018-02-27 18:06:05 +01:00
buffering_task_id_ ( - 1 ) ,
latest_buffer_ ( nullptr ) ,
2019-10-27 23:48:54 +01:00
stereo_balancer_enabled_ ( false ) ,
2021-06-20 23:53:28 +02:00
stereo_balance_ ( 0.0F ) ,
2019-11-08 23:07:21 +01:00
equalizer_enabled_ ( false ) ,
equalizer_preamp_ ( 0 ) ,
2018-02-27 18:06:05 +01:00
seek_timer_ ( new QTimer ( this ) ) ,
2021-06-20 23:53:28 +02:00
waiting_to_seek_ ( false ) ,
seek_pos_ ( 0 ) ,
2018-02-27 18:06:05 +01:00
timer_id_ ( - 1 ) ,
2024-08-03 00:05:06 +02:00
has_faded_out_to_pause_ ( false ) ,
2018-02-27 18:06:05 +01:00
scope_chunk_ ( 0 ) ,
2020-10-21 00:07:58 +02:00
have_new_buffer_ ( false ) ,
2021-06-20 23:53:28 +02:00
scope_chunks_ ( 0 ) ,
2020-10-21 00:07:58 +02:00
discovery_finished_cb_id_ ( - 1 ) ,
2024-08-03 00:05:06 +02:00
discovery_discovered_cb_id_ ( - 1 ) ,
delayed_state_ ( State : : Empty ) ,
delayed_state_pause_ ( false ) ,
delayed_state_offset_nanosec_ ( 0 ) {
2018-02-27 18:06:05 +01:00
seek_timer_ - > setSingleShot ( true ) ;
seek_timer_ - > setInterval ( kSeekDelayNanosec / kNsecPerMsec ) ;
2021-01-26 16:48:04 +01:00
QObject : : connect ( seek_timer_ , & QTimer : : timeout , this , & GstEngine : : SeekNow ) ;
2018-02-27 18:06:05 +01:00
2021-03-21 04:47:11 +01:00
GstEngine : : ReloadSettings ( ) ;
2018-02-27 18:06:05 +01:00
}
GstEngine : : ~ GstEngine ( ) {
2019-11-08 23:07:21 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
current_pipeline_ . reset ( ) ;
2019-11-08 23:07:21 +01:00
2019-10-20 18:52:58 +02:00
if ( latest_buffer_ ) {
gst_buffer_unref ( latest_buffer_ ) ;
latest_buffer_ = nullptr ;
}
2019-11-08 23:07:21 +01:00
2020-10-21 00:07:58 +02:00
if ( discoverer_ ) {
2021-08-23 21:21:08 +02:00
if ( discovery_discovered_cb_id_ ! = - 1 ) {
2020-10-21 00:07:58 +02:00
g_signal_handler_disconnect ( G_OBJECT ( discoverer_ ) , discovery_discovered_cb_id_ ) ;
2021-08-23 21:21:08 +02:00
}
if ( discovery_finished_cb_id_ ! = - 1 ) {
2020-10-21 00:07:58 +02:00
g_signal_handler_disconnect ( G_OBJECT ( discoverer_ ) , discovery_finished_cb_id_ ) ;
2021-08-23 21:21:08 +02:00
}
2020-10-21 00:07:58 +02:00
gst_discoverer_stop ( discoverer_ ) ;
g_object_unref ( discoverer_ ) ;
discoverer_ = nullptr ;
}
2018-02-27 18:06:05 +01:00
}
bool GstEngine : : Init ( ) {
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
return true ;
}
2023-04-22 19:13:42 +02:00
EngineBase : : State GstEngine : : state ( ) const {
2018-02-27 18:06:05 +01:00
2024-08-09 19:26:15 +02:00
if ( ! current_pipeline_ ) return stream_url_ . isEmpty ( ) ? State : : Empty : State : : Idle ;
2018-02-27 18:06:05 +01:00
switch ( current_pipeline_ - > state ( ) ) {
case GST_STATE_NULL :
2024-08-09 19:26:15 +02:00
return State : : Empty ;
2018-02-27 18:06:05 +01:00
case GST_STATE_READY :
2024-08-09 19:26:15 +02:00
return State : : Idle ;
2018-02-27 18:06:05 +01:00
case GST_STATE_PLAYING :
2024-08-09 19:26:15 +02:00
return State : : Playing ;
2018-02-27 18:06:05 +01:00
case GST_STATE_PAUSED :
2024-08-09 19:26:15 +02:00
return State : : Paused ;
2018-02-27 18:06:05 +01:00
default :
2024-08-09 19:26:15 +02:00
return State : : Empty ;
2018-02-27 18:06:05 +01:00
}
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
2023-04-21 16:20:00 +02:00
void GstEngine : : StartPreloading ( const QUrl & media_url , const QUrl & stream_url , const bool force_stop_at_end , const qint64 beginning_nanosec , const qint64 end_nanosec ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
2023-04-21 16:20:00 +02:00
const QByteArray gst_url = FixupUrl ( stream_url ) ;
2018-02-27 18:06:05 +01:00
2018-04-02 03:43:56 +02:00
// No crossfading, so we can just queue the new URL in the existing pipeline and get gapless playback (hopefully)
2020-10-21 00:07:58 +02:00
if ( current_pipeline_ ) {
2023-04-22 03:54:09 +02:00
current_pipeline_ - > PrepareNextUrl ( media_url , stream_url , gst_url , beginning_nanosec , force_stop_at_end ? end_nanosec : 0 ) ;
2020-10-21 00:07:58 +02:00
// Add request to discover the stream
2022-06-04 15:51:35 +02:00
if ( discoverer_ & & media_url . scheme ( ) ! = QStringLiteral ( " spotify " ) ) {
2021-04-13 22:11:44 +02:00
if ( ! gst_discoverer_discover_uri_async ( discoverer_ , gst_url . constData ( ) ) ) {
2020-10-21 00:07:58 +02:00
qLog ( Error ) < < " Failed to start stream discovery for " < < gst_url ;
}
}
}
2018-03-10 13:02:56 +01:00
2018-02-27 18:06:05 +01:00
}
2023-06-27 04:05:01 +02:00
bool GstEngine : : Load ( const QUrl & media_url , const QUrl & stream_url , const EngineBase : : TrackChangeFlags change , const bool force_stop_at_end , const quint64 beginning_nanosec , const qint64 end_nanosec , const std : : optional < double > ebur128_integrated_loudness_lufs ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
2024-06-12 18:52:53 +02:00
EngineBase : : Load ( media_url , stream_url , change , force_stop_at_end , beginning_nanosec , end_nanosec , ebur128_integrated_loudness_lufs ) ;
2018-02-27 18:06:05 +01:00
2023-04-21 16:20:00 +02:00
const QByteArray gst_url = FixupUrl ( stream_url ) ;
2018-02-27 18:06:05 +01:00
2023-04-22 19:13:42 +02:00
bool crossfade = current_pipeline_ & & ( ( crossfade_enabled_ & & change & EngineBase : : TrackChangeType : : Manual ) | | ( autocrossfade_enabled_ & & change & EngineBase : : TrackChangeType : : Auto ) | | ( ( crossfade_enabled_ | | autocrossfade_enabled_ ) & & change & EngineBase : : TrackChangeType : : Intro ) ) ;
2018-02-27 18:06:05 +01:00
2023-04-22 19:13:42 +02:00
if ( change & EngineBase : : TrackChangeType : : Auto & & change & EngineBase : : TrackChangeType : : SameAlbum & & ! crossfade_same_album_ ) {
2018-02-27 18:06:05 +01:00
crossfade = false ;
2023-04-21 15:07:17 +02:00
}
2018-02-27 18:06:05 +01:00
2023-07-09 21:29:08 +02:00
if ( ! crossfade & & current_pipeline_ & & current_pipeline_ - > stream_url ( ) = = stream_url & & change & EngineBase : : TrackChangeType : : Auto ) {
2018-02-27 18:06:05 +01:00
// We're not crossfading, and the pipeline is already playing the URI we want, so just do nothing.
2023-07-09 21:29:08 +02:00
current_pipeline_ - > SetEBUR128LoudnessNormalizingGain_dB ( ebur128_loudness_normalizing_gain_db_ ) ;
2018-02-27 18:06:05 +01:00
return true ;
}
2018-03-10 13:02:56 +01:00
2024-08-03 00:05:06 +02:00
GstEnginePipelinePtr pipeline = CreatePipeline ( media_url , stream_url , gst_url , force_stop_at_end ? end_nanosec : 0 , ebur128_loudness_normalizing_gain_db_ ) ;
2018-02-27 18:06:05 +01:00
if ( ! pipeline ) return false ;
2024-08-03 00:05:06 +02:00
GstEnginePipelinePtr old_pipeline = current_pipeline_ ;
2018-02-27 18:06:05 +01:00
current_pipeline_ = pipeline ;
2024-08-11 17:37:23 +02:00
if ( old_pipeline ) {
if ( crossfade & & ! old_pipeline - > exclusive_mode ( ) & & ! AnyExclusivePipelineActive ( ) & & ! fadeout_pipelines_ . contains ( old_pipeline - > id ( ) ) ) {
StartFadeout ( old_pipeline ) ;
}
else {
FinishPipeline ( old_pipeline ) ;
}
2024-08-03 00:05:06 +02:00
}
2018-02-27 18:06:05 +01:00
2024-08-11 17:37:23 +02:00
BufferingFinished ( ) ;
2018-02-27 18:06:05 +01:00
SetVolume ( volume_ ) ;
2019-11-08 23:07:21 +01:00
SetStereoBalance ( stereo_balance_ ) ;
2019-04-20 22:23:22 +02:00
SetEqualizerParameters ( equalizer_preamp_ , equalizer_gains_ ) ;
2018-02-27 18:06:05 +01:00
// Maybe fade in this track
2024-08-11 17:37:23 +02:00
if ( crossfade & & ( ! old_pipeline | | ! old_pipeline - > exclusive_mode ( ) ) & & ! AnyExclusivePipelineActive ( ) ) {
2018-02-27 18:06:05 +01:00
current_pipeline_ - > StartFader ( fadeout_duration_nanosec_ , QTimeLine : : Forward ) ;
2021-08-23 21:21:08 +02:00
}
2018-02-27 18:06:05 +01:00
2020-10-21 00:07:58 +02:00
// Setting up stream discoverer
if ( ! discoverer_ ) {
discoverer_ = gst_discoverer_new ( kDiscoveryTimeoutS * GST_SECOND , nullptr ) ;
if ( discoverer_ ) {
discovery_discovered_cb_id_ = CHECKED_GCONNECT ( G_OBJECT ( discoverer_ ) , " discovered " , & StreamDiscovered , this ) ;
discovery_finished_cb_id_ = CHECKED_GCONNECT ( G_OBJECT ( discoverer_ ) , " finished " , & StreamDiscoveryFinished , this ) ;
gst_discoverer_start ( discoverer_ ) ;
}
}
// Add request to discover the stream
2022-06-04 15:51:35 +02:00
if ( discoverer_ & & media_url . scheme ( ) ! = QStringLiteral ( " spotify " ) ) {
2021-04-13 22:11:44 +02:00
if ( ! gst_discoverer_discover_uri_async ( discoverer_ , gst_url . constData ( ) ) ) {
2020-10-21 00:07:58 +02:00
qLog ( Error ) < < " Failed to start stream discovery for " < < gst_url ;
}
}
2018-02-27 18:06:05 +01:00
return true ;
2018-10-30 23:39:08 +01:00
2018-02-27 18:06:05 +01:00
}
2024-08-03 00:05:06 +02:00
bool GstEngine : : Play ( const bool pause , const quint64 offset_nanosec ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
2024-09-17 22:23:17 +02:00
if ( ! current_pipeline_ | | current_pipeline_ - > is_buffering ( ) | | current_pipeline_ - > state ( ) = = GstState : : GST_STATE_PLAYING ) return false ;
2018-02-27 18:06:05 +01:00
2024-08-11 17:37:23 +02:00
if ( OldExclusivePipelineActive ( ) ) {
2024-08-03 00:05:06 +02:00
qLog ( Debug ) < < " Delaying play because a exclusive pipeline is already active... " ;
delayed_state_ = pause ? State : : Paused : State : : Playing ;
delayed_state_pause_ = pause ;
delayed_state_offset_nanosec_ = offset_nanosec ;
return true ;
}
if ( fadeout_pause_pipeline_ ) {
StopFadeoutPause ( ) ;
}
delayed_state_ = State : : Empty ;
delayed_state_pause_ = false ;
delayed_state_offset_nanosec_ = 0 ;
2021-01-30 21:53:53 +01:00
QFutureWatcher < GstStateChangeReturn > * watcher = new QFutureWatcher < GstStateChangeReturn > ( ) ;
2022-10-29 18:45:09 +02:00
const int pipeline_id = current_pipeline_ - > id ( ) ;
2024-08-03 00:05:06 +02:00
QObject : : connect ( watcher , & QFutureWatcher < GstStateChangeReturn > : : finished , this , [ this , watcher , pipeline_id , pause , offset_nanosec ] ( ) {
2022-10-29 18:45:09 +02:00
const GstStateChangeReturn ret = watcher - > result ( ) ;
2021-01-30 21:53:53 +01:00
watcher - > deleteLater ( ) ;
2024-08-03 00:05:06 +02:00
PlayDone ( ret , pause , offset_nanosec , pipeline_id ) ;
2021-01-30 21:53:53 +01:00
} ) ;
2024-09-16 00:34:39 +02:00
QFuture < GstStateChangeReturn > future = current_pipeline_ - > Play ( pause , beginning_nanosec_ + offset_nanosec ) ;
2021-06-16 00:30:21 +02:00
watcher - > setFuture ( future ) ;
2018-06-28 01:15:32 +02:00
return true ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : Stop ( const bool stop_after ) {
2018-02-27 18:06:05 +01:00
StopTimers ( ) ;
2024-08-03 00:05:06 +02:00
delayed_state_ = State : : Empty ;
delayed_state_pause_ = false ;
delayed_state_offset_nanosec_ = 0 ;
2023-04-21 16:20:00 +02:00
media_url_ . clear ( ) ;
2022-10-29 18:43:49 +02:00
stream_url_ . clear ( ) ; // To ensure we return Empty from state()
2018-02-27 18:06:05 +01:00
beginning_nanosec_ = end_nanosec_ = 0 ;
2018-04-02 03:43:56 +02:00
// Check if we started a fade out. If it isn't finished yet and the user pressed stop, we cancel the fader and just stop the playback.
2024-08-03 00:05:06 +02:00
if ( fadeout_pause_pipeline_ ) {
StopFadeoutPause ( ) ;
2018-02-27 18:06:05 +01:00
}
2024-08-03 00:05:06 +02:00
if ( current_pipeline_ ) {
2024-08-11 17:37:23 +02:00
if ( fadeout_enabled_ & & ! stop_after & & ! AnyExclusivePipelineActive ( ) ) {
GstEnginePipelinePtr old_pipeline = current_pipeline_ ;
2024-08-03 00:05:06 +02:00
current_pipeline_ = GstEnginePipelinePtr ( ) ;
2024-08-11 17:37:23 +02:00
StartFadeout ( old_pipeline ) ;
2024-08-03 00:05:06 +02:00
}
else {
GstEnginePipelinePtr old_pipeline = current_pipeline_ ;
current_pipeline_ = GstEnginePipelinePtr ( ) ;
FinishPipeline ( old_pipeline ) ;
}
}
2018-02-27 18:06:05 +01:00
BufferingFinished ( ) ;
2024-08-03 00:05:06 +02:00
2024-08-25 01:06:30 +02:00
Q_EMIT StateChanged ( State : : Empty ) ;
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
void GstEngine : : Pause ( ) {
if ( ! current_pipeline_ | | current_pipeline_ - > is_buffering ( ) ) return ;
2024-08-03 00:05:06 +02:00
delayed_state_ = State : : Empty ;
delayed_state_pause_ = false ;
delayed_state_offset_nanosec_ = 0 ;
if ( fadeout_pause_pipeline_ ) {
2018-02-27 18:06:05 +01:00
return ;
}
if ( current_pipeline_ - > state ( ) = = GST_STATE_PLAYING ) {
2024-08-11 17:37:23 +02:00
if ( fadeout_pause_enabled_ & & ! AnyExclusivePipelineActive ( ) ) {
2018-02-27 18:06:05 +01:00
StartFadeoutPause ( ) ;
}
else {
2024-08-03 00:05:06 +02:00
current_pipeline_ - > SetStateAsync ( GST_STATE_PAUSED ) ;
2024-08-25 01:06:30 +02:00
Q_EMIT StateChanged ( State : : Paused ) ;
2018-02-27 18:06:05 +01:00
StopTimers ( ) ;
}
}
2018-05-01 00:41:33 +02:00
2018-02-27 18:06:05 +01:00
}
void GstEngine : : Unpause ( ) {
if ( ! current_pipeline_ | | current_pipeline_ - > is_buffering ( ) ) return ;
if ( current_pipeline_ - > state ( ) = = GST_STATE_PAUSED ) {
2018-03-10 13:02:56 +01:00
// Check if we faded out last time. If yes, fade in no matter what the settings say.
// If we pause with fadeout, deactivate fadeout and resume playback, the player would be muted if not faded in.
2024-08-11 17:37:23 +02:00
if ( has_faded_out_to_pause_ & & ! AnyExclusivePipelineActive ( ) ) {
2023-07-21 05:55:24 +02:00
QObject : : disconnect ( & * current_pipeline_ , & GstEnginePipeline : : FaderFinished , nullptr , nullptr ) ;
2024-08-03 00:05:06 +02:00
current_pipeline_ - > StartFader ( fadeout_pause_duration_nanosec_ , QTimeLine : : Forward , QEasingCurve : : Linear , false ) ;
has_faded_out_to_pause_ = false ;
2018-02-27 18:06:05 +01:00
}
2024-08-03 00:05:06 +02:00
current_pipeline_ - > SetStateAsync ( GST_STATE_PLAYING ) ;
2024-08-25 01:06:30 +02:00
Q_EMIT StateChanged ( State : : Playing ) ;
2018-02-27 18:06:05 +01:00
StartTimers ( ) ;
}
2024-08-03 00:05:06 +02:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : Seek ( const quint64 offset_nanosec ) {
2018-02-27 18:06:05 +01:00
if ( ! current_pipeline_ ) return ;
seek_pos_ = beginning_nanosec_ + offset_nanosec ;
waiting_to_seek_ = true ;
if ( ! seek_timer_ - > isActive ( ) ) {
SeekNow ( ) ;
seek_timer_ - > start ( ) ; // Stop us from seeking again for a little while
}
2024-08-03 00:05:06 +02:00
2018-02-27 18:06:05 +01:00
}
2022-12-03 03:46:59 +01:00
void GstEngine : : SetVolumeSW ( const uint volume ) {
if ( current_pipeline_ ) current_pipeline_ - > SetVolume ( volume ) ;
2018-06-28 01:15:32 +02:00
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
qint64 GstEngine : : position_nanosec ( ) const {
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ ) return 0 ;
2018-02-27 18:06:05 +01:00
2021-10-30 02:21:29 +02:00
const qint64 result = current_pipeline_ - > position ( ) - static_cast < qint64 > ( beginning_nanosec_ ) ;
2022-06-13 00:23:42 +02:00
return std : : max ( 0LL , result ) ;
2018-06-28 01:15:32 +02:00
}
qint64 GstEngine : : length_nanosec ( ) const {
if ( ! current_pipeline_ ) return 0 ;
2021-10-30 02:21:29 +02:00
const qint64 result = end_nanosec_ - static_cast < qint64 > ( beginning_nanosec_ ) ;
2018-06-28 01:15:32 +02:00
if ( result > 0 ) {
return result ;
}
else {
// Get the length from the pipeline if we don't know.
return current_pipeline_ - > length ( ) ;
}
}
2023-04-22 19:13:42 +02:00
const EngineBase : : Scope & GstEngine : : scope ( const int chunk_length ) {
2018-06-28 01:15:32 +02:00
// The new buffer could have a different size
if ( have_new_buffer_ ) {
if ( latest_buffer_ ) {
2020-06-15 17:59:02 +02:00
scope_chunks_ = ceil ( ( static_cast < double > ( GST_BUFFER_DURATION ( latest_buffer_ ) / static_cast < double > ( chunk_length * kNsecPerMsec ) ) ) ) ;
2018-06-28 01:15:32 +02:00
}
// if the buffer is shorter than the chunk length
if ( scope_chunks_ < = 0 ) {
scope_chunks_ = 1 ;
}
scope_chunk_ = 0 ;
have_new_buffer_ = false ;
}
if ( latest_buffer_ ) {
UpdateScope ( chunk_length ) ;
}
return scope_ ;
}
EngineBase : : OutputDetailsList GstEngine : : GetOutputsList ( ) const {
2020-10-17 17:29:09 +02:00
const_cast < GstEngine * > ( this ) - > EnsureInitialized ( ) ;
2018-10-22 20:40:02 +02:00
2023-04-22 17:18:29 +02:00
OutputDetailsList outputs ;
GstRegistry * registry = gst_registry_get ( ) ;
GList * const features = gst_registry_get_feature_list ( registry , GST_TYPE_ELEMENT_FACTORY ) ;
for ( GList * future = features ; future ; future = g_list_next ( future ) ) {
GstElementFactory * factory = GST_ELEMENT_FACTORY ( future - > data ) ;
2024-02-14 18:46:23 +01:00
const QString metadata = QString : : fromUtf8 ( gst_element_factory_get_metadata ( factory , GST_ELEMENT_METADATA_KLASS ) ) ;
const QString name = QString : : fromUtf8 ( gst_plugin_feature_get_name ( future - > data ) ) ;
2024-09-07 04:24:14 +02:00
const QStringList classes = metadata . split ( u ' / ' ) ;
if ( classes . contains ( " Audio " _L1 , Qt : : CaseInsensitive ) & & ( classes . contains ( " Sink " _L1 , Qt : : CaseInsensitive ) | | ( classes . contains ( " Source " _L1 , Qt : : CaseInsensitive ) & & name . contains ( " sink " _L1 ) ) ) ) {
2024-02-14 18:46:23 +01:00
QString description = QString : : fromUtf8 ( gst_element_factory_get_metadata ( factory , GST_ELEMENT_METADATA_DESCRIPTION ) ) ;
2024-09-07 04:24:14 +02:00
if ( name = = " wasapi2sink " _L1 & & description = = " Stream audio to an audio capture device through WASAPI " _L1 ) {
description . append ( u ' 2 ' ) ;
2024-02-14 18:46:23 +01:00
}
2024-09-07 04:24:14 +02:00
else if ( name = = " pipewiresink " _L1 & & description = = " Send video to PipeWire " _L1 ) {
description = " Send audio to PipeWire " _L1 ;
2023-06-01 17:21:35 +02:00
}
2024-02-14 18:46:23 +01:00
OutputDetails output ;
output . name = name ;
output . description = description ;
2024-09-07 04:24:14 +02:00
if ( output . name = = QLatin1String ( kAutoSink ) ) output . iconname = " soundcard " _L1 ;
else if ( output . name = = QLatin1String ( kALSASink ) | | output . name = = QLatin1String ( kOSS4Sink ) ) output . iconname = " alsa " _L1 ;
else if ( output . name = = QLatin1String ( kJackAudioSink ) ) output . iconname = " jack " _L1 ;
else if ( output . name = = QLatin1String ( kPulseSink ) ) output . iconname = " pulseaudio " _L1 ;
else if ( output . name = = QLatin1String ( kA2DPSink ) | | output . name = = QLatin1String ( kAVDTPSink ) ) output . iconname = " bluetooth " _L1 ;
else output . iconname = " soundcard " _L1 ;
2023-04-22 17:18:29 +02:00
outputs < < output ;
}
2018-06-28 01:15:32 +02:00
}
2023-04-22 17:18:29 +02:00
gst_plugin_feature_list_free ( features ) ;
return outputs ;
2018-06-28 01:15:32 +02:00
}
2018-07-01 01:29:52 +02:00
bool GstEngine : : ValidOutput ( const QString & output ) {
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-10-22 20:40:02 +02:00
2023-04-22 17:18:29 +02:00
const OutputDetailsList output_details = GetOutputsList ( ) ;
return std : : any_of ( output_details . begin ( ) , output_details . end ( ) , [ output ] ( const OutputDetails & output_detail ) { return output_detail . name = = output ; } ) ;
2018-07-01 01:29:52 +02:00
}
2024-08-02 23:35:52 +02:00
bool GstEngine : : CustomDeviceSupport ( const QString & output ) const {
2024-04-11 02:56:01 +02:00
return output = = QLatin1String ( kALSASink ) | | output = = QLatin1String ( kOpenALSASink ) | | output = = QLatin1String ( kOSSSink ) | | output = = QLatin1String ( kOSS4Sink ) | | output = = QLatin1String ( kPulseSink ) | | output = = QLatin1String ( kA2DPSink ) | | output = = QLatin1String ( kAVDTPSink ) | | output = = QLatin1String ( kJackAudioSink ) ;
2018-06-28 01:15:32 +02:00
}
2024-08-02 23:35:52 +02:00
bool GstEngine : : ALSADeviceSupport ( const QString & output ) const {
2024-04-11 02:56:01 +02:00
return output = = QLatin1String ( kALSASink ) ;
2018-09-21 23:29:00 +02:00
}
2024-08-02 23:35:52 +02:00
bool GstEngine : : ExclusiveModeSupport ( const QString & output ) const {
2024-04-11 02:56:01 +02:00
return output = = QLatin1String ( kWASAPISink ) ;
2024-02-20 01:08:00 +01:00
}
2018-06-28 01:15:32 +02:00
void GstEngine : : ReloadSettings ( ) {
2023-04-22 19:13:42 +02:00
EngineBase : : ReloadSettings ( ) ;
2018-06-28 01:15:32 +02:00
2024-04-11 02:56:01 +02:00
if ( output_ . isEmpty ( ) ) output_ = QLatin1String ( kAutoSink ) ;
2018-06-28 01:15:32 +02:00
}
2019-10-19 01:45:24 +02:00
void GstEngine : : ConsumeBuffer ( GstBuffer * buffer , const int pipeline_id , const QString & format ) {
2018-06-28 01:15:32 +02:00
// Schedule this to run in the GUI thread. The buffer gets added to the queue and unreffed by UpdateScope.
2019-10-19 01:45:24 +02:00
if ( ! QMetaObject : : invokeMethod ( this , " AddBufferToScope " , Q_ARG ( GstBuffer * , buffer ) , Q_ARG ( int , pipeline_id ) , Q_ARG ( QString , format ) ) ) {
2018-06-28 01:15:32 +02:00
qLog ( Warning ) < < " Failed to invoke AddBufferToScope on GstEngine " ;
2020-05-24 23:21:26 +02:00
gst_buffer_unref ( buffer ) ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
2018-02-27 18:06:05 +01:00
}
2019-11-08 23:07:21 +01:00
void GstEngine : : SetStereoBalancerEnabled ( const bool enabled ) {
2018-03-04 20:13:05 +01:00
2019-11-08 23:07:21 +01:00
stereo_balancer_enabled_ = enabled ;
if ( current_pipeline_ ) current_pipeline_ - > set_stereo_balancer_enabled ( enabled ) ;
2018-02-27 18:06:05 +01:00
}
2019-11-08 23:07:21 +01:00
void GstEngine : : SetStereoBalance ( const float value ) {
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
stereo_balance_ = value ;
if ( current_pipeline_ ) current_pipeline_ - > SetStereoBalance ( value ) ;
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
}
void GstEngine : : SetEqualizerEnabled ( const bool enabled ) {
equalizer_enabled_ = enabled ;
if ( current_pipeline_ ) current_pipeline_ - > set_equalizer_enabled ( enabled ) ;
2019-04-20 22:23:22 +02:00
2018-02-27 18:06:05 +01:00
}
2019-11-08 23:07:21 +01:00
void GstEngine : : SetEqualizerParameters ( const int preamp , const QList < int > & band_gains ) {
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
equalizer_preamp_ = preamp ;
equalizer_gains_ = band_gains ;
2018-02-27 18:06:05 +01:00
2019-11-08 23:07:21 +01:00
if ( current_pipeline_ ) current_pipeline_ - > SetEqualizerParams ( preamp , band_gains ) ;
2019-04-20 22:23:22 +02:00
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
void GstEngine : : AddBufferConsumer ( GstBufferConsumer * consumer ) {
2019-11-08 23:07:21 +01:00
2018-06-28 01:15:32 +02:00
buffer_consumers_ < < consumer ;
if ( current_pipeline_ ) current_pipeline_ - > AddBufferConsumer ( consumer ) ;
2019-11-08 23:07:21 +01:00
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
void GstEngine : : RemoveBufferConsumer ( GstBufferConsumer * consumer ) {
2019-11-08 23:07:21 +01:00
2018-06-28 01:15:32 +02:00
buffer_consumers_ . removeAll ( consumer ) ;
if ( current_pipeline_ ) current_pipeline_ - > RemoveBufferConsumer ( consumer ) ;
2019-11-08 23:07:21 +01:00
2018-02-27 18:06:05 +01:00
}
void GstEngine : : timerEvent ( QTimerEvent * e ) {
if ( e - > timerId ( ) ! = timer_id_ ) return ;
2023-04-22 03:54:09 +02:00
if ( current_pipeline_ & & ! about_to_end_emitted_ ) {
2018-02-27 18:06:05 +01:00
const qint64 current_length = length_nanosec ( ) ;
2023-04-21 15:07:17 +02:00
// Only if we know the length of the current stream...
2018-02-27 18:06:05 +01:00
if ( current_length > 0 ) {
2023-04-22 03:54:09 +02:00
const qint64 current_position = position_nanosec ( ) ;
const qint64 remaining = current_length - current_position ;
const qint64 fudge = kTimerIntervalNanosec + 100 * kNsecPerMsec ; // Mmm fudge
const qint64 gap = static_cast < qint64 > ( buffer_duration_nanosec_ ) + ( autocrossfade_enabled_ ? fadeout_duration_nanosec_ : kPreloadGapNanosec ) ;
2023-04-21 15:07:17 +02:00
// Emit TrackAboutToEnd when we're a few seconds away from finishing
2018-02-27 18:06:05 +01:00
if ( remaining < gap + fudge ) {
2023-04-22 03:54:09 +02:00
qLog ( Debug ) < < " Stream from URL " < < media_url_ . toString ( ) < < " about to end in " < < remaining / kNsecPerSec < < " seconds. Fuge: " < < fudge / kNsecPerMsec < < " + " < < " Gap: " < < gap / kNsecPerMsec ;
EmitAboutToFinish ( ) ;
2018-02-27 18:06:05 +01:00
}
}
}
2018-03-04 20:13:05 +01:00
2018-02-27 18:06:05 +01:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : EndOfStreamReached ( const int pipeline_id , const bool has_next_track ) {
2018-06-28 01:15:32 +02:00
2023-04-21 15:07:17 +02:00
if ( ! current_pipeline_ | | current_pipeline_ - > id ( ) ! = pipeline_id ) {
2018-06-28 01:15:32 +02:00
return ;
2023-04-21 15:07:17 +02:00
}
2018-06-28 01:15:32 +02:00
if ( ! has_next_track ) {
2024-08-11 15:53:41 +02:00
current_pipeline_ = GstEnginePipelinePtr ( ) ;
2018-06-28 01:15:32 +02:00
BufferingFinished ( ) ;
}
2023-04-21 15:07:17 +02:00
2024-08-25 01:06:30 +02:00
Q_EMIT TrackEnded ( ) ;
2018-10-30 23:39:08 +01:00
2018-06-28 01:15:32 +02:00
}
2022-04-14 20:56:57 +02:00
void GstEngine : : HandlePipelineError ( const int pipeline_id , const int domain , const int error_code , const QString & message , const QString & debugstr ) {
2018-06-28 01:15:32 +02:00
2020-10-13 00:49:34 +02:00
qLog ( Error ) < < " GStreamer error: " < < domain < < error_code < < message ;
2018-06-28 01:15:32 +02:00
2024-09-02 22:27:45 +02:00
Q_EMIT Error ( message ) ;
Q_EMIT Error ( debugstr ) ;
2024-08-11 17:37:23 +02:00
2024-09-02 22:27:45 +02:00
if ( fadeout_pause_pipeline_ & & pipeline_id = = fadeout_pause_pipeline_ - > id ( ) ) {
StopFadeoutPause ( ) ;
2018-10-30 23:39:08 +01:00
}
2018-06-28 01:15:32 +02:00
2024-09-02 22:27:45 +02:00
if ( current_pipeline_ & & current_pipeline_ - > id ( ) = = pipeline_id ) {
FinishPipeline ( current_pipeline_ ) ;
current_pipeline_ = GstEnginePipelinePtr ( ) ;
BufferingFinished ( ) ;
Q_EMIT StateChanged ( State : : Error ) ;
if (
( domain = = static_cast < int > ( GST_RESOURCE_ERROR ) & & (
error_code = = static_cast < int > ( GST_RESOURCE_ERROR_NOT_FOUND ) | |
error_code = = static_cast < int > ( GST_RESOURCE_ERROR_OPEN_READ ) | |
error_code = = static_cast < int > ( GST_RESOURCE_ERROR_NOT_AUTHORIZED )
) )
| | ( domain = = static_cast < int > ( GST_STREAM_ERROR ) )
) {
Q_EMIT InvalidSongRequested ( stream_url_ ) ;
}
else {
Q_EMIT FatalError ( ) ;
}
}
else if ( fadeout_pipelines_ . contains ( pipeline_id ) ) {
GstEnginePipelinePtr pipeline = fadeout_pipelines_ . take ( pipeline_id ) ;
FinishPipeline ( pipeline ) ;
}
2018-06-28 01:15:32 +02:00
}
2023-04-22 19:13:42 +02:00
void GstEngine : : NewMetaData ( const int pipeline_id , const EngineMetadata & engine_metadata ) {
2018-06-28 01:15:32 +02:00
2023-04-21 15:07:17 +02:00
if ( ! current_pipeline_ | | current_pipeline_ - > id ( ) ! = pipeline_id ) return ;
2024-08-25 01:06:30 +02:00
Q_EMIT MetaData ( engine_metadata ) ;
2019-09-07 23:34:13 +02:00
2018-06-28 01:15:32 +02:00
}
2019-10-19 01:45:24 +02:00
void GstEngine : : AddBufferToScope ( GstBuffer * buf , const int pipeline_id , const QString & format ) {
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ | | current_pipeline_ - > id ( ) ! = pipeline_id ) {
gst_buffer_unref ( buf ) ;
return ;
}
if ( latest_buffer_ ) {
gst_buffer_unref ( latest_buffer_ ) ;
}
2019-10-19 01:45:24 +02:00
buffer_format_ = format ;
2018-06-28 01:15:32 +02:00
latest_buffer_ = buf ;
have_new_buffer_ = true ;
}
2024-08-03 00:05:06 +02:00
void GstEngine : : FadeoutFinished ( const int pipeline_id ) {
if ( ! fadeout_pipelines_ . contains ( pipeline_id ) ) {
return ;
}
2024-09-02 22:27:45 +02:00
GstEnginePipelinePtr pipeline = fadeout_pipelines_ . take ( pipeline_id ) ;
2023-04-21 15:07:17 +02:00
2024-09-02 22:27:45 +02:00
FinishPipeline ( pipeline ) ;
2023-04-21 15:07:17 +02:00
2018-06-28 01:15:32 +02:00
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
void GstEngine : : FadeoutPauseFinished ( ) {
2018-02-27 18:06:05 +01:00
2024-09-02 22:27:45 +02:00
if ( ! fadeout_pause_pipeline_ ) return ;
2024-08-03 00:05:06 +02:00
fadeout_pause_pipeline_ - > SetStateAsync ( GST_STATE_PAUSED ) ;
2024-08-25 01:06:30 +02:00
Q_EMIT StateChanged ( State : : Paused ) ;
2018-06-28 01:15:32 +02:00
StopTimers ( ) ;
2024-08-03 00:05:06 +02:00
has_faded_out_to_pause_ = true ;
fadeout_pause_pipeline_ = GstEnginePipelinePtr ( ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
}
void GstEngine : : SeekNow ( ) {
if ( ! waiting_to_seek_ ) return ;
waiting_to_seek_ = false ;
2018-04-07 12:20:31 +02:00
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ ) return ;
2021-10-30 02:21:29 +02:00
if ( ! current_pipeline_ - > Seek ( static_cast < qint64 > ( seek_pos_ ) ) ) {
2018-06-28 01:15:32 +02:00
qLog ( Warning ) < < " Seek failed " ;
}
2021-08-23 21:21:08 +02:00
2018-02-27 18:06:05 +01:00
}
2024-08-03 00:05:06 +02:00
void GstEngine : : PlayDone ( const GstStateChangeReturn ret , const bool pause , const quint64 offset_nanosec , const int pipeline_id ) {
2018-06-28 01:15:32 +02:00
if ( ! current_pipeline_ | | pipeline_id ! = current_pipeline_ - > id ( ) ) {
2018-02-27 18:06:05 +01:00
return ;
2018-06-28 01:15:32 +02:00
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
if ( ret = = GST_STATE_CHANGE_FAILURE ) {
// Failure, but we got a redirection URL - try loading that instead
2024-09-02 22:27:45 +02:00
GstEnginePipelinePtr old_pipeline = current_pipeline_ ;
current_pipeline_ = GstEnginePipelinePtr ( ) ;
QByteArray redirect_url ;
{
QMutexLocker l ( old_pipeline - > mutex_redirect_url ( ) ) ;
redirect_url = old_pipeline - > redirect_url ( ) ;
redirect_url . detach ( ) ;
}
QByteArray gst_url ;
{
QMutexLocker l ( old_pipeline - > mutex_url ( ) ) ;
gst_url = old_pipeline - > gst_url ( ) ;
gst_url . detach ( ) ;
}
if ( ! redirect_url . isEmpty ( ) & & redirect_url ! = gst_url ) {
2018-06-28 01:15:32 +02:00
qLog ( Info ) < < " Redirecting to " < < redirect_url ;
2024-09-02 22:27:45 +02:00
QUrl media_url ;
QUrl stream_url ;
{
QMutexLocker l ( old_pipeline - > mutex_url ( ) ) ;
media_url = old_pipeline - > media_url ( ) ;
media_url . detach ( ) ;
stream_url = old_pipeline - > stream_url ( ) ;
stream_url . detach ( ) ;
2024-08-03 00:05:06 +02:00
}
2024-09-02 22:27:45 +02:00
current_pipeline_ = CreatePipeline ( media_url , stream_url , redirect_url , end_nanosec_ , old_pipeline - > ebur128_loudness_normalizing_gain_db ( ) ) ;
FinishPipeline ( old_pipeline ) ;
2024-08-03 00:05:06 +02:00
Play ( pause , offset_nanosec ) ;
2018-06-28 01:15:32 +02:00
return ;
}
// Failure - give up
qLog ( Warning ) < < " Could not set thread to PLAYING. " ;
2024-09-02 22:27:45 +02:00
FinishPipeline ( old_pipeline ) ;
2018-02-27 18:06:05 +01:00
BufferingFinished ( ) ;
2018-06-28 01:15:32 +02:00
return ;
2018-02-27 18:06:05 +01:00
}
2024-08-03 00:05:06 +02:00
if ( ! pause ) {
StartTimers ( ) ;
2018-06-28 01:15:32 +02:00
}
2024-08-25 01:06:30 +02:00
Q_EMIT StateChanged ( pause ? State : : Paused : State : : Playing ) ;
2024-08-03 00:05:06 +02:00
2018-09-22 23:13:56 +02:00
// We've successfully started playing a media stream with this url
2024-08-25 01:06:30 +02:00
Q_EMIT ValidSongRequested ( stream_url_ ) ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
void GstEngine : : BufferingStarted ( ) {
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
if ( buffering_task_id_ ! = - 1 ) {
task_manager_ - > SetTaskFinished ( buffering_task_id_ ) ;
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
buffering_task_id_ = task_manager_ - > StartTask ( tr ( " Buffering " ) ) ;
task_manager_ - > SetTaskProgress ( buffering_task_id_ , 0 , 100 ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
}
2018-02-27 18:06:05 +01:00
2019-09-15 20:27:32 +02:00
void GstEngine : : BufferingProgress ( const int percent ) {
2018-06-28 01:15:32 +02:00
task_manager_ - > SetTaskProgress ( buffering_task_id_ , percent , 100 ) ;
}
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
void GstEngine : : BufferingFinished ( ) {
2023-04-21 15:07:17 +02:00
2018-06-28 01:15:32 +02:00
if ( buffering_task_id_ ! = - 1 ) {
task_manager_ - > SetTaskFinished ( buffering_task_id_ ) ;
buffering_task_id_ = - 1 ;
}
2023-04-21 15:07:17 +02:00
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
QByteArray GstEngine : : FixupUrl ( const QUrl & url ) {
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-06-28 01:15:32 +02:00
QByteArray uri ;
// It's a file:// url with a hostname set.
// QUrl::fromLocalFile does this when given a \\host\share\file path on Windows.
// Munge it back into a path that gstreamer will recognise.
2023-01-10 18:26:42 +01:00
if ( url . isLocalFile ( ) & & ! url . host ( ) . isEmpty ( ) ) {
2024-09-07 04:24:14 +02:00
QString str = " file://// " _L1 + url . host ( ) + url . path ( ) ;
2020-04-30 17:32:31 +02:00
uri = str . toUtf8 ( ) ;
2018-06-28 01:15:32 +02:00
}
2024-09-07 04:24:14 +02:00
else if ( url . scheme ( ) = = " cdda " _L1 ) {
2018-06-28 01:15:32 +02:00
QString str ;
if ( url . path ( ) . isEmpty ( ) ) {
str = url . toString ( ) ;
2024-09-07 04:24:14 +02:00
str . remove ( str . lastIndexOf ( u ' a ' ) , 1 ) ;
2018-06-28 01:15:32 +02:00
}
else {
// Currently, Gstreamer can't handle input CD devices inside cdda URL.
2022-08-28 02:44:37 +02:00
// So we handle them ourselves: we extract the track number and re-create a URL with only cdda:// + the track number (which can be handled by Gstreamer).
2018-06-28 01:15:32 +02:00
// We keep the device in mind, and we will set it later using SourceSetupCallback
2024-09-07 04:24:14 +02:00
QStringList path = url . path ( ) . split ( u ' / ' ) ;
2024-04-09 23:20:26 +02:00
str = QStringLiteral ( " cdda://%1 " ) . arg ( path . takeLast ( ) ) ;
2024-09-07 04:24:14 +02:00
QString device = path . join ( u ' / ' ) ;
2018-10-22 20:40:02 +02:00
if ( current_pipeline_ ) current_pipeline_ - > SetSourceDevice ( device ) ;
2018-06-28 01:15:32 +02:00
}
2020-04-30 17:32:31 +02:00
uri = str . toUtf8 ( ) ;
2018-06-28 01:15:32 +02:00
}
else {
uri = url . toEncoded ( ) ;
}
return uri ;
}
2024-08-11 17:37:23 +02:00
void GstEngine : : StartFadeout ( GstEnginePipelinePtr pipeline ) {
2018-06-28 01:15:32 +02:00
2024-08-03 00:05:06 +02:00
if ( fadeout_pipelines_ . contains ( pipeline - > id ( ) ) ) {
return ;
}
2024-09-02 22:27:45 +02:00
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : FaderFinished , this , & GstEngine : : FadeoutPauseFinished ) ;
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : EndOfStreamReached , this , & GstEngine : : EndOfStreamReached ) ;
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : MetadataFound , this , & GstEngine : : NewMetaData ) ;
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : BufferingStarted , this , & GstEngine : : BufferingStarted ) ;
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : BufferingProgress , this , & GstEngine : : BufferingProgress ) ;
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : BufferingFinished , this , & GstEngine : : BufferingFinished ) ;
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : VolumeChanged , this , & EngineBase : : UpdateVolume ) ;
QObject : : disconnect ( & * pipeline , & GstEnginePipeline : : AboutToFinish , this , & EngineBase : : EmitAboutToFinish ) ;
2024-08-11 15:53:41 +02:00
2024-08-03 00:05:06 +02:00
fadeout_pipelines_ . insert ( pipeline - > id ( ) , pipeline ) ;
pipeline - > RemoveAllBufferConsumers ( ) ;
2018-06-28 01:15:32 +02:00
2024-08-03 00:05:06 +02:00
pipeline - > StartFader ( fadeout_duration_nanosec_ , QTimeLine : : Backward ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : FaderFinished , this , & GstEngine : : FadeoutFinished ) ;
2018-06-28 01:15:32 +02:00
}
void GstEngine : : StartFadeoutPause ( ) {
2024-08-03 00:05:06 +02:00
if ( ! current_pipeline_ | | fadeout_pipelines_ . contains ( current_pipeline_ - > id ( ) ) ) return ;
2018-06-28 01:15:32 +02:00
2024-08-03 00:05:06 +02:00
fadeout_pause_pipeline_ = current_pipeline_ ;
2023-07-21 05:55:24 +02:00
QObject : : connect ( & * fadeout_pause_pipeline_ , & GstEnginePipeline : : FaderFinished , this , & GstEngine : : FadeoutPauseFinished ) ;
2024-08-03 00:05:06 +02:00
fadeout_pause_pipeline_ - > StartFader ( fadeout_pause_duration_nanosec_ , QTimeLine : : Backward , QEasingCurve : : Linear , false ) ;
}
void GstEngine : : StopFadeoutPause ( ) {
if ( ! fadeout_pause_pipeline_ ) return ;
QObject : : disconnect ( & * fadeout_pause_pipeline_ , & GstEnginePipeline : : FaderFinished , this , & GstEngine : : FadeoutPauseFinished ) ;
has_faded_out_to_pause_ = true ;
fadeout_pause_pipeline_ = GstEnginePipelinePtr ( ) ;
2018-06-28 01:15:32 +02:00
}
void GstEngine : : StartTimers ( ) {
2023-04-22 03:54:09 +02:00
2018-06-28 01:15:32 +02:00
StopTimers ( ) ;
timer_id_ = startTimer ( kTimerIntervalNanosec / kNsecPerMsec ) ;
2023-04-22 03:54:09 +02:00
2018-06-28 01:15:32 +02:00
}
void GstEngine : : StopTimers ( ) {
2023-04-22 03:54:09 +02:00
2018-06-28 01:15:32 +02:00
if ( timer_id_ ! = - 1 ) {
killTimer ( timer_id_ ) ;
timer_id_ = - 1 ;
}
2023-04-22 03:54:09 +02:00
2018-06-28 01:15:32 +02:00
}
2024-08-03 00:05:06 +02:00
GstEnginePipelinePtr GstEngine : : CreatePipeline ( ) {
2018-02-27 18:06:05 +01:00
2020-10-17 17:29:09 +02:00
EnsureInitialized ( ) ;
2018-02-27 18:06:05 +01:00
2024-08-11 18:40:07 +02:00
GstEnginePipelinePtr pipeline = make_shared < GstEnginePipeline > ( ) ;
pipeline - > set_output_device ( output_ , device_ ) ;
pipeline - > set_exclusive_mode ( exclusive_mode_ ) ;
pipeline - > set_volume_enabled ( volume_control_ ) ;
pipeline - > set_stereo_balancer_enabled ( stereo_balancer_enabled_ ) ;
pipeline - > set_equalizer_enabled ( equalizer_enabled_ ) ;
pipeline - > set_replaygain ( rg_enabled_ , rg_mode_ , rg_preamp_ , rg_fallbackgain_ , rg_compression_ ) ;
pipeline - > set_ebur128_loudness_normalization ( ebur128_loudness_normalization_ ) ;
pipeline - > set_buffer_duration_nanosec ( buffer_duration_nanosec_ ) ;
pipeline - > set_buffer_low_watermark ( buffer_low_watermark_ ) ;
pipeline - > set_buffer_high_watermark ( buffer_high_watermark_ ) ;
pipeline - > set_proxy_settings ( proxy_address_ , proxy_authentication_ , proxy_user_ , proxy_pass_ ) ;
pipeline - > set_channels ( channels_enabled_ , channels_ ) ;
pipeline - > set_bs2b_enabled ( bs2b_enabled_ ) ;
pipeline - > set_strict_ssl_enabled ( strict_ssl_enabled_ ) ;
pipeline - > set_fading_enabled ( fadeout_enabled_ | | autocrossfade_enabled_ | | fadeout_pause_enabled_ ) ;
2018-02-27 18:06:05 +01:00
2022-06-04 15:51:35 +02:00
# ifdef HAVE_SPOTIFY
2024-08-11 18:40:07 +02:00
pipeline - > set_spotify_login ( spotify_username_ , spotify_password_ ) ;
2022-06-04 15:51:35 +02:00
# endif
2024-08-11 18:40:07 +02:00
pipeline - > AddBufferConsumer ( this ) ;
2024-04-23 17:15:42 +02:00
for ( GstBufferConsumer * consumer : std : : as_const ( buffer_consumers_ ) ) {
2024-08-11 18:40:07 +02:00
pipeline - > AddBufferConsumer ( consumer ) ;
2018-02-27 18:06:05 +01:00
}
2024-08-11 18:40:07 +02:00
QObject : : connect ( & * pipeline , & GstEnginePipeline : : EndOfStreamReached , this , & GstEngine : : EndOfStreamReached ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : Error , this , & GstEngine : : HandlePipelineError ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : MetadataFound , this , & GstEngine : : NewMetaData ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : BufferingStarted , this , & GstEngine : : BufferingStarted ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : BufferingProgress , this , & GstEngine : : BufferingProgress ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : BufferingFinished , this , & GstEngine : : BufferingFinished ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : VolumeChanged , this , & EngineBase : : UpdateVolume ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : AboutToFinish , this , & EngineBase : : EmitAboutToFinish ) ;
2018-02-27 18:06:05 +01:00
2024-08-11 18:40:07 +02:00
return pipeline ;
2018-04-02 03:43:56 +02:00
}
2024-08-03 00:05:06 +02:00
GstEnginePipelinePtr GstEngine : : CreatePipeline ( const QUrl & media_url , const QUrl & stream_url , const QByteArray & gst_url , const qint64 end_nanosec , const double ebur128_loudness_normalizing_gain_db ) {
2018-04-02 03:43:56 +02:00
2024-08-03 00:05:06 +02:00
GstEnginePipelinePtr ret = CreatePipeline ( ) ;
2021-10-16 21:28:56 +02:00
QString error ;
2023-06-27 04:05:01 +02:00
if ( ! ret - > InitFromUrl ( media_url , stream_url , gst_url , end_nanosec , ebur128_loudness_normalizing_gain_db , error ) ) {
2021-10-16 21:28:56 +02:00
ret . reset ( ) ;
2024-08-25 01:06:30 +02:00
Q_EMIT Error ( error ) ;
Q_EMIT StateChanged ( State : : Error ) ;
Q_EMIT FatalError ( ) ;
2021-10-16 21:28:56 +02:00
}
2018-02-27 18:06:05 +01:00
return ret ;
2018-03-10 13:02:56 +01:00
2018-02-27 18:06:05 +01:00
}
2024-08-03 00:05:06 +02:00
void GstEngine : : FinishPipeline ( GstEnginePipelinePtr pipeline ) {
const int pipeline_id = pipeline - > id ( ) ;
2024-08-11 15:53:41 +02:00
QObject : : disconnect ( & * pipeline , nullptr , this , nullptr ) ;
2024-08-03 00:05:06 +02:00
if ( ! pipeline - > Finish ( ) & & ! old_pipelines_ . contains ( pipeline - > id ( ) ) ) {
old_pipelines_ . insert ( pipeline_id , pipeline ) ;
QObject : : connect ( & * pipeline , & GstEnginePipeline : : Finished , this , [ this , pipeline_id ] ( ) {
PipelineFinished ( pipeline_id ) ;
} ) ;
}
}
void GstEngine : : PipelineFinished ( const int pipeline_id ) {
qLog ( Debug ) < < " Pipeline " < < pipeline_id < < " finished " ;
2024-08-23 20:30:59 +02:00
GstEnginePipelinePtr pipeline = old_pipelines_ . value ( pipeline_id ) ;
2024-08-03 00:05:06 +02:00
old_pipelines_ . remove ( pipeline_id ) ;
if ( pipeline = = fadeout_pause_pipeline_ ) {
StopFadeoutPause ( ) ;
}
pipeline = GstEnginePipelinePtr ( ) ;
if ( current_pipeline_ & & old_pipelines_ . isEmpty ( ) & & delayed_state_ ! = State : : Empty ) {
switch ( delayed_state_ ) {
case State : : Playing :
Play ( delayed_state_pause_ , delayed_state_offset_nanosec_ ) ;
break ;
case State : : Paused :
Pause ( ) ;
break ;
default :
break ;
}
delayed_state_ = State : : Empty ;
delayed_state_pause_ = false ;
delayed_state_offset_nanosec_ = 0 ;
}
qLog ( Debug ) < < ( current_pipeline_ ? 1 : 0 ) + old_pipelines_ . count ( ) < < " pipelines are active " ;
2024-09-02 22:27:45 +02:00
if ( ! current_pipeline_ & & old_pipelines_ . isEmpty ( ) ) {
Q_EMIT Finished ( ) ;
}
2024-08-03 00:05:06 +02:00
}
2019-09-15 20:27:32 +02:00
void GstEngine : : UpdateScope ( const int chunk_length ) {
2018-02-27 18:06:05 +01:00
2023-04-22 19:13:42 +02:00
using sample_type = EngineBase : : Scope : : value_type ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
// Prevent dbz or invalid chunk size
if ( ! GST_CLOCK_TIME_IS_VALID ( GST_BUFFER_DURATION ( latest_buffer_ ) ) ) return ;
if ( GST_BUFFER_DURATION ( latest_buffer_ ) = = 0 ) return ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
GstMapInfo map ;
gst_buffer_map ( latest_buffer_ , & map , GST_MAP_READ ) ;
2018-03-10 13:02:56 +01:00
2018-06-28 01:15:32 +02:00
// Determine where to split the buffer
2021-03-21 18:53:02 +01:00
int chunk_density = static_cast < int > ( ( map . size * kNsecPerMsec ) / GST_BUFFER_DURATION ( latest_buffer_ ) ) ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
int chunk_size = chunk_length * chunk_density ;
2018-02-27 18:06:05 +01:00
2018-06-28 01:15:32 +02:00
// In case a buffer doesn't arrive in time
if ( scope_chunk_ > = scope_chunks_ ) {
scope_chunk_ = 0 ;
2020-05-24 23:21:26 +02:00
gst_buffer_unmap ( latest_buffer_ , & map ) ;
2018-06-28 01:15:32 +02:00
return ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
const sample_type * source = reinterpret_cast < sample_type * > ( map . data ) ;
sample_type * dest = scope_ . data ( ) ;
source + = ( chunk_size / sizeof ( sample_type ) ) * scope_chunk_ ;
2018-02-27 18:06:05 +01:00
2021-08-24 17:52:08 +02:00
size_t bytes = 0 ;
2018-04-07 12:19:01 +02:00
2018-06-28 01:15:32 +02:00
// Make sure we don't go beyond the end of the buffer
if ( scope_chunk_ = = scope_chunks_ - 1 ) {
2023-04-22 19:13:42 +02:00
bytes = qMin ( static_cast < EngineBase : : Scope : : size_type > ( map . size - ( chunk_size * scope_chunk_ ) ) , scope_ . size ( ) * sizeof ( sample_type ) ) ;
2018-06-28 01:15:32 +02:00
}
else {
2023-04-22 19:13:42 +02:00
bytes = qMin ( static_cast < EngineBase : : Scope : : size_type > ( chunk_size ) , scope_ . size ( ) * sizeof ( sample_type ) ) ;
2018-02-27 18:06:05 +01:00
}
2018-06-28 01:15:32 +02:00
scope_chunk_ + + ;
2019-10-19 01:45:24 +02:00
2024-09-07 04:24:14 +02:00
if ( buffer_format_ . startsWith ( " S16LE " _L1 ) | |
buffer_format_ . startsWith ( " U16LE " _L1 ) | |
buffer_format_ . startsWith ( " S24LE " _L1 ) | |
buffer_format_ . startsWith ( " S24_32LE " _L1 ) | |
buffer_format_ . startsWith ( " S32LE " _L1 ) | |
buffer_format_ . startsWith ( " F32LE " _L1 )
2020-07-04 00:29:11 +02:00
) {
2019-10-19 01:45:24 +02:00
memcpy ( dest , source , bytes ) ;
2019-10-20 18:52:58 +02:00
}
else {
2019-10-19 01:45:24 +02:00
memset ( dest , 0 , bytes ) ;
2019-10-20 18:52:58 +02:00
}
2018-06-07 19:38:40 +02:00
2018-06-28 01:15:32 +02:00
gst_buffer_unmap ( latest_buffer_ , & map ) ;
2018-06-07 19:38:40 +02:00
2018-06-28 01:15:32 +02:00
if ( scope_chunk_ = = scope_chunks_ ) {
gst_buffer_unref ( latest_buffer_ ) ;
latest_buffer_ = nullptr ;
2019-10-19 01:45:24 +02:00
buffer_format_ . clear ( ) ;
2018-06-28 01:15:32 +02:00
}
2018-06-07 19:38:40 +02:00
}
2020-10-21 00:07:58 +02:00
void GstEngine : : StreamDiscovered ( GstDiscoverer * , GstDiscovererInfo * info , GError * , gpointer self ) {
GstEngine * instance = reinterpret_cast < GstEngine * > ( self ) ;
if ( ! instance - > current_pipeline_ ) return ;
2023-04-21 16:20:00 +02:00
const QByteArray discovered_url = gst_discoverer_info_get_uri ( info ) ;
2020-10-21 00:07:58 +02:00
GstDiscovererResult result = gst_discoverer_info_get_result ( info ) ;
if ( result ! = GST_DISCOVERER_OK ) {
2024-04-11 02:56:01 +02:00
const QString error_message = GSTdiscovererErrorMessage ( result ) ;
qLog ( Error ) < < QStringLiteral ( " Stream discovery for %1 failed: %2 " ) . arg ( QString : : fromUtf8 ( discovered_url ) , error_message ) ;
2020-10-21 00:07:58 +02:00
return ;
}
GList * audio_streams = gst_discoverer_info_get_audio_streams ( info ) ;
if ( audio_streams ) {
GstDiscovererStreamInfo * stream_info = reinterpret_cast < GstDiscovererStreamInfo * > ( g_list_first ( audio_streams ) - > data ) ;
2023-04-22 19:13:42 +02:00
EngineMetadata engine_metadata ;
2024-09-02 22:27:45 +02:00
bool match = false ;
{
QMutexLocker l ( instance - > current_pipeline_ - > mutex_url ( ) ) ;
if ( discovered_url = = instance - > current_pipeline_ - > gst_url ( ) ) {
match = true ;
engine_metadata . type = EngineMetadata : : Type : : Current ;
engine_metadata . media_url = instance - > current_pipeline_ - > media_url ( ) ;
engine_metadata . stream_url = instance - > current_pipeline_ - > stream_url ( ) ;
}
2020-10-21 00:07:58 +02:00
}
2024-09-02 22:27:45 +02:00
if ( ! match ) {
QMutexLocker l ( instance - > current_pipeline_ - > mutex_next_url ( ) ) ;
if ( discovered_url = = instance - > current_pipeline_ - > next_gst_url ( ) ) {
engine_metadata . type = EngineMetadata : : Type : : Next ;
engine_metadata . media_url = instance - > current_pipeline_ - > next_media_url ( ) ;
engine_metadata . stream_url = instance - > current_pipeline_ - > next_stream_url ( ) ;
}
2020-10-21 00:07:58 +02:00
}
2023-04-22 19:13:42 +02:00
engine_metadata . samplerate = static_cast < int > ( gst_discoverer_audio_info_get_sample_rate ( GST_DISCOVERER_AUDIO_INFO ( stream_info ) ) ) ;
engine_metadata . bitdepth = static_cast < int > ( gst_discoverer_audio_info_get_depth ( GST_DISCOVERER_AUDIO_INFO ( stream_info ) ) ) ;
engine_metadata . bitrate = static_cast < int > ( gst_discoverer_audio_info_get_bitrate ( GST_DISCOVERER_AUDIO_INFO ( stream_info ) ) / 1000 ) ;
2020-10-21 00:07:58 +02:00
GstCaps * caps = gst_discoverer_stream_info_get_caps ( stream_info ) ;
2021-01-08 22:38:37 +01:00
const guint caps_size = gst_caps_get_size ( caps ) ;
for ( guint i = 0 ; i < caps_size ; + + i ) {
GstStructure * gst_structure = gst_caps_get_structure ( caps , i ) ;
if ( ! gst_structure ) continue ;
2024-04-11 02:56:01 +02:00
QString mimetype = QString : : fromUtf8 ( gst_structure_get_name ( gst_structure ) ) ;
2024-09-07 04:24:14 +02:00
if ( ! mimetype . isEmpty ( ) & & mimetype ! = " audio/mpeg " _L1 ) {
2023-04-22 19:13:42 +02:00
engine_metadata . filetype = Song : : FiletypeByMimetype ( mimetype ) ;
if ( engine_metadata . filetype = = Song : : FileType : : Unknown ) {
2021-01-08 22:38:37 +01:00
qLog ( Error ) < < " Unknown mimetype " < < mimetype ;
}
}
}
2023-04-22 19:13:42 +02:00
if ( engine_metadata . filetype = = Song : : FileType : : Unknown ) {
2021-01-08 22:38:37 +01:00
gchar * codec_description = gst_pb_utils_get_codec_description ( caps ) ;
2024-04-11 02:56:01 +02:00
QString filetype_description = ( codec_description ? QString : : fromUtf8 ( codec_description ) : QString ( ) ) ;
2021-01-08 22:38:37 +01:00
g_free ( codec_description ) ;
if ( ! filetype_description . isEmpty ( ) ) {
2023-04-22 19:13:42 +02:00
engine_metadata . filetype = Song : : FiletypeByDescription ( filetype_description ) ;
if ( engine_metadata . filetype = = Song : : FileType : : Unknown ) {
2021-01-08 22:38:37 +01:00
qLog ( Error ) < < " Unknown filetype " < < filetype_description ;
}
}
}
2020-10-21 00:07:58 +02:00
gst_caps_unref ( caps ) ;
gst_discoverer_stream_info_list_free ( audio_streams ) ;
2023-04-22 19:13:42 +02:00
qLog ( Debug ) < < " Got stream info for " < < discovered_url + " : " < < Song : : TextForFiletype ( engine_metadata . filetype ) ;
2020-10-21 00:07:58 +02:00
2024-08-25 01:06:30 +02:00
Q_EMIT instance - > MetaData ( engine_metadata ) ;
2020-10-21 00:07:58 +02:00
}
else {
qLog ( Error ) < < " Could not detect an audio stream in " < < discovered_url ;
}
}
void GstEngine : : StreamDiscoveryFinished ( GstDiscoverer * , gpointer ) { }
QString GstEngine : : GSTdiscovererErrorMessage ( GstDiscovererResult result ) {
switch ( result ) {
2024-04-09 23:20:26 +02:00
case GST_DISCOVERER_URI_INVALID : return QStringLiteral ( " The URI is invalid " ) ;
case GST_DISCOVERER_TIMEOUT : return QStringLiteral ( " The discovery timed-out " ) ;
case GST_DISCOVERER_BUSY : return QStringLiteral ( " The discoverer was already discovering a file " ) ;
case GST_DISCOVERER_MISSING_PLUGINS : return QStringLiteral ( " Some plugins are missing for full discovery " ) ;
2020-10-21 00:07:58 +02:00
case GST_DISCOVERER_ERROR :
2024-04-09 23:20:26 +02:00
default : return QStringLiteral ( " An error happened and the GError is set " ) ;
2020-10-21 00:07:58 +02:00
}
}
2024-08-03 00:05:06 +02:00
2024-08-11 17:37:23 +02:00
bool GstEngine : : OldExclusivePipelineActive ( ) const {
2024-08-03 00:05:06 +02:00
2024-08-11 17:37:23 +02:00
if ( current_pipeline_ & & current_pipeline_ - > exclusive_mode ( ) & & ( ! fadeout_pipelines_ . isEmpty ( ) | | ! old_pipelines_ . isEmpty ( ) ) ) {
return true ;
2024-08-03 00:05:06 +02:00
}
2024-08-11 17:37:23 +02:00
for ( const GstEnginePipelinePtr & pipeline : std : : as_const ( fadeout_pipelines_ ) ) {
if ( pipeline - > exclusive_mode ( ) ) {
return true ;
}
2024-08-03 00:05:06 +02:00
}
for ( const GstEnginePipelinePtr & pipeline : std : : as_const ( old_pipelines_ ) ) {
if ( pipeline - > exclusive_mode ( ) ) {
return true ;
}
}
return false ;
}
2024-08-11 17:37:23 +02:00
bool GstEngine : : AnyExclusivePipelineActive ( ) const {
return ( current_pipeline_ & & current_pipeline_ - > exclusive_mode ( ) ) | | OldExclusivePipelineActive ( ) ;
}