summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2026-01-27 00:38:03 +0100
committerCarl Hetherington <cth@carlh.net>2026-02-16 01:20:38 +0100
commit651722fb46269ff06e5ff41227fd874ed5fd9854 (patch)
tree8427e31087b52e9ac162d5a9de0e5a94384213e9
parent87d740bcf70fd1b5d1e25e763c0c77b8906b7361 (diff)
Rework player content handling.
The idea now is ... There is a "pending" or "next" playlist. You can load a playlist from the database, or add content to it. Play loads the pending playlist into the current one and starts playing it. Stop stops and clears the current playlist. Pause pauses. While something is playing you can do what you like to the next playlist.
-rw-r--r--src/wx/content_view.cc13
-rw-r--r--src/wx/content_view.h4
-rw-r--r--src/wx/controls.cc15
-rw-r--r--src/wx/controls.h6
-rw-r--r--src/wx/player_frame.cc391
-rw-r--r--src/wx/player_frame.h22
-rw-r--r--src/wx/playlist_controls.cc327
-rw-r--r--src/wx/playlist_controls.h38
-rw-r--r--src/wx/standard_controls.cc15
-rw-r--r--src/wx/standard_controls.h3
-rw-r--r--web/index.html63
-rw-r--r--web/sidebar.html4
12 files changed, 563 insertions, 338 deletions
diff --git a/src/wx/content_view.cc b/src/wx/content_view.cc
index 46b8c8ce9..52e8e708e 100644
--- a/src/wx/content_view.cc
+++ b/src/wx/content_view.cc
@@ -51,6 +51,9 @@ using std::string;
using std::vector;
using std::weak_ptr;
using boost::optional;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
using namespace dcpomatic;
@@ -62,6 +65,16 @@ ContentView::ContentView (wxWindow* parent)
AppendColumn({}, wxLIST_FORMAT_LEFT, 80);
/* annotation text */
AppendColumn({}, wxLIST_FORMAT_LEFT, 580);
+
+ Bind(wxEVT_LIST_ITEM_ACTIVATED, boost::bind(&ContentView::activated, this, _1));
+}
+
+
+void
+ContentView::activated(wxListEvent& ev)
+{
+ DCPOMATIC_ASSERT(ev.GetIndex() < static_cast<int>(_content.size()));
+ Activated(_content[ev.GetIndex()]);
}
diff --git a/src/wx/content_view.h b/src/wx/content_view.h
index 300e698f3..30284d42e 100644
--- a/src/wx/content_view.h
+++ b/src/wx/content_view.h
@@ -24,6 +24,7 @@
LIBDCP_DISABLE_WARNINGS
#include <wx/listctrl.h>
LIBDCP_ENABLE_WARNINGS
+#include <boost/signals2.hpp>
#include <vector>
@@ -39,8 +40,11 @@ public:
std::shared_ptr<Content> selected () const;
void update ();
+ boost::signals2::signal<void (std::weak_ptr<Content>)> Activated;
+
private:
void add (std::shared_ptr<Content> content);
+ void activated(wxListEvent& ev);
std::weak_ptr<Film> _film;
std::vector<std::shared_ptr<Content>> _content;
diff --git a/src/wx/controls.cc b/src/wx/controls.cc
index fce3fd7eb..2c4e92108 100644
--- a/src/wx/controls.cc
+++ b/src/wx/controls.cc
@@ -457,3 +457,18 @@ Controls::seek(int slider)
slider_moved(false);
slider_released();
}
+
+
+void
+Controls::play()
+{
+ _viewer.start();
+}
+
+
+void
+Controls::stop()
+{
+ _viewer.stop();
+}
+
diff --git a/src/wx/controls.h b/src/wx/controls.h
index 3dec6fb88..21b469bb7 100644
--- a/src/wx/controls.h
+++ b/src/wx/controls.h
@@ -62,14 +62,16 @@ public:
void set_film(std::shared_ptr<Film> film);
- virtual void play() {};
- virtual void stop() {};
+ void play();
+ void stop();
void seek(int slider);
std::shared_ptr<Film> film() const;
void back_frame();
void forward_frame();
+ virtual void playlist_changed() {}
+
protected:
virtual void started();
diff --git a/src/wx/player_frame.cc b/src/wx/player_frame.cc
index a945a5b1d..afc43b807 100644
--- a/src/wx/player_frame.cc
+++ b/src/wx/player_frame.cc
@@ -48,6 +48,7 @@
#include "lib/font_config.h"
#include "lib/job_manager.h"
#include "lib/null_log.h"
+#include "lib/show_playlist_content_store.h"
#include "lib/text_content.h"
#include "lib/update_checker.h"
#include "lib/variant.h"
@@ -59,6 +60,7 @@
LIBDCP_DISABLE_WARNINGS
#include <wx/display.h>
#include <wx/preferences.h>
+#include <wx/progdlg.h>
#include <wx/stdpaths.h>
LIBDCP_ENABLE_WARNINGS
@@ -214,9 +216,7 @@ PlayerFrame::PlayerFrame()
Bind(wxEVT_CLOSE_WINDOW, boost::bind(&PlayerFrame::close, this, _1));
- if (Config::instance()->enable_player_http_server()) {
- update_content_store();
- }
+ update_content_store();
if (Config::instance()->player_mode() == Config::PlayerMode::DUAL) {
_controls = new PlaylistControls(_overall_panel, this, _viewer);
@@ -227,6 +227,7 @@ PlayerFrame::PlayerFrame()
_viewer.set_dcp_decode_reduction(Config::instance()->decode_reduction());
_viewer.PlaybackPermitted.connect(bind(&PlayerFrame::playback_permitted, this));
_viewer.TooManyDropped.connect(bind(&PlayerFrame::too_many_frames_dropped, this));
+ _viewer.Finished.connect(boost::bind(&PlayerFrame::viewer_finished, this));
_info = new PlayerInformation(_overall_panel, _viewer);
setup_main_sizer(Config::instance()->player_mode());
#ifdef __WXOSX__
@@ -267,7 +268,7 @@ PlayerFrame::PlayerFrame()
Bind(wxEVT_MENU, boost::bind(&PlayerFrame::go_to_start, this), ID_go_to_start);
Bind(wxEVT_MENU, boost::bind(&PlayerFrame::go_to_end, this), ID_go_to_end);
- reset_film();
+ take_playlist_entry();
UpdateChecker::instance()->StateChanged.connect(boost::bind(&PlayerFrame::update_checker_state_changed, this));
setup_screen();
@@ -324,14 +325,14 @@ PlayerFrame::setup_main_sizer(Config::PlayerMode mode)
bool
PlayerFrame::playback_permitted()
{
- if (!_film || !Config::instance()->respect_kdm_validity_periods()) {
+ if (!Config::instance()->respect_kdm_validity_periods()) {
return true;
}
bool ok = true;
- for (auto i: _film->content()) {
- auto d = dynamic_pointer_cast<DCPContent>(i);
- if (d && !d->kdm_timing_window_valid()) {
+ for (auto content: _playlist) {
+ auto dcp = dynamic_pointer_cast<DCPContent>(content.first);
+ if (dcp && !dcp->kdm_timing_window_valid()) {
ok = false;
}
}
@@ -379,39 +380,30 @@ PlayerFrame::set_decode_reduction(optional<int> reduction)
void
PlayerFrame::load_dcp(boost::filesystem::path dir)
{
- DCPOMATIC_ASSERT(_film);
-
- auto film = std::make_shared<Film>(optional<boost::filesystem::path>());
-
try {
_stress.set_suspended(true);
/* Handler to set things up once the DCP has been examined */
- auto setup = [this](weak_ptr<Film> weak_film, weak_ptr<Job> weak_job, weak_ptr<Content> weak_content)
+ auto setup = [this](weak_ptr<Job> weak_job, weak_ptr<Content> weak_content)
{
auto job = weak_job.lock();
if (!job || !job->finished_ok()) {
return;
}
- auto content = weak_content.lock();
- if (!content) {
- return;
+ if (auto content = weak_content.lock()) {
+ _playlist = { make_pair(content, boost::optional<float>()) };
+ _playlist_position = 0;
+ _controls->playlist_changed();
+ take_playlist_entry();
}
- auto film = weak_film.lock();
- if (!film) {
- return;
- }
-
- film->add_content({content});
_stress.set_suspended(false);
- reset_film(film);
};
auto dcp = make_shared<DCPContent>(dir);
auto job = make_shared<ExamineContentJob>(vector<shared_ptr<Content>>{dcp}, true);
- _examine_job_connection = job->Finished.connect(boost::bind<void>(setup, weak_ptr<Film>(film), weak_ptr<Job>(job), weak_ptr<Content>(dcp)));
+ _examine_job_connection = job->Finished.connect(boost::bind<void>(setup, weak_ptr<Job>(job), weak_ptr<Content>(dcp)));
JobManager::instance()->add(job);
bool const ok = display_progress(variant::wx::dcpomatic_player(), _("Loading content"));
if (ok && report_errors_from_last_job(this)) {
@@ -436,19 +428,6 @@ PlayerFrame::load_dcp(boost::filesystem::path dir)
}
-void
-PlayerFrame::reset_film(shared_ptr<Film> film, optional<float> crop_to_ratio)
-{
- _film = film;
-
- if (!crop_to_ratio) {
- crop_to_ratio = Config::instance()->player_crop_output_ratio();
- }
-
- prepare_to_play_film(crop_to_ratio);
-}
-
-
/* _film is now something new: set up to play it */
void
PlayerFrame::prepare_to_play_film(optional<float> crop_to_ratio)
@@ -553,13 +532,9 @@ PlayerFrame::prepare_to_play_film(optional<float> crop_to_ratio)
void
PlayerFrame::set_audio_delay_from_config()
{
- if (!_film) {
- return;
- }
-
- for (auto i: _film->content()) {
- if (i->audio) {
- i->audio->set_delay(Config::instance()->player_audio_delay());
+ for (auto content: _playlist) {
+ if (content.first->audio) {
+ content.first->audio->set_delay(Config::instance()->player_audio_delay());
}
}
}
@@ -596,8 +571,11 @@ PlayerFrame::idle()
void
PlayerFrame::examine_content()
{
- DCPOMATIC_ASSERT(_film);
- auto dcp = dynamic_pointer_cast<DCPContent>(_film->content().front());
+ if (_playlist.empty()) {
+ return;
+ }
+
+ auto dcp = dynamic_pointer_cast<DCPContent>(_playlist.front().first);
DCPOMATIC_ASSERT(dcp);
dcp->examine({}, true);
@@ -761,9 +739,8 @@ PlayerFrame::file_add_ov()
}
if (r == wxID_OK) {
- DCPOMATIC_ASSERT(_film);
- DCPOMATIC_ASSERT(!_film->content().empty());
- auto dcp = std::dynamic_pointer_cast<DCPContent>(_film->content().front());
+ DCPOMATIC_ASSERT(!_playlist.empty());
+ auto dcp = std::dynamic_pointer_cast<DCPContent>(_playlist.front().first);
DCPOMATIC_ASSERT(dcp);
try {
@@ -774,7 +751,7 @@ PlayerFrame::file_add_ov()
}
auto job = make_shared<ExamineContentJob>(vector<shared_ptr<Content>>{dcp}, true);
- _examine_job_connection = job->Finished.connect(boost::bind(&PlayerFrame::prepare_to_play_film, this, Config::instance()->player_crop_output_ratio()));
+ _examine_job_connection = job->Finished.connect(boost::bind(&PlayerFrame::take_playlist_entry, this));
JobManager::instance()->add(job);
display_progress(variant::wx::dcpomatic_player(), _("Loading content"));
@@ -789,21 +766,19 @@ PlayerFrame::file_add_kdm()
FileDialog dialog(this, _("Select KDM"), char_to_wx("XML files|*.xml|All files|*.*"), wxFD_MULTIPLE, "AddKDMPath");
if (dialog.show()) {
- DCPOMATIC_ASSERT(_film);
- auto dcp = std::dynamic_pointer_cast<DCPContent>(_film->content().front());
+ DCPOMATIC_ASSERT(!_playlist.empty());
+ auto dcp = std::dynamic_pointer_cast<DCPContent>(_playlist.front().first);
DCPOMATIC_ASSERT(dcp);
try {
- if (dcp) {
- dcp::ScopeGuard sg([this]() {
- _viewer.set_coalesce_player_changes(false);
- });
- _viewer.set_coalesce_player_changes(true);
- for (auto path: dialog.paths()) {
- dcp->add_kdm(dcp::EncryptedKDM(dcp::file_to_string(path)));
- _kdms.push_back(path);
- }
- examine_content();
+ dcp::ScopeGuard sg([this]() {
+ _viewer.set_coalesce_player_changes(false);
+ });
+ _viewer.set_coalesce_player_changes(true);
+ for (auto path: dialog.paths()) {
+ dcp->add_kdm(dcp::EncryptedKDM(dcp::file_to_string(path)));
+ _kdms.push_back(path);
}
+ examine_content();
} catch (exception& e) {
error_dialog(this, wxString::Format(_("Could not load KDM.")), std_to_wx(e.what()));
return;
@@ -871,7 +846,11 @@ PlayerFrame::file_history(wxCommandEvent& event)
void
PlayerFrame::file_close()
{
- reset_film();
+ _playlist.clear();
+ _playlist_position = 0;
+ _controls->playlist_changed();
+
+ take_playlist_entry();
_info->triggered_update();
set_menu_sensitivity();
}
@@ -901,7 +880,8 @@ PlayerFrame::edit_preferences()
void
PlayerFrame::view_cpl(wxCommandEvent& ev)
{
- auto dcp = std::dynamic_pointer_cast<DCPContent>(_film->content().front());
+ DCPOMATIC_ASSERT(!_playlist.empty());
+ auto dcp = std::dynamic_pointer_cast<DCPContent>(_playlist.front().first);
DCPOMATIC_ASSERT(dcp);
auto cpls = dcp->cpls();
int id = ev.GetId() - ID_view_cpl;
@@ -1041,8 +1021,8 @@ PlayerFrame::view_closed_captions()
void
PlayerFrame::tools_verify()
{
- DCPOMATIC_ASSERT(!_film->content().empty());
- auto dcp = std::dynamic_pointer_cast<DCPContent>(_film->content().front());
+ DCPOMATIC_ASSERT(!_playlist.empty());
+ auto dcp = std::dynamic_pointer_cast<DCPContent>(_playlist.front().first);
DCPOMATIC_ASSERT(dcp);
VerifyDCPDialog dialog(this, _("Verify DCP"), dcp->directories(), _kdms);
@@ -1053,8 +1033,8 @@ PlayerFrame::tools_verify()
void
PlayerFrame::tools_audio_graph()
{
- DCPOMATIC_ASSERT(!_film->content().empty());
- auto dcp = std::dynamic_pointer_cast<DCPContent>(_film->content().front());
+ DCPOMATIC_ASSERT(!_playlist.empty());
+ auto dcp = std::dynamic_pointer_cast<DCPContent>(_playlist.front().first);
DCPOMATIC_ASSERT(dcp);
_audio_dialog.reset(this, _film, dcp);
@@ -1265,7 +1245,7 @@ PlayerFrame::update_from_config(Config::Property prop)
void
PlayerFrame::set_menu_sensitivity()
{
- auto const have_content = _film && !_film->content().empty();
+ auto const have_content = !_playlist.empty();
auto const dcp = _viewer.dcp();
auto const playable = dcp && !dcp->needs_assets() && !dcp->needs_kdm();
_tools_verify->Enable(have_content);
@@ -1322,3 +1302,276 @@ PlayerFrame::go_to_end()
{
_viewer.seek(_film->length() - _viewer.one_video_frame(), true);
}
+
+static
+optional<dcp::EncryptedKDM>
+get_kdm_from_directory(shared_ptr<DCPContent> dcp)
+{
+ using namespace boost::filesystem;
+ auto kdm_dir = Config::instance()->player_kdm_directory();
+ if (!kdm_dir) {
+ return {};
+ }
+ for (auto i: directory_iterator(*kdm_dir)) {
+ try {
+ if (file_size(i.path()) < MAX_KDM_SIZE) {
+ dcp::EncryptedKDM kdm(dcp::file_to_string(i.path()));
+ if (kdm.cpl_id() == dcp->cpl()) {
+ return kdm;
+ }
+ }
+ } catch (std::exception& e) {
+ /* Hey well */
+ }
+ }
+
+ return {};
+}
+
+
+bool
+PlayerFrame::set_playlist(vector<ShowPlaylistEntry> playlist)
+{
+ bool was_playing = false;
+ if (_viewer.playing()) {
+ was_playing = true;
+ _viewer.stop();
+ }
+
+ wxProgressDialog dialog(variant::wx::dcpomatic(), _("Loading playlist and KDMs"));
+
+ _playlist.clear();
+ _playlist_position = 0;
+
+ auto const store = ShowPlaylistContentStore::instance();
+ for (auto const& entry: playlist) {
+ dialog.Pulse();
+ auto content = store->get(entry);
+ if (!content) {
+ error_dialog(this, _("This playlist cannot be loaded as some content is missing."));
+ _playlist.clear();
+ _controls->playlist_changed();
+ return false;
+ }
+
+ auto dcp = dynamic_pointer_cast<DCPContent>(content);
+ if (dcp && dcp->needs_kdm()) {
+ optional<dcp::EncryptedKDM> kdm;
+ kdm = get_kdm_from_directory(dcp);
+ if (kdm) {
+ try {
+ dcp->add_kdm(*kdm);
+ dcp->examine(shared_ptr<Job>(), true);
+ } catch (KDMError& e) {
+ error_dialog(this, _("Could not load KDM."));
+ }
+ }
+ if (dcp->needs_kdm()) {
+ /* We didn't get a KDM for this */
+ error_dialog(this, _("This playlist cannot be loaded as a KDM is missing or incorrect."));
+ _playlist.clear();
+ _controls->playlist_changed();
+ return false;
+ }
+ }
+ _playlist.push_back({content, entry.crop_to_ratio()});
+ }
+
+ take_playlist_entry();
+
+ if (was_playing) {
+ _viewer.start();
+ }
+
+ _controls->playlist_changed();
+
+ return true;
+}
+
+
+/** Stop the viewer, take the thing at _playlist_position and prepare to play it.
+ * Set up to play nothing if the playlist is empty, or we're off the
+ * end of it.
+ * @return true if the viewer was playing when this method was called.
+ */
+bool
+PlayerFrame::take_playlist_entry()
+{
+ boost::optional<float> crop_to_ratio;
+
+ if (_playlist_position < 0 || _playlist_position >= static_cast<int>(_playlist.size())) {
+ _film = std::make_shared<Film>(boost::none);
+ } else {
+ auto const entry = _playlist[_playlist_position];
+
+ _film = std::make_shared<Film>(optional<boost::filesystem::path>());
+ _film->add_content({entry.first});
+
+ if (!entry.second) {
+ crop_to_ratio = Config::instance()->player_crop_output_ratio();
+ }
+ }
+
+ bool const playing = _viewer.playing();
+ if (playing) {
+ _viewer.stop();
+ }
+
+ /* Start off as Flat */
+ auto auto_ratio = Ratio::from_id("185");
+
+ _film->set_audio_channels(MAX_DCP_AUDIO_CHANNELS);
+
+ for (auto i: _film->content()) {
+ auto dcp = dynamic_pointer_cast<DCPContent>(i);
+
+ copy_dcp_markers_to_film(dcp, _film);
+
+ for (auto j: i->text) {
+ j->set_use(true);
+ }
+
+ if (i->video && i->video->size()) {
+ auto const r = Ratio::nearest_from_ratio(i->video->size()->ratio());
+ if (r.id() == "239") {
+ /* Any scope content means we use scope */
+ auto_ratio = r;
+ }
+ }
+
+ /* Any 3D content means we use 3D mode */
+ if (i->video && i->video->frame_type() != VideoFrameType::TWO_D) {
+ _film->set_three_d(true);
+ }
+
+ if (dcp->video_frame_rate()) {
+ _film->set_video_frame_rate(dcp->video_frame_rate().get());
+ }
+
+ switch (dcp->video_encoding().get_value_or(VideoEncoding::JPEG2000)) {
+ case VideoEncoding::JPEG2000:
+ _viewer.set_optimisation(Optimisation::JPEG2000);
+ break;
+ case VideoEncoding::MPEG2:
+ _viewer.set_optimisation(Optimisation::MPEG2);
+ break;
+ case VideoEncoding::COUNT:
+ DCPOMATIC_ASSERT(false);
+ }
+ }
+
+ set_audio_delay_from_config();
+
+ auto old = _cpl_menu->GetMenuItems();
+ for (auto const& i: old) {
+ _cpl_menu->Remove(i);
+ }
+
+ if (_film->content().size() == 1) {
+ /* Offer a CPL menu */
+ if (auto first = dynamic_pointer_cast<DCPContent>(_film->content().front())) {
+ int id = ID_view_cpl;
+ for (auto i: dcp::find_and_resolve_cpls(first->directories(), true)) {
+ auto j = _cpl_menu->AppendRadioItem(
+ id,
+ wxString::Format(char_to_wx("%s (%s)"), std_to_wx(i->content_title_text()).data(), std_to_wx(i->id()).data())
+ );
+ j->Check(!first->cpl() || i->id() == *first->cpl());
+ ++id;
+ }
+ }
+
+ if (crop_to_ratio) {
+ auto size = _film->content()[0]->video->size().get_value_or({1998, 1080});
+ int pixels = 0;
+ if (*crop_to_ratio > (2048.0 / 1080.0)) {
+ pixels = (size.height - (size.width / *crop_to_ratio)) / 2;
+ _film->content()[0]->video->set_crop(Crop{0, 0, std::max(0, pixels), std::max(0, pixels)});
+ } else {
+ pixels = (size.width - (size.height * *crop_to_ratio)) / 2;
+ _film->content()[0]->video->set_crop(Crop{std::max(0, pixels), std::max(0, pixels), 0, 0});
+ }
+ }
+ }
+
+ if (crop_to_ratio) {
+ _film->set_container(Ratio(*crop_to_ratio, "custom", "custom", {}, "custom"));
+ } else {
+ _film->set_container(auto_ratio);
+ }
+
+ _viewer.set_film(_film);
+ _viewer.seek(DCPTime(), true);
+ _viewer.set_eyes(_view_eye_left->IsChecked() ? Eyes::LEFT : Eyes::RIGHT);
+ _info->triggered_update();
+ set_menu_sensitivity();
+
+ _controls->set_film(_film);
+ return playing;
+}
+
+
+void
+PlayerFrame::viewer_finished()
+{
+ _playlist_position++;
+
+ /* Either get the next piece of content, or go black */
+ take_playlist_entry();
+
+ if (_playlist_position < static_cast<int>(_playlist.size())) {
+ /* Start the next piece of content */
+ _viewer.start();
+ } else {
+ /* Be ready to start again from the top of the playlist */
+ _playlist_position = 0;
+ }
+}
+
+
+
+bool
+PlayerFrame::can_do_next() const
+{
+ return _playlist_position < (static_cast<int>(_playlist.size()) - 1);
+}
+
+
+void
+PlayerFrame::next()
+{
+ _playlist_position++;
+ if (take_playlist_entry()) {
+ _viewer.start();
+ }
+}
+
+
+bool
+PlayerFrame::can_do_previous() const
+{
+ return _playlist_position > 0;
+}
+
+
+void
+PlayerFrame::previous()
+{
+ _playlist_position--;
+ if (take_playlist_entry()) {
+ _viewer.start();
+ }
+}
+
+
+vector<shared_ptr<Content>>
+PlayerFrame::playlist() const
+{
+ vector<shared_ptr<Content>> content;
+ for (auto entry: _playlist) {
+ content.push_back(entry.first);
+ }
+ return content;
+}
+
+
diff --git a/src/wx/player_frame.h b/src/wx/player_frame.h
index 84da6b3a6..e50bc7995 100644
--- a/src/wx/player_frame.h
+++ b/src/wx/player_frame.h
@@ -26,6 +26,7 @@
#include "wx_ptr.h"
#include "lib/config.h"
#include "lib/http_server.h"
+#include "lib/show_playlist_entry.h"
#include <dcp/warnings.h>
LIBDCP_DISABLE_WARNINGS
#include <wx/dnd.h>
@@ -65,14 +66,23 @@ public:
void too_many_frames_dropped();
void set_decode_reduction(boost::optional<int> reduction);
void load_dcp(boost::filesystem::path dir);
- void reset_film(std::shared_ptr<Film> film = std::make_shared<Film>(boost::none), boost::optional<float> crop_to_ratio = {});
- /* _film is now something new: set up to play it */
- void prepare_to_play_film(boost::optional<float> crop_to_ratio);
void set_audio_delay_from_config();
void load_stress_script(boost::filesystem::path path);
void idle();
+ /** Set the playlist. If we're currently playing, this will stop whatever is
+ * happening now and start playing this playlist.
+ */
+ bool set_playlist(std::vector<ShowPlaylistEntry> playlist);
+
+ std::vector<std::shared_ptr<Content>> playlist() const;
+
+ bool can_do_next() const;
+ void next();
+ bool can_do_previous() const;
+ void previous();
+
private:
void examine_content();
bool report_errors_from_last_job(wxWindow* parent) const;
@@ -115,6 +125,10 @@ private:
void go_to_start();
void go_to_end();
+ bool take_playlist_entry();
+ void prepare_to_play_film(boost::optional<float> crop_to_ratio);
+ void viewer_finished();
+
wxFrame* _dual_screen = nullptr;
bool _update_news_requested = false;
PlayerInformation* _info = nullptr;
@@ -134,6 +148,8 @@ private:
Controls* _controls;
wx_ptr<SystemInformationDialog> _system_information_dialog;
std::shared_ptr<Film> _film;
+ std::vector<std::pair<std::shared_ptr<Content>, boost::optional<float>>> _playlist;
+ int _playlist_position = 0;
boost::signals2::scoped_connection _config_changed_connection;
boost::signals2::scoped_connection _examine_job_connection;
wxMenuItem* _file_add_ov = nullptr;
diff --git a/src/wx/playlist_controls.cc b/src/wx/playlist_controls.cc
index d0d0c1d23..a0f91b00f 100644
--- a/src/wx/playlist_controls.cc
+++ b/src/wx/playlist_controls.cc
@@ -52,7 +52,11 @@ using std::shared_ptr;
using std::sort;
using std::string;
using std::vector;
+using std::weak_ptr;
using boost::optional;
+#if BOOST_VERSION >= 106100
+using namespace boost::placeholders;
+#endif
using namespace dcpomatic;
using namespace dcpomatic::ui;
@@ -72,26 +76,26 @@ PlaylistControls::PlaylistControls(wxWindow* parent, PlayerFrame* player, FilmVi
_button_sizer->Add(_stop_button, 0, wxEXPAND);
_button_sizer->Add(_next_button, 0, wxEXPAND);
- _spl_view = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
- _spl_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 740);
+ _playlists_view = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
+ _playlists_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 740);
auto left_sizer = new wxBoxSizer(wxVERTICAL);
- auto e_sizer = new wxBoxSizer(wxHORIZONTAL);
+ auto h_sizer = new wxBoxSizer(wxHORIZONTAL);
wxFont subheading_font(*wxNORMAL_FONT);
subheading_font.SetWeight(wxFONTWEIGHT_BOLD);
- auto spl_header = new wxBoxSizer(wxHORIZONTAL);
+ auto playlists_header = new wxBoxSizer(wxHORIZONTAL);
{
auto m = new StaticText(this, _("Playlists"));
m->SetFont(subheading_font);
- spl_header->Add(m, 1, wxALIGN_CENTER_VERTICAL);
+ playlists_header->Add(m, 1, wxALIGN_CENTER_VERTICAL);
}
- _refresh_spl_view = new Button(this, _("Refresh"));
- spl_header->Add(_refresh_spl_view, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP / 2);
+ _refresh_playlists_view = new Button(this, _("Refresh"));
+ playlists_header->Add(_refresh_playlists_view, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP / 2);
- left_sizer->Add(spl_header, 0, wxLEFT | wxRIGHT | wxEXPAND, DCPOMATIC_SIZER_GAP);
- left_sizer->Add(_spl_view, 1, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ left_sizer->Add(playlists_header, 0, wxLEFT | wxRIGHT | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ left_sizer->Add(_playlists_view, 1, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, DCPOMATIC_SIZER_GAP);
_content_view = new ContentView(this);
@@ -107,24 +111,54 @@ PlaylistControls::PlaylistControls(wxWindow* parent, PlayerFrame* player, FilmVi
left_sizer->Add(content_header, 0, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, DCPOMATIC_SIZER_GAP);
left_sizer->Add(_content_view, 1, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, DCPOMATIC_SIZER_GAP);
- _current_spl_view = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
- _current_spl_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 500);
- _current_spl_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 80);
- e_sizer->Add(left_sizer, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
- e_sizer->Add(_current_spl_view, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ auto right_sizer = new wxBoxSizer(wxVERTICAL);
- _v_sizer->Add(e_sizer, 1, wxEXPAND);
+ _next_playlist_view = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
+ _next_playlist_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 600);
+ _next_playlist_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 80);
+
+ auto next_playlist_header = new wxBoxSizer(wxHORIZONTAL);
+ {
+ auto m = new StaticText(this, _("Next playlist"));
+ m->SetFont(subheading_font);
+ next_playlist_header->Add(m, 1, wxALIGN_CENTER_VERTICAL);
+ }
+ _clear_next_playlist = new wxButton(this, wxID_ANY, _("Clear"));
+ next_playlist_header->Add(_clear_next_playlist, 0, wxBOTTOM, DCPOMATIC_SIZER_GAP / 2);
+
+ right_sizer->Add(next_playlist_header, 0, wxLEFT | wxRIGHT | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ right_sizer->Add(_next_playlist_view, 1, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, DCPOMATIC_SIZER_GAP);
+
+ _current_playlist_view = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_NO_HEADER);
+ _current_playlist_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 600);
+ _current_playlist_view->AppendColumn({}, wxLIST_FORMAT_LEFT, 80);
+
+ auto current_playlist_header = new wxBoxSizer(wxHORIZONTAL);
+ {
+ auto m = new StaticText(this, _("Current playlist"));
+ m->SetFont(subheading_font);
+ current_playlist_header->Add(m, 1, wxALIGN_CENTER_VERTICAL);
+ }
+
+ right_sizer->Add(current_playlist_header, 0, wxTOP | wxBOTTOM | wxLEFT | wxRIGHT | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ right_sizer->Add(_current_playlist_view, 1, wxLEFT | wxRIGHT | wxBOTTOM | wxEXPAND, DCPOMATIC_SIZER_GAP);
+
+ h_sizer->Add(left_sizer, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ h_sizer->Add(right_sizer, 1, wxALL | wxEXPAND, DCPOMATIC_SIZER_GAP);
+ _v_sizer->Add(h_sizer, 1, wxEXPAND);
_play_button->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::play_clicked, this));
_pause_button->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::pause_clicked, this));
_stop_button->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::stop_clicked, this));
_next_button->Bind (wxEVT_BUTTON, boost::bind(&PlaylistControls::next_clicked, this));
_previous_button->Bind(wxEVT_BUTTON, boost::bind(&PlaylistControls::previous_clicked, this));
- _spl_view->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&PlaylistControls::spl_selection_changed, this));
- _spl_view->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&PlaylistControls::spl_selection_changed, this));
- _viewer.Finished.connect(boost::bind(&PlaylistControls::viewer_finished, this));
- _refresh_spl_view->Bind(wxEVT_BUTTON, boost::bind(&PlaylistControls::update_playlists, this));
+ _playlists_view->Bind (wxEVT_LIST_ITEM_SELECTED, boost::bind(&PlaylistControls::playlist_selection_changed, this));
+ _playlists_view->Bind (wxEVT_LIST_ITEM_DESELECTED, boost::bind(&PlaylistControls::playlist_selection_changed, this));
+ _refresh_playlists_view->Bind(wxEVT_BUTTON, boost::bind(&PlaylistControls::update_playlists, this));
_refresh_content_view->Bind(wxEVT_BUTTON, boost::bind(&ContentView::update, _content_view));
+ _clear_next_playlist->Bind(wxEVT_BUTTON, boost::bind(&PlaylistControls::clear_next_playlist, this));
+
+ _content_view->Activated.connect(boost::bind(&PlaylistControls::content_activated, this, _1));
update_playlists();
_content_view->update();
@@ -132,40 +166,45 @@ PlaylistControls::PlaylistControls(wxWindow* parent, PlayerFrame* player, FilmVi
void
-PlaylistControls::started()
+PlaylistControls::clear_next_playlist()
{
- Controls::started();
- _play_button->Enable(false);
- _pause_button->Enable(true);
+ _next_playlist_view->DeleteAllItems();
+ _next_playlist.clear();
}
-/** Called when the viewer finishes a single piece of content, or it is explicitly stopped */
void
-PlaylistControls::stopped()
+PlaylistControls::content_activated(weak_ptr<Content> weak_content)
{
- Controls::stopped();
- _play_button->Enable(true);
- _pause_button->Enable(false);
+ if (auto content = weak_content.lock()) {
+ add_next_playlist_entry(ShowPlaylistEntry(content, {}));
+ }
}
void
-PlaylistControls::deselect_playlist()
+PlaylistControls::started()
{
- long int const selected = _spl_view->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
- if (selected != -1) {
- _selected_playlist = boost::none;
- _spl_view->SetItemState(selected, 0, wxLIST_STATE_SELECTED);
- }
- _player->reset_film();
+ Controls::started();
+ setup_sensitivity();
+}
+
+
+/** Called when the viewer finishes a single piece of content, or it is explicitly stopped */
+void
+PlaylistControls::stopped()
+{
+ Controls::stopped();
+ setup_sensitivity();
}
void
PlaylistControls::play_clicked()
{
- _viewer.start();
+ if (_player->set_playlist(_next_playlist)) {
+ _viewer.start();
+ }
}
@@ -173,92 +212,70 @@ void
PlaylistControls::setup_sensitivity()
{
Controls::setup_sensitivity();
- bool const active_job = _active_job && *_active_job != "examine_content";
- bool const c = _film && !_film->content().empty() && !active_job;
- _play_button->Enable(c && !_viewer.playing());
- _pause_button->Enable(_viewer.playing());
- _spl_view->Enable(!_viewer.playing());
- _next_button->Enable(can_do_next());
- _previous_button->Enable(can_do_previous());
+ _play_button->Enable(!_viewer.playing() && !_paused && !_next_playlist.empty());
+ _pause_button->Enable(_viewer.playing() || _paused);
+ _stop_button->Enable(_viewer.playing() || _paused);
+ _next_button->Enable(_player->can_do_next());
+ _previous_button->Enable(_player->can_do_previous());
}
void
PlaylistControls::pause_clicked()
{
- _viewer.stop();
+ if (_paused) {
+ _viewer.start();
+ _paused = false;
+ } else {
+ _viewer.stop();
+ _paused = true;
+ }
+ setup_sensitivity();
}
void
PlaylistControls::stop_clicked()
{
+ _paused = false;
_viewer.stop();
_viewer.seek(DCPTime(), true);
- if (_selected_playlist) {
- _selected_playlist_position = 0;
- update_current_content();
- }
- deselect_playlist();
-}
-
-
-bool
-PlaylistControls::can_do_previous()
-{
- return _selected_playlist && (_selected_playlist_position - 1) >= 0;
+ _player->set_playlist({});
}
void
PlaylistControls::previous_clicked()
{
- if (!can_do_previous()) {
- return;
- }
-
- _selected_playlist_position--;
- update_current_content();
-}
-
-
-bool
-PlaylistControls::can_do_next()
-{
- return _selected_playlist && (_selected_playlist_position + 1) < static_cast<int>(_playlists->entries(*_selected_playlist).size());
+ _player->previous();
}
void
PlaylistControls::next_clicked()
{
- if (!can_do_next()) {
- return;
- }
-
- _selected_playlist_position++;
- update_current_content();
+ _player->next();
}
void
-PlaylistControls::add_playlist_to_list(ShowPlaylist spl)
+PlaylistControls::add_playlist_to_list(ShowPlaylist playlist)
{
- int const N = _spl_view->GetItemCount();
+ int const N = _playlists_view->GetItemCount();
wxListItem it;
it.SetId(N);
it.SetColumn(0);
- auto id = _playlists->get_show_playlist_id(spl.uuid());
+ auto id = _playlists->get_show_playlist_id(playlist.uuid());
DCPOMATIC_ASSERT(id);
it.SetData(id->get());
- string t = spl.name();
+ string t = playlist.name();
- if (_playlists->missing(spl.uuid())) {
+ if (_playlists->missing(playlist.uuid())) {
t += " (content missing)";
}
it.SetText(std_to_wx(t));
- _spl_view->InsertItem(it);
+ _playlists_view->InsertItem(it);
}
@@ -267,14 +284,12 @@ PlaylistControls::update_playlists()
{
using namespace boost::filesystem;
- _spl_view->DeleteAllItems();
+ _playlists_view->DeleteAllItems();
_playlists.reset(new ShowPlaylistList());
for (auto i: _playlists->show_playlists()) {
add_playlist_to_list(i.second);
}
-
- _selected_playlist = boost::none;
}
@@ -304,20 +319,31 @@ PlaylistControls::get_kdm_from_directory(shared_ptr<DCPContent> dcp)
void
-PlaylistControls::spl_selection_changed()
+PlaylistControls::add_next_playlist_entry(ShowPlaylistEntry entry)
{
- long int selected = _spl_view->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
+ wxListItem it;
+ it.SetId(_next_playlist_view->GetItemCount());
+ it.SetColumn(0);
+ it.SetText(std_to_wx(entry.name()));
+ _next_playlist_view->InsertItem(it);
+ _next_playlist.push_back(entry);
+
+ setup_sensitivity();
+}
+
+
+void
+PlaylistControls::playlist_selection_changed()
+{
+ long int selected = _playlists_view->GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
if (selected == -1) {
- _current_spl_view->DeleteAllItems();
- _selected_playlist = boost::none;
return;
}
- auto const id = ShowPlaylistID(_spl_view->GetItemData(selected));
+ auto const id = ShowPlaylistID(_playlists_view->GetItemData(selected));
if (_playlists->missing(id)) {
error_dialog(this, _("This playlist cannot be loaded as some content is missing."));
- deselect_playlist();
return;
}
@@ -326,72 +352,11 @@ PlaylistControls::spl_selection_changed()
return;
}
- select_playlist(id, 0);
-}
-
-
-void
-PlaylistControls::select_playlist(ShowPlaylistID selected, int position)
-{
- wxProgressDialog dialog(variant::wx::dcpomatic(), _("Loading playlist and KDMs"));
-
- auto const store = ShowPlaylistContentStore::instance();
- for (auto const& i: _playlists->entries(selected)) {
- dialog.Pulse();
- auto dcp = dynamic_pointer_cast<DCPContent>(store->get(i));
- if (dcp && dcp->needs_kdm()) {
- optional<dcp::EncryptedKDM> kdm;
- kdm = get_kdm_from_directory(dcp);
- if (kdm) {
- try {
- dcp->add_kdm(*kdm);
- dcp->examine(shared_ptr<Job>(), true);
- } catch (KDMError& e) {
- error_dialog(this, _("Could not load KDM."));
- }
- }
- if (dcp->needs_kdm()) {
- /* We didn't get a KDM for this */
- error_dialog(this, _("This playlist cannot be loaded as a KDM is missing or incorrect."));
- deselect_playlist();
- return;
- }
- }
- }
-
- _current_spl_view->DeleteAllItems();
-
- int N = 0;
- for (auto const& i: _playlists->entries(selected)) {
- wxListItem it;
- it.SetId(N);
- it.SetColumn(0);
- it.SetText(std_to_wx(i.name()));
- _current_spl_view->InsertItem(it);
- ++N;
+ _next_playlist_view->DeleteAllItems();
+ _next_playlist.clear();
+ for (auto const& i: _playlists->entries(id)) {
+ add_next_playlist_entry(i);
}
-
- _selected_playlist = selected;
- _selected_playlist_position = position;
- dialog.Pulse();
- reset_film();
- dialog.Pulse();
- update_current_content();
-}
-
-
-void
-PlaylistControls::reset_film()
-{
- DCPOMATIC_ASSERT(_selected_playlist);
-
- auto film = std::make_shared<Film>(optional<boost::filesystem::path>());
-
- auto const entries = _playlists->entries(*_selected_playlist);
- DCPOMATIC_ASSERT(_selected_playlist_position < static_cast<int>(entries.size()));
- auto const entry = entries[_selected_playlist_position];
- film->add_content(vector<shared_ptr<Content>>{ShowPlaylistContentStore::instance()->get(entry)});
- _player->reset_film(film, entry.crop_to_ratio());
}
@@ -409,50 +374,18 @@ PlaylistControls::config_changed(int property)
void
-PlaylistControls::update_current_content()
-{
- DCPOMATIC_ASSERT(_selected_playlist);
-
- wxProgressDialog dialog(variant::wx::dcpomatic(), _("Loading content"));
-
- setup_sensitivity();
- dialog.Pulse();
- reset_film();
-}
-
-
-/** One piece of content in our SPL has finished playing */
-void
-PlaylistControls::viewer_finished()
+PlaylistControls::playlist_changed()
{
- if (!_selected_playlist) {
- return;
- }
+ _current_playlist_view->DeleteAllItems();
- _selected_playlist_position++;
- if (_selected_playlist_position < int(_playlists->entries(*_selected_playlist).size())) {
- /* Next piece of content on the SPL */
- update_current_content();
- _viewer.start();
- } else {
- /* Finished the whole SPL */
- _selected_playlist_position = 0;
- _player->reset_film();
- _play_button->Enable(true);
- _pause_button->Enable(false);
+ int N = 0;
+ for (auto content: _player->playlist()) {
+ wxListItem it;
+ it.SetId(N++);
+ it.SetColumn(0);
+ ShowPlaylistEntry entry(content, {});
+ it.SetText(std_to_wx(entry.name()));
+ _current_playlist_view->InsertItem(it);
}
}
-
-void
-PlaylistControls::play()
-{
- play_clicked();
-}
-
-
-void
-PlaylistControls::stop()
-{
- stop_clicked();
-}
diff --git a/src/wx/playlist_controls.h b/src/wx/playlist_controls.h
index 71f9d7f47..da17358cb 100644
--- a/src/wx/playlist_controls.h
+++ b/src/wx/playlist_controls.h
@@ -41,8 +41,7 @@ class PlaylistControls : public Controls
public:
PlaylistControls(wxWindow* parent, dcpomatic::ui::PlayerFrame* player, FilmViewer& viewer);
- void play() override;
- void stop() override;
+ void playlist_changed() override;
private:
void play_clicked();
@@ -50,21 +49,20 @@ private:
void stop_clicked();
void next_clicked();
void previous_clicked();
- void add_playlist_to_list(ShowPlaylist spl);
- void update_content_directory();
+
void update_playlists();
- void spl_selection_changed();
- void select_playlist(ShowPlaylistID selected, int position);
+ void playlist_selection_changed();
+
+ void config_changed(int) override;
void started() override;
void stopped() override;
+
void setup_sensitivity() override;
- void config_changed(int) override;
- void viewer_finished();
- void reset_film();
- void update_current_content();
- bool can_do_previous();
- bool can_do_next();
- void deselect_playlist();
+
+ void add_playlist_to_list(ShowPlaylist spl);
+ void add_next_playlist_entry(ShowPlaylistEntry entry);
+ void clear_next_playlist();
+ void content_activated(std::weak_ptr<Content> content);
boost::optional<dcp::EncryptedKDM> get_kdm_from_directory(std::shared_ptr<DCPContent> dcp);
@@ -78,11 +76,15 @@ private:
ContentView* _content_view;
wxButton* _refresh_content_view;
- wxListCtrl* _spl_view;
- wxButton* _refresh_spl_view;
- wxListCtrl* _current_spl_view;
+ wxListCtrl* _playlists_view;
+ wxButton* _refresh_playlists_view;
+ wxListCtrl* _next_playlist_view;
+ wxButton* _clear_next_playlist;
+
+ wxListCtrl* _current_playlist_view;
std::unique_ptr<ShowPlaylistList> _playlists;
- boost::optional<ShowPlaylistID> _selected_playlist;
- int _selected_playlist_position;
+ std::vector<ShowPlaylistEntry> _next_playlist;
+
+ bool _paused = false;
};
diff --git a/src/wx/standard_controls.cc b/src/wx/standard_controls.cc
index 9126f24b4..03934e482 100644
--- a/src/wx/standard_controls.cc
+++ b/src/wx/standard_controls.cc
@@ -87,18 +87,3 @@ StandardControls::setup_sensitivity ()
_play_button->Enable (_film && !_film->content().empty() && !active_job);
}
-
-void
-StandardControls::play ()
-{
- _play_button->SetValue (true);
- play_clicked ();
-}
-
-
-void
-StandardControls::stop ()
-{
- _play_button->SetValue (false);
- play_clicked ();
-}
diff --git a/src/wx/standard_controls.h b/src/wx/standard_controls.h
index 1b8618763..21f1b0039 100644
--- a/src/wx/standard_controls.h
+++ b/src/wx/standard_controls.h
@@ -27,9 +27,6 @@ class StandardControls : public Controls
public:
StandardControls(wxWindow* parent, FilmViewer& viewer, bool editor_controls);
- void play () override;
- void stop () override;
-
private:
void check_play_state ();
void play_clicked ();
diff --git a/web/index.html b/web/index.html
index db313c48e..b0b5b4505 100644
--- a/web/index.html
+++ b/web/index.html
@@ -24,33 +24,6 @@ function stop() {
fetch("/api/v1/stop", { method: "POST" });
}
-function selectedPlaylistId()
-{
- var children = document.getElementById("content-and-playlists-inner").children;
- for (var i = 0; i < children.length; i++) {
- if (children[i].classList.contains("selected")) {
- return children[i].uuid;
- }
- }
-
- return null;
-}
-
-
-function setSelectedPlaylist(uuid)
-{
- var children = document.getElementById("content-and-playlists-inner").children;
- for (var i = 0; i < children.length; i++) {
- var child = children[i];
- if (child.uuid == uuid) {
- child.classList.add("selected");
- } else {
- child.classList.remove("selected");
- }
- }
-};
-
-
// Fetch all playlists and put them in the shared content/playlists div
function showPlaylists()
{
@@ -67,7 +40,20 @@ function showPlaylists()
li.appendChild(document.createTextNode(playlist.name));
li.uuid = playlist['uuid'];
li.onclick = function() {
- setSelectedPlaylist(playlist['uuid']);
+ fetch("/api/v1/playlist/" + playlist['uuid']).then(response => {
+ response.json().then(data => {
+ var nextPlaylist = document.getElementById('next-playlist');
+ nextPlaylist.innerHTML = "";
+ var ul = document.createElement("ul");
+ nextPlaylist.appendChild(ul);
+ data.content.forEach(content => {
+ var li = document.createElement("li");
+ li.appendChild(document.createTextNode(content.name));
+ ul.appendChild(li);
+ });
+ console.log(data);
+ })
+ });
};
document.getElementById('content-and-playlists-inner').appendChild(li);
});
@@ -159,6 +145,8 @@ li.playlist.selected {
SIDEBAR
+<div style="margin-left: 15%;">
+
<div id="controls" style="float: left; width: 20%;">
<button name="play" value="play" onclick="play()">Play</button>
<button name="stop" value="stop" onclick="stop()">Stop</button>
@@ -169,14 +157,29 @@ SIDEBAR
</table>
</div>
-<div id="current-playlist" style="float: left; width: 20%;">
+<div style="float: right; width: 70%;">
+
+<div style="float: right; width: 100%;">
+Next playlist:
+<div id="next-playlist" style="width: 100%; height: 40vh;">
+</div>
+</div>
+
+<div style="float: right; width: 100%; margin-top: 4em;">
Current playlist:
+<div id="current-playlist" style="width: 100%; height: 10vh;">
+</div>
</div>
-<div id="content-and-playlists" style="float: left; width: 20%;">
+</div>
+
+<div id="content-and-playlists" style="float: left; width: 20%; left-margin: 10%;">
<div id="playlists-tab" onclick="showPlaylists();" class="tab selected">Playlists</div>
<div id="content-tab" onclick="showContent();" class="tab">Content</div>
<div id="content-and-playlists-inner" style="margin-top: 5pt; margin-bottom: 5pt;"></div>
</div>
+
+</div>
+
</body>
</html>
diff --git a/web/sidebar.html b/web/sidebar.html
index 7ea629cc0..b7c3e7555 100644
--- a/web/sidebar.html
+++ b/web/sidebar.html
@@ -1,7 +1,9 @@
<style>
div.nav {
- float: left;
margin-right: 4em;
+ height: 100vh;
+ width: 10%;
+ position: fixed;
}
li.nav {
border: 1px solid rgba(57, 61, 65, 0.50);