for (const Clip &clip : clips) {
clips_with_id.emplace_back(ClipWithID{ clip, 0 });
}
- double total_length = compute_total_time(clips_with_id);
+ TimeRemaining total_length = compute_total_time(clips_with_id);
promise<void> done_promise;
future<void> done = done_promise.get_future();
player.set_done_callback([&done_promise] {
done_promise.set_value();
});
- player.set_progress_callback([¤t_value, total_length](const std::map<uint64_t, double> &player_progress, double time_remaining) {
- current_value = 1.0 - time_remaining / total_length;
+ player.set_progress_callback([¤t_value, total_length](const std::map<uint64_t, double> &player_progress, TimeRemaining time_remaining) {
+ current_value = 1.0 - time_remaining.t / total_length.t; // Nothing to do about the infinite clips.
});
player.play(clips_with_id);
while (done.wait_for(std::chrono::milliseconds(100)) != future_status::ready && !progress.wasCanceled()) {
live_player_done();
});
});
- live_player->set_progress_callback([this](const map<uint64_t, double> &progress, double time_remaining) {
+ live_player->set_progress_callback([this](const map<uint64_t, double> &progress, TimeRemaining time_remaining) {
post_to_main_thread([this, progress, time_remaining] {
live_player_clip_progress(progress, time_remaining);
});
if (!selected->hasSelection()) {
Clip clip = *cliplist_clips->back();
clip.stream_idx = 0;
- if (clip.pts_out != -1) {
- playlist_clips->add_clip(clip);
- playlist_selection_changed();
- ui->playlist->scrollToBottom();
- }
+ playlist_clips->add_clip(clip);
+ playlist_selection_changed();
+ ui->playlist->scrollToBottom();
return;
}
clip.stream_idx = ui->preview_display->get_stream_idx();
}
- if (clip.pts_out != -1) {
- playlist_clips->add_clip(clip);
- playlist_selection_changed();
- ui->playlist->scrollToBottom();
- if (!ui->playlist->selectionModel()->hasSelection()) {
- // TODO: Figure out why this doesn't always seem to actually select the row.
- QModelIndex bottom = playlist_clips->index(playlist_clips->size() - 1, 0);
- ui->playlist->setCurrentIndex(bottom);
- }
+ playlist_clips->add_clip(clip);
+ playlist_selection_changed();
+ ui->playlist->scrollToBottom();
+ if (!ui->playlist->selectionModel()->hasSelection()) {
+ // TODO: Figure out why this doesn't always seem to actually select the row.
+ QModelIndex bottom = playlist_clips->index(playlist_clips->size() - 1, 0);
+ ui->playlist->setCurrentIndex(bottom);
}
}
} else {
clip.stream_idx = ui->preview_display->get_stream_idx();
}
+ if (clip.pts_out == -1) {
+ clip.pts_out = clip.pts_in + int64_t(TIMEBASE) * 86400 * 7; // One week; effectively infinite, but without overflow issues.
+ }
preview_player->play(clip);
preview_playing = true;
enable_or_disable_preview_button();
vector<ClipWithID> clips;
for (unsigned row = start_row; row < playlist_clips->size(); ++row) {
- clips.emplace_back(*playlist_clips->clip_with_id(row));
+ ClipWithID clip = *playlist_clips->clip_with_id(row);
+ if (clip.clip.pts_out == -1) {
+ clip.clip.pts_out = clip.clip.pts_in + int64_t(TIMEBASE) * 86400 * 7; // One week; effectively infinite, but without overflow issues.
+ }
+ clips.emplace_back(clip);
}
live_player->play(clips);
playlist_clips->set_progress({ { start_row, 0.0f } });
playlist_selection_changed();
}
-void MainWindow::live_player_clip_progress(const map<uint64_t, double> &progress, double time_remaining)
+void MainWindow::live_player_clip_progress(const map<uint64_t, double> &progress, TimeRemaining time_remaining)
{
playlist_clips->set_progress(progress);
set_output_status(format_duration(time_remaining) + " left");
for (size_t row = selected->selectedRows().front().row(); row < playlist_clips->size(); ++row) {
clips.emplace_back(*playlist_clips->clip_with_id(row));
}
- double remaining = compute_total_time(clips);
+ TimeRemaining remaining = compute_total_time(clips);
set_output_status(format_duration(remaining) + " ready");
}
}
if (cliplist_clips->empty()) {
enabled = false;
} else {
- QItemSelectionModel *selected = ui->clip_list->selectionModel();
- if (!selected->hasSelection()) {
- Clip clip = *cliplist_clips->back();
- enabled = clip.pts_out != -1;
- } else {
- QModelIndex index = selected->currentIndex();
- Clip clip = *cliplist_clips->clip(index.row());
- enabled = clip.pts_out != -1;
- }
+ enabled = true;
}
ui->queue_btn->setEnabled(enabled);
#include "clip_list.h"
#include "db.h"
#include "midi_mapper.h"
+#include "player.h"
#include "state.pb.h"
#include <QLabel>
void speed_lock_clicked();
void preview_player_done();
void live_player_done();
- void live_player_clip_progress(const std::map<uint64_t, double> &progress, double time_remaining);
+ void live_player_clip_progress(const std::map<uint64_t, double> &progress, TimeRemaining time_remaining);
void set_output_status(const std::string &status);
void playlist_duplicate();
void playlist_remove();
// NOTE: None of this will take into account any snapping done below.
double clip_progress = calc_progress(*clip, in_pts_for_progress);
map<uint64_t, double> progress{ { clip_list[clip_idx].id, clip_progress } };
- double time_remaining;
+ TimeRemaining time_remaining;
if (next_clip != nullptr && time_left_this_clip <= next_clip_fade_time) {
double next_clip_progress = calc_progress(*next_clip, in_pts_secondary_for_progress);
progress[clip_list[clip_idx + 1].id] = next_clip_progress;
ss.imbue(locale("C"));
ss.precision(3);
ss << "Futatabi " NAGERU_VERSION ";PLAYING;";
- ss << fixed << time_remaining;
+ ss << fixed << time_remaining.t;
ss << ";" << format_duration(time_remaining) << " left";
subtitle = ss.str();
}
new_clip_changed.notify_all();
}
-double compute_time_left(const vector<ClipWithID> &clips, size_t currently_playing_idx, double progress_currently_playing)
+TimeRemaining compute_time_left(const vector<ClipWithID> &clips, size_t currently_playing_idx, double progress_currently_playing)
{
// Look at the last clip and then start counting from there.
- double remaining = 0.0;
+ TimeRemaining remaining { 0, 0.0 };
double last_fade_time_seconds = 0.0;
for (size_t row = currently_playing_idx; row < clips.size(); ++row) {
const Clip &clip = clips[row].clip;
double clip_length = double(clip.pts_out - clip.pts_in) / TIMEBASE / clip.speed;
- if (row == currently_playing_idx) {
- // A clip we're playing: Subtract the part we've already played.
- remaining = clip_length * (1.0 - progress_currently_playing);
+ if (clip_length >= 86400.0) { // More than one day.
+ ++remaining.num_infinite;
} else {
- // A clip we haven't played yet: Subtract the part that's overlapping
- // with a previous clip (due to fade).
- remaining += max(clip_length - last_fade_time_seconds, 0.0);
+ if (row == currently_playing_idx) {
+ // A clip we're playing: Subtract the part we've already played.
+ remaining.t = clip_length * (1.0 - progress_currently_playing);
+ } else {
+ // A clip we haven't played yet: Subtract the part that's overlapping
+ // with a previous clip (due to fade).
+ remaining.t += max(clip_length - last_fade_time_seconds, 0.0);
+ }
}
last_fade_time_seconds = min(clip_length, clip.fade_time_seconds);
}
return remaining;
}
-string format_duration(double t)
+string format_duration(TimeRemaining t)
{
- int t_ms = lrint(t * 1e3);
+ int t_ms = lrint(t.t * 1e3);
int ms = t_ms % 1000;
t_ms /= 1000;
int m = t_ms;
char buf[256];
- snprintf(buf, sizeof(buf), "%d:%02d.%03d", m, s, ms);
+ if (t.num_infinite > 1 && t.t > 0.0) {
+ snprintf(buf, sizeof(buf), "%zu clips + %d:%02d.%03d", t.num_infinite, m, s, ms);
+ } else if (t.num_infinite > 1) {
+ snprintf(buf, sizeof(buf), "%zu clips", t.num_infinite);
+ } else if (t.num_infinite == 1 && t.t > 0.0) {
+ snprintf(buf, sizeof(buf), "%zu clip + %d:%02d.%03d", t.num_infinite, m, s, ms);
+ } else if (t.num_infinite == 1) {
+ snprintf(buf, sizeof(buf), "%zu clip", t.num_infinite);
+ } else {
+ snprintf(buf, sizeof(buf), "%d:%02d.%03d", m, s, ms);
+ }
return buf;
}
class QSurface;
class QSurfaceFormat;
+struct TimeRemaining {
+ size_t num_infinite;
+ double t;
+};
+
class Player : public QueueInterface {
public:
enum StreamOutput {
// Not thread-safe to set concurrently with playing.
// Will be called back from the player thread.
// The keys in the given map are row members in the vector given to play().
- using progress_callback_func = std::function<void(const std::map<uint64_t, double> &progress, double time_remaining)>;
+ using progress_callback_func = std::function<void(const std::map<uint64_t, double> &progress, TimeRemaining time_remaining)>;
void set_progress_callback(progress_callback_func cb) { progress_callback = cb; }
// QueueInterface.
const StreamOutput stream_output;
};
-double compute_time_left(const std::vector<ClipWithID> &clips, size_t currently_playing_idx, double progress_currently_playing);
+TimeRemaining compute_time_left(const std::vector<ClipWithID> &clips, size_t currently_playing_idx, double progress_currently_playing);
-static inline double compute_total_time(const std::vector<ClipWithID> &clips)
+static inline TimeRemaining compute_total_time(const std::vector<ClipWithID> &clips)
{
return compute_time_left(clips, 0, 0.0);
}
-std::string format_duration(double t);
+std::string format_duration(TimeRemaining t);
#endif // !defined(_PLAYER_H)