Once track can be played gaplessly. But the gst pipeline position isn't reset to 0 and this causes further problems.
This commit is contained in:
parent
a759fdb2e0
commit
341d5d3722
@ -116,7 +116,7 @@ bool MediaPipeline::Init(int sample_rate, int channels) {
|
||||
|
||||
// Set size
|
||||
byte_rate_ = quint64(sample_rate) * channels * 2;
|
||||
const quint64 bytes = byte_rate_ * length_msec_ / 1000;
|
||||
const quint64 bytes = -1; //byte_rate_ * length_msec_ / 1000;
|
||||
gst_app_src_set_size(appsrc_, bytes);
|
||||
|
||||
// Ready to go
|
||||
@ -143,6 +143,12 @@ void MediaPipeline::WriteData(const char* data, qint64 length) {
|
||||
gst_app_src_push_buffer(appsrc_, buffer);
|
||||
}
|
||||
|
||||
void MediaPipeline::SendEvent() {
|
||||
GstTagList* list = gst_tag_list_new_empty();
|
||||
GstEvent* tag = gst_event_new_tag(list);
|
||||
gst_element_send_event(pipeline_, tag);
|
||||
}
|
||||
|
||||
void MediaPipeline::EndStream() {
|
||||
if (!is_initialised()) return;
|
||||
|
||||
|
@ -37,6 +37,9 @@ class MediaPipeline {
|
||||
|
||||
void WriteData(const char* data, qint64 length);
|
||||
void EndStream();
|
||||
void SendEvent();
|
||||
|
||||
int port() const { return port_; }
|
||||
|
||||
private:
|
||||
static void NeedDataCallback(GstAppSrc* src, guint length, void* data);
|
||||
|
@ -321,11 +321,14 @@ void SpotifyClient::SetPlaybackSettings(
|
||||
}
|
||||
|
||||
qLog(Debug) << "Setting playback settings: bitrate" << bitrate
|
||||
<< "normalisation" << req.volume_normalisation();
|
||||
<< "normalisation" << req.volume_normalisation()
|
||||
<< "gapless" << req.gapless();
|
||||
|
||||
sp_session_preferred_bitrate(session_, bitrate);
|
||||
sp_session_preferred_offline_bitrate(session_, bitrate, false);
|
||||
sp_session_set_volume_normalization(session_, req.volume_normalisation());
|
||||
|
||||
gapless_playback_ = req.gapless();
|
||||
}
|
||||
|
||||
void SpotifyClient::Login(const pb::spotify::LoginRequest& req) {
|
||||
@ -762,6 +765,38 @@ void SpotifyClient::MetadataUpdatedCallback(sp_session* session) {
|
||||
me->pending_playback_requests_) {
|
||||
me->TryPlaybackAgain(playback);
|
||||
}
|
||||
if (me->gapless_playback_) {
|
||||
for (const PrefetchTrackRequest req : me->prefetched_tracks_.values()) {
|
||||
me->ContinueGaplessPlayback(req);
|
||||
}
|
||||
}
|
||||
|
||||
qLog(Debug) << "MetadataUpdatedCallback";
|
||||
}
|
||||
|
||||
void SpotifyClient::ContinueGaplessPlayback(const PrefetchTrackRequest& req) {
|
||||
if (!gapless_playback_)
|
||||
return;
|
||||
|
||||
// If the track was not loaded then we have to come back later
|
||||
if (!sp_track_is_loaded(req.track_)) {
|
||||
qLog(Debug) << "Playback track not loaded yet, will try again later";
|
||||
return;
|
||||
}
|
||||
|
||||
sp_error error = sp_session_player_load(session_, req.track_);
|
||||
qLog(Debug) << Q_FUNC_INFO << sp_error_message(error);
|
||||
|
||||
//int port = media_pipeline_->port();
|
||||
//media_pipeline_.reset(new MediaPipeline(port,
|
||||
// sp_track_duration(req.track_)));
|
||||
|
||||
error = sp_session_player_play(session_, true);
|
||||
qLog(Debug) << Q_FUNC_INFO << sp_error_message(error);
|
||||
|
||||
sp_link_release(req.link_);
|
||||
|
||||
prefetched_tracks_.clear();
|
||||
}
|
||||
|
||||
int SpotifyClient::MusicDeliveryCallback(sp_session* session,
|
||||
@ -791,8 +826,23 @@ int SpotifyClient::MusicDeliveryCallback(sp_session* session,
|
||||
return 0;
|
||||
}
|
||||
|
||||
me->media_pipeline_->WriteData(reinterpret_cast<const char*>(frames),
|
||||
num_frames * format->channels * 2);
|
||||
const char* buf = reinterpret_cast<const char*>(frames);
|
||||
bool buf_null = true;
|
||||
|
||||
// Spotify sends a buffer with only null at the end of each track. To get gapless playback
|
||||
// we have to check if the buffer is empty and discard this data.
|
||||
if (me->gapless_playback_) {
|
||||
for (int i=0;i<num_frames;++i) {
|
||||
if (buf[i] != '\0') {
|
||||
buf_null = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!me->gapless_playback_ || !buf_null) {
|
||||
me->media_pipeline_->WriteData(buf, num_frames * format->channels * 2);
|
||||
}
|
||||
|
||||
return num_frames;
|
||||
}
|
||||
@ -801,7 +851,13 @@ void SpotifyClient::EndOfTrackCallback(sp_session* session) {
|
||||
SpotifyClient* me =
|
||||
reinterpret_cast<SpotifyClient*>(sp_session_userdata(session));
|
||||
|
||||
me->media_pipeline_.reset();
|
||||
if (me->gapless_playback_ && !me->prefetched_tracks_.isEmpty()) {
|
||||
for (const PrefetchTrackRequest req : me->prefetched_tracks_.values()) {
|
||||
me->ContinueGaplessPlayback(req);
|
||||
}
|
||||
} else {
|
||||
me->media_pipeline_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void SpotifyClient::StreamingErrorCallback(sp_session* session,
|
||||
@ -909,34 +965,20 @@ int SpotifyClient::GetDownloadProgress(sp_playlist* playlist) {
|
||||
void SpotifyClient::StartPlayback(const pb::spotify::PlaybackRequest& req) {
|
||||
QString uri = QString::fromStdString(req.track_uri());
|
||||
|
||||
sp_link* link;
|
||||
sp_track* track;
|
||||
|
||||
if (prefetched_tracks_.contains(uri)) {
|
||||
PrefetchTrackRequest prefetch_request;
|
||||
prefetch_request = prefetched_tracks_.take(uri);
|
||||
link = prefetch_request.link_;
|
||||
track = prefetch_request.track_;
|
||||
|
||||
qLog(Debug) << "Using prefetched track";
|
||||
} else {
|
||||
// Get a link object from the URI
|
||||
link = sp_link_create_from_string(req.track_uri().c_str());
|
||||
if (!link) {
|
||||
SendPlaybackError("Invalid Spotify URI");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the track from the link
|
||||
track = sp_link_as_track(link);
|
||||
if (!track) {
|
||||
SendPlaybackError("Spotify URI was not a track");
|
||||
sp_link_release(link);
|
||||
return;
|
||||
}
|
||||
// Get a link object from the URI
|
||||
sp_link* link = sp_link_create_from_string(req.track_uri().c_str());
|
||||
if (!link) {
|
||||
SendPlaybackError("Invalid Spotify URI");
|
||||
return;
|
||||
}
|
||||
|
||||
prefetched_tracks_.clear();
|
||||
// Get the track from the link
|
||||
sp_track* track = sp_link_as_track(link);
|
||||
if (!track) {
|
||||
SendPlaybackError("Spotify URI was not a track");
|
||||
sp_link_release(link);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingPlaybackRequest pending_playback;
|
||||
pending_playback.request_ = req;
|
||||
@ -1019,6 +1061,9 @@ void SpotifyClient::PrefetchTrack(const pb::spotify::PrefetchRequest &req) {
|
||||
prefetch_request.track_ = track;
|
||||
|
||||
prefetched_tracks_.insert(uri, prefetch_request);
|
||||
|
||||
qDebug() << "Sending event";
|
||||
media_pipeline_->SendEvent();
|
||||
}
|
||||
|
||||
void SpotifyClient::SendPlaybackError(const QString& error) {
|
||||
|
@ -180,6 +180,8 @@ class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
|
||||
void SendDownloadProgress(pb::spotify::PlaylistType type, int index,
|
||||
int download_progress);
|
||||
|
||||
void ContinueGaplessPlayback(const PrefetchTrackRequest& req);
|
||||
|
||||
QByteArray api_key_;
|
||||
|
||||
QTcpSocket* protocol_socket_;
|
||||
@ -202,6 +204,7 @@ class SpotifyClient : public AbstractMessageHandler<pb::spotify::Message> {
|
||||
QMap<sp_toplistbrowse*, pb::spotify::BrowseToplistRequest>
|
||||
pending_toplist_browses_;
|
||||
QMap<QString, PrefetchTrackRequest> prefetched_tracks_;
|
||||
bool gapless_playback_;
|
||||
|
||||
QMap<sp_search*, QList<sp_albumbrowse*>> pending_search_album_browses_;
|
||||
QMap<sp_albumbrowse*, sp_search*> pending_search_album_browse_responses_;
|
||||
|
@ -39,7 +39,7 @@ static Level sDefaultLevel = Level_Debug;
|
||||
static QMap<QString, Level>* sClassLevels = nullptr;
|
||||
static QIODevice* sNullDevice = nullptr;
|
||||
|
||||
const char* kDefaultLogLevels = "GstEnginePipeline:2,*:3";
|
||||
const char* kDefaultLogLevels = "GstEnginePipeline:3,*:3";
|
||||
|
||||
static const char* kMessageHandlerMagic = "__logging_message__";
|
||||
static const int kMessageHandlerMagicLength = strlen(kMessageHandlerMagic);
|
||||
|
@ -190,6 +190,7 @@ enum Bitrate {
|
||||
message PlaybackSettings {
|
||||
optional Bitrate bitrate = 1 [default = Bitrate320k];
|
||||
optional bool volume_normalisation = 2 [default = false];
|
||||
optional bool gapless = 3 [default = false];
|
||||
}
|
||||
|
||||
message PauseRequest {
|
||||
|
@ -657,6 +657,12 @@ void GstEngine::timerEvent(QTimerEvent* e) {
|
||||
|
||||
// only if we know the length of the current stream...
|
||||
if (current_length > 0) {
|
||||
if (remaining < 0) {
|
||||
qDebug() << "Remaning 0" << current_pipeline_->has_next_valid_url();
|
||||
EndOfStreamReached(current_pipeline_->id(), current_pipeline_->has_next_valid_url());
|
||||
//current_pipeline_->SpotifyMovedToNextTrack();
|
||||
return;
|
||||
}
|
||||
// emit TrackAboutToEnd when we're a few seconds away from finishing
|
||||
if (remaining < gap + fudge) {
|
||||
EmitAboutToEnd();
|
||||
@ -699,10 +705,12 @@ void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
|
||||
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id)
|
||||
return;
|
||||
|
||||
/*if (!has_next_track) {
|
||||
if (!has_next_track) {
|
||||
current_pipeline_.reset();
|
||||
BufferingFinished();
|
||||
}*/
|
||||
} else {
|
||||
current_pipeline_->SpotifyMovedToNextTrack();
|
||||
}
|
||||
emit TrackEnded();
|
||||
}
|
||||
|
||||
|
@ -487,12 +487,12 @@ GstBusSyncReply GstEnginePipeline::BusCallbackSync(GstBus*, GstMessage* msg,
|
||||
gpointer self) {
|
||||
GstEnginePipeline* instance = reinterpret_cast<GstEnginePipeline*>(self);
|
||||
|
||||
//qLog(Debug) << instance->id() << "sync bus message"
|
||||
// << GST_MESSAGE_TYPE_NAME(msg);
|
||||
qLog(Debug) << instance->id() << "sync bus message"
|
||||
<< GST_MESSAGE_TYPE_NAME(msg);
|
||||
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
emit instance->EndOfStreamReached(instance->id(), false);
|
||||
emit instance->EndOfStreamReached(instance->id(), instance->has_next_valid_url());
|
||||
break;
|
||||
|
||||
case GST_MESSAGE_TAG:
|
||||
@ -1173,3 +1173,18 @@ void GstEnginePipeline::SetNextUrl(const QUrl& url, qint64 beginning_nanosec,
|
||||
Q_ARG(QUrl, url));
|
||||
}
|
||||
}
|
||||
|
||||
void GstEnginePipeline::SpotifyMovedToNextTrack() {
|
||||
url_ = next_url_;
|
||||
end_offset_nanosec_ = next_end_offset_nanosec_;
|
||||
next_url_ = QUrl();
|
||||
next_beginning_offset_nanosec_ = 0;
|
||||
next_end_offset_nanosec_ = 0;
|
||||
|
||||
// This function gets called when the source has been drained, even if the
|
||||
// song hasn't finished playing yet. We'll get a new stream when it really
|
||||
// does finish, so emit TrackEnded then.
|
||||
emit_track_ended_on_stream_start_ = true;
|
||||
|
||||
gst_element_seek_simple(pipeline_, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH, 0);
|
||||
}
|
||||
|
@ -82,6 +82,8 @@ class GstEnginePipeline : public QObject {
|
||||
void SetNextUrl(const QUrl& url, qint64 beginning_nanosec,
|
||||
qint64 end_nanosec);
|
||||
bool has_next_valid_url() const { return next_url_.isValid(); }
|
||||
QUrl next_url() const { return next_url_; }
|
||||
void SpotifyMovedToNextTrack();
|
||||
|
||||
// Get information about the music playback
|
||||
QUrl url() const { return url_; }
|
||||
|
@ -77,7 +77,8 @@ void SpotifyServer::SendOrQueueMessage(const pb::spotify::Message& message) {
|
||||
|
||||
void SpotifyServer::Login(const QString& username, const QString& password,
|
||||
pb::spotify::Bitrate bitrate,
|
||||
bool volume_normalisation) {
|
||||
bool volume_normalisation,
|
||||
bool gapless) {
|
||||
pb::spotify::Message message;
|
||||
|
||||
pb::spotify::LoginRequest* request = message.mutable_login_request();
|
||||
@ -88,6 +89,7 @@ void SpotifyServer::Login(const QString& username, const QString& password,
|
||||
request->mutable_playback_settings()->set_bitrate(bitrate);
|
||||
request->mutable_playback_settings()->set_volume_normalisation(
|
||||
volume_normalisation);
|
||||
request->mutable_playback_settings()->set_gapless(gapless);
|
||||
|
||||
SendOrQueueMessage(message);
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class SpotifyServer : public AbstractMessageHandler<pb::spotify::Message> {
|
||||
|
||||
void Init();
|
||||
void Login(const QString& username, const QString& password,
|
||||
pb::spotify::Bitrate bitrate, bool volume_normalisation);
|
||||
pb::spotify::Bitrate bitrate, bool volume_normalisation, bool gapless);
|
||||
|
||||
void LoadStarred();
|
||||
void SyncStarred();
|
||||
|
@ -86,7 +86,8 @@ SpotifyService::SpotifyService(Application* app, InternetModel* parent)
|
||||
search_delay_(new QTimer(this)),
|
||||
login_state_(LoginState_OtherError),
|
||||
bitrate_(pb::spotify::Bitrate320k),
|
||||
volume_normalisation_(false) {
|
||||
volume_normalisation_(false),
|
||||
gapless_(false) {
|
||||
// Build the search path for the binary blob.
|
||||
// Look for one distributed alongside clementine first, then check in the
|
||||
// user's home directory for any that have been downloaded.
|
||||
@ -249,6 +250,7 @@ void SpotifyService::ReloadSettings() {
|
||||
bitrate_ = static_cast<pb::spotify::Bitrate>(
|
||||
s.value("bitrate", pb::spotify::Bitrate320k).toInt());
|
||||
volume_normalisation_ = s.value("volume_normalisation", false).toBool();
|
||||
gapless_ = s.value("gapless", false).toBool();
|
||||
|
||||
if (server_ && blob_process_) {
|
||||
server_->SetPlaybackSettings(bitrate_, volume_normalisation_);
|
||||
@ -306,7 +308,7 @@ void SpotifyService::EnsureServerCreated(const QString& username,
|
||||
}
|
||||
|
||||
server_->Login(login_username, login_password, bitrate_,
|
||||
volume_normalisation_);
|
||||
volume_normalisation_, gapless_);
|
||||
|
||||
StartBlobProcess();
|
||||
}
|
||||
|
@ -191,6 +191,7 @@ signals:
|
||||
LoginState login_state_;
|
||||
pb::spotify::Bitrate bitrate_;
|
||||
bool volume_normalisation_;
|
||||
bool gapless_;
|
||||
};
|
||||
|
||||
#endif // INTERNET_SPOTIFY_SPOTIFYSERVICE_H_
|
||||
|
@ -111,6 +111,7 @@ void SpotifySettingsPage::Load() {
|
||||
s.value("bitrate", pb::spotify::Bitrate320k).toInt()));
|
||||
ui_->volume_normalisation->setChecked(
|
||||
s.value("volume_normalisation", false).toBool());
|
||||
ui_->gapless->setChecked(s.value("gapless", false).toBool());
|
||||
|
||||
UpdateLoginState();
|
||||
}
|
||||
@ -125,6 +126,7 @@ void SpotifySettingsPage::Save() {
|
||||
s.setValue("bitrate",
|
||||
ui_->bitrate->itemData(ui_->bitrate->currentIndex()).toInt());
|
||||
s.setValue("volume_normalisation", ui_->volume_normalisation->isChecked());
|
||||
s.setValue("gapless", ui_->gapless->isChecked());
|
||||
}
|
||||
|
||||
void SpotifySettingsPage::LoginFinished(bool success) {
|
||||
|
@ -29,7 +29,16 @@
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="margin">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
@ -135,7 +144,14 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="bitrate"/>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="gapless">
|
||||
<property name="text">
|
||||
<string>Gapless playback</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="volume_normalisation">
|
||||
<property name="text">
|
||||
<string>Use volume normalisation</string>
|
||||
@ -188,7 +204,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../data/data.qrc">:/spotify-attribution.png</pixmap>
|
||||
<pixmap resource="../../../data/data.qrc">:/spotify-attribution.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -205,7 +221,7 @@
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../../data/data.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
Loading…
x
Reference in New Issue
Block a user