Fix 3D crash.
[dcpomatic.git] / src / wx / film_viewer.cc
index ccb2b790de4fe816e03c7846967ebff5b581b1b4..76e269975fb0eea467589e0a4a9ed775c1983c73 100644 (file)
@@ -26,6 +26,7 @@
 #include "playhead_to_timecode_dialog.h"
 #include "playhead_to_frame_dialog.h"
 #include "wx_util.h"
+#include "closed_captions_dialog.h"
 #include "lib/film.h"
 #include "lib/ratio.h"
 #include "lib/util.h"
@@ -76,8 +77,7 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        : wxPanel (p)
        , _panel (new wxPanel (this))
        , _outline_content (0)
-       , _left_eye (new wxRadioButton (this, wxID_ANY, _("Left eye"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP))
-       , _right_eye (new wxRadioButton (this, wxID_ANY, _("Right eye")))
+       , _eye (0)
        , _jump_to_selected (0)
        , _slider (new wxSlider (this, wxID_ANY, 0, 0, 4096))
        , _rewind_button (new wxButton (this, wxID_ANY, wxT("|<")))
@@ -95,6 +95,7 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        , _playing (false)
        , _latency_history_count (0)
        , _dropped (0)
+       , _closed_captions_dialog (new ClosedCaptionsDialog(GetParent()))
 {
 #ifndef __WXOSX__
        _panel->SetDoubleBuffered (true);
@@ -110,14 +111,20 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        wxBoxSizer* view_options = new wxBoxSizer (wxHORIZONTAL);
        if (outline_content) {
                _outline_content = new wxCheckBox (this, wxID_ANY, _("Outline content"));
-               view_options->Add (_outline_content, 0, wxRIGHT, DCPOMATIC_SIZER_GAP);
+               view_options->Add (_outline_content, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
        }
-       view_options->Add (_left_eye, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
-       view_options->Add (_right_eye, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
+
+       _eye = new wxChoice (this, wxID_ANY);
+       _eye->Append (_("Left"));
+       _eye->Append (_("Right"));
+       _eye->SetSelection (0);
+       view_options->Add (_eye, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
+
        if (jump_to_selected) {
                _jump_to_selected = new wxCheckBox (this, wxID_ANY, _("Jump to selected content"));
-               view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT, DCPOMATIC_SIZER_GAP);
+               view_options->Add (_jump_to_selected, 0, wxLEFT | wxRIGHT | wxALIGN_CENTER_VERTICAL, DCPOMATIC_SIZER_GAP);
        }
+
        _v_sizer->Add (view_options, 0, wxALL, DCPOMATIC_SIZER_GAP);
 
        wxBoxSizer* h_sizer = new wxBoxSizer (wxHORIZONTAL);
@@ -140,24 +147,23 @@ FilmViewer::FilmViewer (wxWindow* p, bool outline_content, bool jump_to_selected
        _back_button->SetMinSize (wxSize (32, -1));
        _forward_button->SetMinSize (wxSize (32, -1));
 
-       _panel->Bind            (wxEVT_PAINT,             boost::bind (&FilmViewer::paint_panel,     this));
-       _panel->Bind            (wxEVT_SIZE,              boost::bind (&FilmViewer::panel_sized,     this, _1));
+       _panel->Bind            (wxEVT_PAINT,               boost::bind (&FilmViewer::paint_panel,     this));
+       _panel->Bind            (wxEVT_SIZE,                boost::bind (&FilmViewer::panel_sized,     this, _1));
        if (_outline_content) {
-               _outline_content->Bind  (wxEVT_CHECKBOX, boost::bind (&FilmViewer::refresh_panel,   this));
+               _outline_content->Bind  (wxEVT_CHECKBOX,    boost::bind (&FilmViewer::refresh_panel,   this));
        }
-       _left_eye->Bind         (wxEVT_RADIOBUTTON,       boost::bind (&FilmViewer::slow_refresh,    this));
-       _right_eye->Bind        (wxEVT_RADIOBUTTON,       boost::bind (&FilmViewer::slow_refresh,    this));
-       _slider->Bind           (wxEVT_SCROLL_THUMBTRACK, boost::bind (&FilmViewer::slider_moved,    this, false));
-       _slider->Bind           (wxEVT_SCROLL_PAGEUP,     boost::bind (&FilmViewer::slider_moved,    this, true));
-       _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,   boost::bind (&FilmViewer::slider_moved,    this, true));
+       _eye->Bind              (wxEVT_CHOICE,              boost::bind (&FilmViewer::slow_refresh,    this));
+       _slider->Bind           (wxEVT_SCROLL_THUMBTRACK,   boost::bind (&FilmViewer::slider_moved,    this, false));
+       _slider->Bind           (wxEVT_SCROLL_PAGEUP,       boost::bind (&FilmViewer::slider_moved,    this, true));
+       _slider->Bind           (wxEVT_SCROLL_PAGEDOWN,     boost::bind (&FilmViewer::slider_moved,    this, true));
        _slider->Bind           (wxEVT_SCROLL_THUMBRELEASE, boost::bind (&FilmViewer::slider_released, this));
-       _play_button->Bind      (wxEVT_TOGGLEBUTTON,      boost::bind (&FilmViewer::play_clicked,    this));
-       _timer.Bind             (wxEVT_TIMER,             boost::bind (&FilmViewer::timer,           this));
-       _rewind_button->Bind    (wxEVT_LEFT_DOWN,         boost::bind (&FilmViewer::rewind_clicked,  this, _1));
-       _back_button->Bind      (wxEVT_LEFT_DOWN,         boost::bind (&FilmViewer::back_clicked,    this, _1));
-       _forward_button->Bind   (wxEVT_LEFT_DOWN,         boost::bind (&FilmViewer::forward_clicked, this, _1));
-       _frame_number->Bind     (wxEVT_LEFT_DOWN,         boost::bind (&FilmViewer::frame_number_clicked, this));
-       _timecode->Bind         (wxEVT_LEFT_DOWN,         boost::bind (&FilmViewer::timecode_clicked, this));
+       _play_button->Bind      (wxEVT_TOGGLEBUTTON,        boost::bind (&FilmViewer::play_clicked,    this));
+       _timer.Bind             (wxEVT_TIMER,               boost::bind (&FilmViewer::timer,           this));
+       _rewind_button->Bind    (wxEVT_LEFT_DOWN,           boost::bind (&FilmViewer::rewind_clicked,  this, _1));
+       _back_button->Bind      (wxEVT_LEFT_DOWN,           boost::bind (&FilmViewer::back_clicked,    this, _1));
+       _forward_button->Bind   (wxEVT_LEFT_DOWN,           boost::bind (&FilmViewer::forward_clicked, this, _1));
+       _frame_number->Bind     (wxEVT_LEFT_DOWN,           boost::bind (&FilmViewer::frame_number_clicked, this));
+       _timecode->Bind         (wxEVT_LEFT_DOWN,           boost::bind (&FilmViewer::timecode_clicked, this));
        if (_jump_to_selected) {
                _jump_to_selected->Bind (wxEVT_CHECKBOX, boost::bind (&FilmViewer::jump_to_selected_clicked, this));
                _jump_to_selected->SetValue (Config::instance()->jump_to_selected ());
@@ -190,6 +196,7 @@ FilmViewer::set_film (shared_ptr<Film> film)
        _film = film;
 
        _frame.reset ();
+       _closed_captions_dialog->clear ();
 
        update_position_slider ();
        update_position_label ();
@@ -214,14 +221,11 @@ FilmViewer::set_film (shared_ptr<Film> film)
                return;
        }
 
-       /* Always burn in subtitles, even if content is set not to, otherwise we won't see them
-          in the preview.
-       */
-       _player->set_always_burn_subtitles (true);
+       _player->set_always_burn_open_subtitles ();
        _player->set_play_referenced ();
 
-       _film->Changed.connect (boost::bind (&FilmViewer::film_changed, this, _1));
-       _player->Changed.connect (boost::bind (&FilmViewer::player_changed, this, _1, _2));
+       _film->Change.connect (boost::bind (&FilmViewer::film_change, this, _1, _2));
+       _player->Change.connect (boost::bind (&FilmViewer::player_change, this, _1, _2, _3));
 
        /* Keep about 1 second's worth of history samples */
        _latency_history_count = _film->audio_frame_rate() / _audio_block_size;
@@ -251,13 +255,18 @@ FilmViewer::recreate_butler ()
                        map.set (i, i, 1);
                }
        } else {
-               /* Special case: stereo output, at least 3 channel input, map L+R to L/R and
-                  C to both, all 3dB down.
+               /* Special case: stereo output, at least 3 channel input.
+                  Map so that Lt = L(-3dB) + Ls(-3dB) + C(-6dB) + Lfe(-10dB)
+                              Rt = R(-3dB) + Rs(-3dB) + C(-6dB) + Lfe(-10dB)
                */
-               map.set (0, 0, 1 / sqrt(2)); // L -> L
-               map.set (1, 1, 1 / sqrt(2)); // R -> R
-               map.set (2, 0, 1 / sqrt(2)); // C -> L
-               map.set (2, 1, 1 / sqrt(2)); // C -> R
+               map.set (dcp::LEFT,   0, 1 / sqrt(2)); // L -> Lt
+               map.set (dcp::RIGHT,  1, 1 / sqrt(2)); // R -> Rt
+               map.set (dcp::CENTRE, 0, 1 / 2.0); // C -> Lt
+               map.set (dcp::CENTRE, 1, 1 / 2.0); // C -> Rt
+               map.set (dcp::LFE,    0, 1 / sqrt(10)); // Lfe -> Lt
+               map.set (dcp::LFE,    1, 1 / sqrt(10)); // Lfe -> Rt
+               map.set (dcp::LS,     0, 1 / sqrt(2)); // Ls -> Lt
+               map.set (dcp::RS,     1, 1 / sqrt(2)); // Rs -> Rt
        }
 
        _butler.reset (new Butler (_player, _film->log(), map, _audio_channels));
@@ -265,6 +274,8 @@ FilmViewer::recreate_butler ()
                _butler->disable_audio ();
        }
 
+       _closed_captions_dialog->set_butler (_butler);
+
        if (was_running) {
                start ();
        }
@@ -285,8 +296,9 @@ FilmViewer::get ()
        do {
                _player_video = _butler->get_video ();
        } while (
+               _player_video.first &&
                _film->three_d() &&
-               ((_left_eye->GetValue() && _player_video.first->eyes() == EYES_RIGHT) || (_right_eye->GetValue() && _player_video.first->eyes() == EYES_LEFT))
+               ((_eye->GetSelection() == 0 && _player_video.first->eyes() == EYES_RIGHT) || (_eye->GetSelection() == 1 && _player_video.first->eyes() == EYES_LEFT))
                );
 
        _butler->rethrow ();
@@ -343,6 +355,8 @@ FilmViewer::display_player_video ()
        _inter_size = _player_video.first->inter_size ();
 
        refresh_panel ();
+
+       _closed_captions_dialog->update (time());
 }
 
 void
@@ -421,6 +435,7 @@ FilmViewer::slider_moved (bool page)
        }
 
        DCPTime t (_slider->GetValue() * _film->length().get() / 4096);
+       t = t.round (_film->video_frame_rate());
        /* Ensure that we hit the end of the film at the end of the slider */
        if (t >= _film->length ()) {
                t = _film->length() - one_video_frame();
@@ -456,7 +471,7 @@ FilmViewer::panel_sized (wxSizeEvent& ev)
 void
 FilmViewer::calculate_sizes ()
 {
-       if (!_film) {
+       if (!_film || !_player) {
                return;
        }
 
@@ -651,9 +666,9 @@ FilmViewer::forward_clicked (wxKeyboardState& ev)
 }
 
 void
-FilmViewer::player_changed (int property, bool frequent)
+FilmViewer::player_change (ChangeType type, int property, bool frequent)
 {
-       if (frequent) {
+       if (type != CHANGE_TYPE_DONE || frequent) {
                return;
        }
 
@@ -702,13 +717,16 @@ FilmViewer::setup_sensitivity ()
                _jump_to_selected->Enable (c);
        }
 
-       _left_eye->Enable (c && _film->three_d ());
-       _right_eye->Enable (c && _film->three_d ());
+       _eye->Enable (c && _film->three_d ());
 }
 
 void
-FilmViewer::film_changed (Film::Property p)
+FilmViewer::film_change (ChangeType type, Film::Property p)
 {
+       if (type != CHANGE_TYPE_DONE) {
+               return;
+       }
+
        if (p == Film::CONTENT || p == Film::THREE_D) {
                setup_sensitivity ();
        } else if (p == Film::AUDIO_CHANNELS) {
@@ -751,6 +769,15 @@ FilmViewer::set_position (DCPTime p)
        update_position_slider ();
 }
 
+void
+FilmViewer::set_position (shared_ptr<Content> content, ContentTime t)
+{
+       optional<DCPTime> dt = _player->content_time_to_dcp (content, t);
+       if (dt) {
+               set_position (*dt);
+       }
+}
+
 void
 FilmViewer::set_coalesce_player_changes (bool c)
 {
@@ -758,7 +785,7 @@ FilmViewer::set_coalesce_player_changes (bool c)
 
        if (!c) {
                BOOST_FOREACH (int i, _pending_player_changes) {
-                       player_changed (i, false);
+                       player_change (CHANGE_TYPE_DONE, i, false);
                }
                _pending_player_changes.clear ();
        }
@@ -799,6 +826,7 @@ FilmViewer::seek (DCPTime t, bool accurate)
 
        bool const was_running = stop ();
 
+       _closed_captions_dialog->clear ();
        _butler->seek (t, accurate);
        get ();
 
@@ -860,6 +888,16 @@ FilmViewer::config_changed (Config::Property p)
        }
 }
 
+DCPTime
+FilmViewer::uncorrected_time () const
+{
+       if (_audio.isStreamRunning ()) {
+               return DCPTime::from_seconds (const_cast<RtAudio*>(&_audio)->getStreamTime());
+       }
+
+       return _video_position;
+}
+
 DCPTime
 FilmViewer::time () const
 {
@@ -874,7 +912,14 @@ FilmViewer::time () const
 int
 FilmViewer::audio_callback (void* out_p, unsigned int frames)
 {
-       _butler->get_audio (reinterpret_cast<float*> (out_p), frames);
+       while (true) {
+               optional<DCPTime> t = _butler->get_audio (reinterpret_cast<float*> (out_p), frames);
+               if (!t || DCPTime(uncorrected_time() - *t) < one_video_frame()) {
+                       /* There was an underrun or this audio is on time; carry on */
+                       break;
+               }
+               /* The audio we just got was (very) late; drop it and get some more. */
+       }
 
         boost::mutex::scoped_lock lm (_latency_history_mutex, boost::try_to_lock);
         if (lm) {
@@ -923,3 +968,9 @@ FilmViewer::one_video_frame () const
 {
        return DCPTime::from_frames (1, _film->video_frame_rate());
 }
+
+void
+FilmViewer::show_closed_captions ()
+{
+       _closed_captions_dialog->Show();
+}