Use nanoseconds instead of seconds or milliseconds throughout the Player and Engine.

This commit is contained in:
David Sansome 2011-02-13 18:29:27 +00:00
parent 9aefae4e39
commit 37618dae96
10 changed files with 101 additions and 96 deletions

View File

@ -191,12 +191,12 @@ int Mpris1Player::VolumeGet() const {
return player_->GetVolume();
}
void Mpris1Player::PositionSet(int pos) {
player_->Seek(pos/1000);
void Mpris1Player::PositionSet(int pos_msec) {
player_->Seek(pos_msec / 1e3);
}
int Mpris1Player::PositionGet() const {
return player_->engine()->position();
return player_->engine()->position_nanosec() / 1e6;
}
QVariantMap Mpris1Player::GetMetadata() const {

View File

@ -325,7 +325,7 @@ void Mpris2::SetVolume(double value) {
}
qlonglong Mpris2::Position() const {
return mpris1_->player()->PositionGet() * 1e3;
return mpris1_->player()->PositionGet() / 1e3;
}
double Mpris2::MaximumRate() const {
@ -400,16 +400,16 @@ void Mpris2::Play() {
void Mpris2::Seek(qlonglong offset) {
if(CanSeek()) {
player_->Seek((Position() + offset) / 1e6);
player_->Seek(player_->engine()->position_nanosec() / 1e9 + offset / 1e6);
}
}
void Mpris2::SetPosition(const QDBusObjectPath& trackId, qlonglong offset) {
if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) {
offset /= 1e6;
offset *= 1e3;
if(offset < player_->GetCurrentItem()->Metadata().length()) {
player_->Seek(offset);
player_->Seek(offset / 1e9);
}
}
}

View File

@ -96,8 +96,8 @@ void Player::HandleSpecialLoad(const PlaylistItem::SpecialLoadResult &result) {
return;
engine_->Play(result.media_url_, stream_change_type_,
item->Metadata().beginning() * 1000,
item->Metadata().end() * 1000);
item->Metadata().beginning() * 1e9,
item->Metadata().end() * 1e9);
current_item_ = item;
loading_async_ = QUrl();
@ -245,7 +245,7 @@ int Player::GetVolume() const {
}
void Player::PlayAt(int index, Engine::TrackChangeType change, bool reshuffle) {
if (change == Engine::Manual && engine_->position() != engine_->length()) {
if (change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
emit TrackSkipped(current_item_);
}
@ -266,8 +266,8 @@ void Player::PlayAt(int index, Engine::TrackChangeType change, bool reshuffle) {
else {
loading_async_ = QUrl();
engine_->Play(current_item_->Url(), change,
current_item_->Metadata().beginning() * 1000,
current_item_->Metadata().end() * 1000);
current_item_->Metadata().beginning() * 1e9,
current_item_->Metadata().end() * 1e9);
#ifdef HAVE_LIBLASTFM
if (lastfm_->IsScrobblingEnabled())
@ -283,21 +283,22 @@ void Player::CurrentMetadataChanged(const Song& metadata) {
}
void Player::Seek(int seconds) {
int msec = qBound(0, seconds * 1000, int(engine_->length()));
engine_->Seek(msec);
qint64 nanosec = qBound(0ll, qint64(seconds) * qint64(1e9),
engine_->length_nanosec());
engine_->Seek(nanosec);
// If we seek the track we don't want to submit it to last.fm
playlists_->active()->set_scrobbled(true);
emit Seeked(msec * 1000);
emit Seeked(nanosec / 1000);
}
void Player::SeekForward() {
Seek(engine()->position() / 1000 + 5);
Seek(engine()->position_nanosec() / 1e9 + 5);
}
void Player::SeekBackward() {
Seek(engine()->position() / 1000 - 5);
Seek(engine()->position_nanosec() / 1e9 - 5);
}
void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle& bundle) {

View File

@ -29,11 +29,11 @@ const char* Engine::Base::kSettingsGroup = "Player";
Engine::Base::Base()
: volume_(50),
beginning_(0),
end_(0),
beginning_nanosec_(0),
end_nanosec_(0),
scope_(kScopeSize),
fadeout_enabled_(true),
fadeout_duration_(2000),
fadeout_duration_nanosec_(2000 * 1e6), // 2s
crossfade_enabled_(true),
next_background_stream_id_(0),
about_to_end_emitted_(false)
@ -43,11 +43,11 @@ Engine::Base::Base()
Engine::Base::~Base() {
}
bool Engine::Base::Load(const QUrl &url, TrackChangeType,
uint beginning, int end) {
bool Engine::Base::Load(const QUrl& url, TrackChangeType,
quint64 beginning_nanosec, qint64 end_nanosec) {
url_ = url;
beginning_ = beginning;
end_ = end;
beginning_nanosec_ = beginning_nanosec;
end_nanosec_ = end_nanosec;
about_to_end_emitted_ = false;
return true;
@ -69,7 +69,7 @@ void Engine::Base::ReloadSettings() {
s.beginGroup(kSettingsGroup);
fadeout_enabled_ = s.value("FadeoutEnabled", true).toBool();
fadeout_duration_ = s.value("FadeoutDuration", 2000).toInt();
fadeout_duration_nanosec_ = s.value("FadeoutDuration", 2000).toLongLong() * 1e6;
crossfade_enabled_ = s.value("CrossfadeEnabled", true).toBool();
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
}
@ -86,8 +86,9 @@ int Engine::Base::AddBackgroundStream(const QUrl& url) {
return -1;
}
bool Engine::Base::Play(const QUrl& u, TrackChangeType c, uint beginning, int end) {
if (!Load(u, c, beginning, end))
bool Engine::Base::Play(const QUrl& u, TrackChangeType c,
quint64 beginning_nanosec, qint64 end_nanosec) {
if (!Load(u, c, beginning_nanosec, end_nanosec))
return false;
return Play(0);

View File

@ -46,31 +46,31 @@ class Base : public QObject, boost::noncopyable {
virtual bool CanDecode(const QUrl &url) = 0;
virtual void StartPreloading(const QUrl&) {}
virtual bool Play(uint offset) = 0;
virtual bool Play(quint64 offset_nanosec) = 0;
virtual void Stop() = 0;
virtual void Pause() = 0;
virtual void Unpause() = 0;
virtual void Seek( uint ms ) = 0;
virtual void Seek(quint64 offset_nanosec) = 0;
virtual int AddBackgroundStream(const QUrl& url);
virtual void StopBackgroundStream(int id) {}
virtual void SetBackgroundStreamVolume(int id, int volume) {}
virtual State state() const = 0;
virtual uint position() const = 0;
virtual uint length() const { return 0; }
virtual qint64 position_nanosec() const = 0;
virtual qint64 length_nanosec() const = 0;
// Subclasses should respect given markers (beginning and end) which are
// in miliseconds.
virtual bool Load(const QUrl& url, TrackChangeType change,
uint beginning, int end);
quint64 beginning_nanosec, qint64 end_nanosec);
// Plays a media stream represented with the URL 'u' from the given 'beginning'
// to the given 'end' (usually from 0 to a song's length). Both markers
// should be passed in miliseconds. 'end' can be negative, indicating that the
// should be passed in nanoseconds. 'end' can be negative, indicating that the
// real length of 'u' stream is unknown.
bool Play(const QUrl& u, TrackChangeType c,
uint beginning, int end);
quint64 beginning_nanosec, qint64 end_nanosec);
void SetVolume(uint value);
@ -115,14 +115,14 @@ class Base : public QObject, boost::noncopyable {
void EmitAboutToEnd();
protected:
uint volume_;
uint beginning_;
int end_;
QUrl url_;
uint volume_;
quint64 beginning_nanosec_;
qint64 end_nanosec_;
QUrl url_;
Scope scope_;
bool fadeout_enabled_;
int fadeout_duration_;
qint64 fadeout_duration_nanosec_;
bool crossfade_enabled_;
bool autocrossfade_enabled_;
int next_background_stream_id_;

View File

@ -75,13 +75,13 @@ GstEngine::GstEngine()
rg_mode_(0),
rg_preamp_(0.0),
rg_compression_(true),
buffer_duration_ms_(1000),
buffer_duration_nanosec_(1000 * 1e6), // 1s
seek_timer_(new QTimer(this)),
timer_id_(-1),
next_element_id_(0)
{
seek_timer_->setSingleShot(true);
seek_timer_->setInterval(kSeekDelay);
seek_timer_->setInterval(kSeekDelayNanosec / 1e6);
connect(seek_timer_, SIGNAL(timeout()), SLOT(SeekNow()));
ReloadSettings();
@ -168,7 +168,7 @@ void GstEngine::ReloadSettings() {
rg_preamp_ = s.value("rgpreamp", 0.0).toDouble();
rg_compression_ = s.value("rgcompression", true).toBool();
buffer_duration_ms_ = s.value("bufferduration", 1000).toInt();
buffer_duration_nanosec_ = s.value("bufferduration", 1000).toLongLong() * 1e6;
}
@ -263,20 +263,20 @@ gboolean GstEngine::CanDecodeBusCallback(GstBus*, GstMessage* msg, gpointer) {
}
uint GstEngine::position() const {
qint64 GstEngine::position_nanosec() const {
if (!current_pipeline_)
return 0;
int result = (current_pipeline_->position() / GST_MSECOND) - beginning_;
return uint(qMax(0, result));
qint64 result = current_pipeline_->position() - beginning_nanosec_;
return qint64(qMax(0ll, result));
}
uint GstEngine::length() const {
qint64 GstEngine::length_nanosec() const {
if (!current_pipeline_)
return 0;
int result = end_ - beginning_;
return uint(qMax(0, result));
qint64 result = end_nanosec_ - beginning_nanosec_;
return qint64(qMax(0ll, result));
}
Engine::State GstEngine::state() const {
@ -442,10 +442,10 @@ QUrl GstEngine::FixupUrl(const QUrl& url) {
}
bool GstEngine::Load(const QUrl& url, Engine::TrackChangeType change,
uint beginning, int end) {
quint64 beginning_nanosec, qint64 end_nanosec) {
EnsureInitialised();
Engine::Base::Load(url, change, beginning, end);
Engine::Base::Load(url, change, beginning_nanosec, end_nanosec);
// Clementine just crashes when asked to load a file that doesn't exist on
// Windows, so check for that here. This is definitely the wrong place for
@ -490,7 +490,7 @@ bool GstEngine::Load(const QUrl& url, Engine::TrackChangeType change,
// Maybe fade in this track
if (crossfade)
current_pipeline_->StartFader(fadeout_duration_, QTimeLine::Forward);
current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward);
return true;
}
@ -501,20 +501,20 @@ void GstEngine::StartFadeout() {
fadeout_pipeline_->RemoveAllBufferConsumers();
ClearScopeBuffers();
fadeout_pipeline_->StartFader(fadeout_duration_, QTimeLine::Backward);
fadeout_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Backward);
connect(fadeout_pipeline_.get(), SIGNAL(FaderFinished()), SLOT(FadeoutFinished()));
}
bool GstEngine::Play(uint offset) {
bool GstEngine::Play(quint64 offset_nanosec) {
EnsureInitialised();
if (!current_pipeline_)
return false;
QFuture<GstStateChangeReturn> future = current_pipeline_->SetState(GST_STATE_PLAYING);
BoundFutureWatcher<GstStateChangeReturn, uint>* watcher =
new BoundFutureWatcher<GstStateChangeReturn, uint>(offset, this);
BoundFutureWatcher<GstStateChangeReturn, quint64>* watcher =
new BoundFutureWatcher<GstStateChangeReturn, quint64>(offset_nanosec, this);
watcher->setFuture(future);
connect(watcher, SIGNAL(finished()), SLOT(PlayDone()));
@ -522,12 +522,12 @@ bool GstEngine::Play(uint offset) {
}
void GstEngine::PlayDone() {
BoundFutureWatcher<GstStateChangeReturn, uint>* watcher =
static_cast<BoundFutureWatcher<GstStateChangeReturn, uint>*>(sender());
BoundFutureWatcher<GstStateChangeReturn, quint64>* watcher =
static_cast<BoundFutureWatcher<GstStateChangeReturn, quint64>*>(sender());
watcher->deleteLater();
GstStateChangeReturn ret = watcher->result();
uint offset = watcher->data();
quint64 offset_nanosec = watcher->data();
if (!current_pipeline_)
return;
@ -538,7 +538,7 @@ void GstEngine::PlayDone() {
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->url()) {
qDebug() << "Redirecting to" << redirect_url;
current_pipeline_ = CreatePipeline(redirect_url);
Play(offset);
Play(offset_nanosec);
return;
}
@ -553,8 +553,8 @@ void GstEngine::PlayDone() {
current_sample_ = 0;
// initial offset
if(offset != 0 || beginning_ != 0) {
Seek(offset);
if(offset_nanosec != 0 || beginning_nanosec_ != 0) {
Seek(offset_nanosec);
}
emit StateChanged(Engine::Playing);
@ -565,7 +565,7 @@ void GstEngine::Stop() {
StopTimers();
url_ = QUrl(); // To ensure we return Empty from state()
beginning_ = end_ = 0;
beginning_nanosec_ = end_nanosec_ = 0;
if (fadeout_enabled_ && current_pipeline_)
StartFadeout();
@ -602,11 +602,11 @@ void GstEngine::Unpause() {
}
}
void GstEngine::Seek(uint ms) {
void GstEngine::Seek(quint64 offset_nanosec) {
if (!current_pipeline_)
return;
seek_pos_ = beginning_ + ms;
seek_pos_ = beginning_nanosec_ + offset_nanosec;
waiting_to_seek_ = true;
if (!seek_timer_->isActive()) {
@ -622,7 +622,7 @@ void GstEngine::SeekNow() {
if (!current_pipeline_)
return;
if (current_pipeline_->Seek(seek_pos_ * GST_MSECOND))
if (current_pipeline_->Seek(seek_pos_))
ClearScopeBuffers();
else
qDebug() << "Seek failed";
@ -652,7 +652,7 @@ void GstEngine::SetVolumeSW( uint percent ) {
void GstEngine::StartTimers() {
StopTimers();
timer_id_ = startTimer(kTimerInterval);
timer_id_ = startTimer(kTimerIntervalNanosec / 1e6);
}
void GstEngine::StopTimers() {
@ -672,13 +672,13 @@ void GstEngine::timerEvent(QTimerEvent* e) {
PruneScope();
if (current_pipeline_) {
const qint64 current_position = position();
const qint64 current_length = length();
const qint64 current_position = position_nanosec();
const qint64 current_length = length_nanosec();
const qint64 remaining = current_length - current_position;
const qint64 fudge = kTimerInterval + 100; // Mmm fudge
const qint64 gap = autocrossfade_enabled_ ? fadeout_duration_ : kPreloadGap;
const qint64 fudge = kTimerIntervalNanosec + 100 * 1e6; // Mmm fudge
const qint64 gap = autocrossfade_enabled_ ? fadeout_duration_nanosec_ : kPreloadGapNanosec;
// only if we know the length of the current stream...
if(current_length > 0) {
@ -692,7 +692,7 @@ void GstEngine::timerEvent(QTimerEvent* e) {
// check to allow for the fact that the length has been rounded down to
// the nearest second, and to stop us from occasionally stopping the
// stream just before it ends normally.
if(current_position >= current_length + 1000) {
if(current_position >= current_length + 1000 * 1e6) {
EndOfStreamReached(current_pipeline_->has_next_valid_url());
}
}
@ -774,7 +774,7 @@ shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
shared_ptr<GstEnginePipeline> ret(new GstEnginePipeline(this));
ret->set_output_device(sink_, device_);
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_compression_);
ret->set_buffer_duration_ms(buffer_duration_ms_);
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
ret->AddBufferConsumer(this);
foreach (BufferConsumer* consumer, buffer_consumers_)
@ -808,7 +808,7 @@ qint64 GstEngine::PruneScope() {
return 0;
// get the position playing in the audio device
const qint64 pos = position() * 1e6;
const qint64 pos = position_nanosec();
const qint64 segment_start = current_pipeline_->segment_start();
GstBuffer *buf = 0;

View File

@ -72,8 +72,8 @@ class GstEngine : public Engine::Base, public BufferConsumer {
void StopBackgroundStream(int id);
void SetBackgroundStreamVolume(int id, int volume);
uint position() const;
uint length() const;
qint64 position_nanosec() const;
qint64 length_nanosec() const;
Engine::State state() const;
const Engine::Scope& scope();
@ -88,12 +88,12 @@ class GstEngine : public Engine::Base, public BufferConsumer {
public slots:
void StartPreloading(const QUrl &);
bool Load(const QUrl&, Engine::TrackChangeType change,
uint beginning, int end);
bool Play(uint offset);
quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
void Stop();
void Pause();
void Unpause();
void Seek(uint ms);
void Seek(quint64 offset_nanosec);
/** Set whether equalizer is enabled */
void SetEqualizerEnabled(bool);
@ -150,9 +150,9 @@ class GstEngine : public Engine::Base, public BufferConsumer {
static QUrl FixupUrl(const QUrl& url);
private:
static const int kTimerInterval = 1000; // msec
static const int kPreloadGap = 1000; // msec
static const int kSeekDelay = 100; // msec
static const int kTimerIntervalNanosec = 1000 * 1e6; // 1s
static const int kPreloadGapNanosec = 1000 * 1e6; // 1s
static const int kSeekDelayNanosec = 100 * 1e6; // 100msec
static const char* kHypnotoadPipeline;
@ -181,7 +181,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
float rg_preamp_;
bool rg_compression_;
int buffer_duration_ms_;
qint64 buffer_duration_nanosec_;
mutable bool can_decode_success_;
mutable bool can_decode_last_;
@ -189,7 +189,7 @@ class GstEngine : public Engine::Base, public BufferConsumer {
// Hack to stop seeks happening too often
QTimer* seek_timer_;
bool waiting_to_seek_;
uint seek_pos_;
quint64 seek_pos_;
int timer_id_;
int next_element_id_;

View File

@ -48,7 +48,7 @@ GstEnginePipeline::GstEnginePipeline(GstEngine* engine)
rg_mode_(0),
rg_preamp_(0.0),
rg_compression_(true),
buffer_duration_ms_(1000),
buffer_duration_nanosec_(1000 * 1e6),
ignore_tags_(false),
volume_percent_(100),
volume_modifier_(1.0),
@ -86,8 +86,8 @@ void GstEnginePipeline::set_replaygain(bool enabled, int mode, float preamp,
rg_compression_ = compression;
}
void GstEnginePipeline::set_buffer_duration_ms(int buffer_duration_ms) {
buffer_duration_ms_ = buffer_duration_ms;
void GstEnginePipeline::set_buffer_duration_nanosec(qint64 buffer_duration_nanosec) {
buffer_duration_nanosec_ = buffer_duration_nanosec;
}
bool GstEnginePipeline::ReplaceDecodeBin(GstElement* new_bin) {
@ -112,7 +112,7 @@ bool GstEnginePipeline::ReplaceDecodeBin(GstElement* new_bin) {
bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) {
GstElement* new_bin = engine_->CreateElement("uridecodebin");
g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(), NULL);
g_object_set(G_OBJECT(new_bin), "buffer-duration", buffer_duration_ms_ * 1000 * 1000, NULL);
g_object_set(G_OBJECT(new_bin), "buffer-duration", buffer_duration_nanosec_, NULL);
g_object_set(G_OBJECT(new_bin), "download", true, NULL);
g_object_set(G_OBJECT(new_bin), "use-buffering", true, NULL);
g_signal_connect(G_OBJECT(new_bin), "drained", G_CALLBACK(SourceDrainedCallback), this);
@ -529,9 +529,11 @@ void GstEnginePipeline::UpdateVolume() {
g_object_set(G_OBJECT(volume_), "volume", vol, NULL);
}
void GstEnginePipeline::StartFader(int duration_msec,
void GstEnginePipeline::StartFader(qint64 duration_nanosec,
QTimeLine::Direction direction,
QTimeLine::CurveShape shape) {
const int duration_msec = duration_nanosec / 1e6;
// If there's already another fader running then start from the same time
// that one was already at.
int start_time = direction == QTimeLine::Forward ? 0 : duration_msec;

View File

@ -46,7 +46,7 @@ class GstEnginePipeline : public QObject {
// Call these setters before Init
void set_output_device(const QString& sink, const QString& device);
void set_replaygain(bool enabled, int mode, float preamp, bool compression);
void set_buffer_duration_ms(int duration_ms);
void set_buffer_duration_nanosec(qint64 duration_nanosec);
// Creates the pipeline, returns false on error
bool InitFromUrl(const QUrl& url);
@ -63,7 +63,7 @@ class GstEnginePipeline : public QObject {
void SetEqualizerEnabled(bool enabled);
void SetEqualizerParams(int preamp, const QList<int>& band_gains);
void SetVolume(int percent);
void StartFader(int duration_msec,
void StartFader(qint64 duration_nanosec,
QTimeLine::Direction direction = QTimeLine::Forward,
QTimeLine::CurveShape shape = QTimeLine::LinearCurve);
@ -154,7 +154,7 @@ class GstEnginePipeline : public QObject {
int rg_mode_;
float rg_preamp_;
bool rg_compression_;
quint64 buffer_duration_ms_;
quint64 buffer_duration_nanosec_;
// The URL that is currently playing, and the URL that is to be preloaded
// when the current track is close to finishing.

View File

@ -806,8 +806,8 @@ void MainWindow::TrackSkipped(PlaylistItemPtr item) {
// the database.
if (item && item->IsLocalLibraryItem() && !playlists_->active()->has_scrobbled()) {
Song song = item->Metadata();
const int position = player_->engine()->position();
const int length = player_->engine()->length();
const qint64 position = player_->engine()->position_nanosec();
const qint64 length = player_->engine()->length_nanosec();
const float percentage = (length == 0 ? 1 : float(position) / length);
library_->backend()->IncrementSkipCountAsync(song.id(), percentage);
@ -925,7 +925,8 @@ void MainWindow::FilePathChanged(const QString& path) {
void MainWindow::UpdateTrackPosition() {
// Track position in seconds
PlaylistItemPtr item(player_->GetCurrentItem());
const int position = std::floor(float(player_->engine()->position()) / 1000.0 + 0.5);
const int position = std::floor(
float(player_->engine()->position_nanosec()) / 1e9 + 0.5);
const int length = item->Metadata().length();
if (length <= 0) {
@ -1399,9 +1400,9 @@ void MainWindow::CommandlineOptionsReceived(const CommandlineOptions &options) {
player_->SetVolume(player_->GetVolume() + options.volume_modifier());
if (options.seek_to() != -1)
player_->Seek(options.seek_to());
player_->Seek(options.seek_to() * 1e9);
else if (options.seek_by() != 0)
player_->Seek(player_->engine()->position() / 1000 + options.seek_by());
player_->Seek(player_->engine()->position_nanosec() + options.seek_by() * 1e9);
if (options.play_track_at() != -1)
player_->PlayAt(options.play_track_at(), Engine::Manual, true);