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:
Andreas 2015-03-17 20:44:01 +01:00 committed by Andreas
parent a759fdb2e0
commit 341d5d3722
15 changed files with 151 additions and 45 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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) {

View File

@ -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_;

View File

@ -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);

View File

@ -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 {

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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_; }

View File

@ -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);
}

View File

@ -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();

View File

@ -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();
}

View File

@ -191,6 +191,7 @@ signals:
LoginState login_state_;
pb::spotify::Bitrate bitrate_;
bool volume_normalisation_;
bool gapless_;
};
#endif // INTERNET_SPOTIFY_SPOTIFYSERVICE_H_

View File

@ -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) {

View File

@ -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>