diff --git a/README.md b/README.md index 9bb29d6ea..dc4f89ecf 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,7 @@ Optional dependencies: * [PNG](https://libpng.sourceforge.io/index.html) * [OpenEXR](https://www.openexr.com/) * [FFmpeg](https://ffmpeg.org) +* [OpenUSD](https://github.com/PixarAnimationStudios/OpenUSD) * [nativefiledialog-extended](https://github.com/btzy/nativefiledialog-extended) * [Qt version 5 or 6](https://www.qt.io) @@ -111,26 +112,23 @@ Optional dependencies: ## Building Dependencies -A CMake super build script is provided to build the dependencies from source, -except for Qt. Qt should be installed separately. +A CMake super build script is provided to build all of the dependencies from +source except for Qt. Qt needs to be installed separately: +https://www.qt.io/ ## Building on Linux Clone the repository: ``` git clone https://github.com/darbyjohnston/tlRender.git -cd tlRender -git submodule init -git submodule update ``` Create a build directory: ``` -mkdir build -cd build +mkdir build && cd build ``` Run CMake with the super build script: ``` -cmake ../etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH=$PWD/install -DCMAKE_BUILD_TYPE=Debug +cmake ../tlRender/etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH=$PWD/install -DCMAKE_BUILD_TYPE=Debug ``` Start the build: ``` @@ -139,32 +137,32 @@ cmake --build . -j 4 --config Debug Try running the `tlplay` application: ``` export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH -./tlRender/src/tlRender-build/bin/tlplay/tlplay ../etc/SampleData/MultipleClips.otio +``` +``` +./tlRender/src/tlRender-build/bin/tlplay/tlplay ../tlRender/etc/SampleData/MultipleClips.otio ``` ### Building on Linux with Qt 6 -When running CMake with the super build script, add the Qt location to -`CMAKE_PREFIX_PATH` (place double quotes around the list of paths), +Add the Qt location to `CMAKE_PREFIX_PATH` (place double quotes around the list of paths) and enable `TLRENDER_QT6`: ``` -cmake ../etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/6.5.0/gcc_64" -DTLRENDER_QT6=ON -DCMAKE_BUILD_TYPE=Debug +cmake ../tlRender/etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/6.5.3/gcc_64" -DTLRENDER_QT6=ON -DCMAKE_BUILD_TYPE=Debug ``` ### Building on Linux with Qt 5 -When running CMake with the super build script, add the Qt location to -`CMAKE_PREFIX_PATH` (place double quotes around the list of paths), +Add the Qt location to `CMAKE_PREFIX_PATH` (place double quotes around the list of paths) and enable `TLRENDER_QT5`: ``` -cmake ../etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/5.15.2/gcc_64" -DTLRENDER_QT5=ON -DCMAKE_BUILD_TYPE=Debug +cmake ../tlRender/etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/5.15.2/gcc_64" -DTLRENDER_QT5=ON -DCMAKE_BUILD_TYPE=Debug ``` ### Minimal build on Linux -Build with only the required dependencies, disabling all optional dependencies. +Build with only the minimal required dependencies: ``` -cmake ../etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DTLRENDER_OCIO=OFF -DTLRENDER_AUDIO=OFF -DTLRENDER_JPEG=OFF -DTLRENDER_TIFF=OFF -DTLRENDER_STB=OFF -DTLRENDER_PNG=OFF -DTLRENDER_EXR=OFF -DTLRENDER_FFMPEG=OFF -DTLRENDER_PROGRAMS=OFF -DTLRENDER_EXAMPLES=OFF -DTLRENDER_TESTS=OFF +cmake ../tlRender/etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH=$PWD/install -DCMAKE_BUILD_TYPE=Debug -DTLRENDER_OCIO=OFF -DTLRENDER_AUDIO=OFF -DTLRENDER_JPEG=OFF -DTLRENDER_TIFF=OFF -DTLRENDER_STB=OFF -DTLRENDER_PNG=OFF -DTLRENDER_EXR=OFF -DTLRENDER_FFMPEG=OFF -DTLRENDER_PROGRAMS=OFF -DTLRENDER_EXAMPLES=OFF -DTLRENDER_TESTS=OFF ``` ### Notes for building on Linux @@ -179,18 +177,14 @@ gcovr -r ../../../../lib --html --object-directory lib --html-details --output g Clone the repository: ``` git clone https://github.com/darbyjohnston/tlRender.git -cd tlRender -git submodule init -git submodule update ``` Create a build directory: ``` -mkdir build -cd build +mkdir build && cd build ``` Run CMake with the super build script: ``` -cmake ../etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH=$PWD/install -DCMAKE_BUILD_TYPE=Debug +cmake ../tlRender/etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH=$PWD/install -DCMAKE_BUILD_TYPE=Debug ``` Start the build: ``` @@ -198,25 +192,23 @@ cmake --build . -j 4 --config Debug ``` Try running the `tlplay` application: ``` -./tlRender/src/tlRender-build/bin/tlplay/tlplay ../etc/SampleData/MultipleClips.otio +./tlRender/src/tlRender-build/bin/tlplay/tlplay ../tlRender/etc/SampleData/MultipleClips.otio ``` ### Building on macOS with Qt 6 -When running CMake with the super build script add the Qt location to -`CMAKE_PREFIX_PATH` (place double quotes around the list of paths), +Add the Qt location to `CMAKE_PREFIX_PATH` (place double quotes around the list of paths) and enable `TLRENDER_QT6`: ``` -cmake ../etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/6.5.0/macos" -DTLRENDER_QT6=ON -DCMAKE_BUILD_TYPE=Debug +cmake ../tlRender/etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/6.5.3/macos" -DTLRENDER_QT6=ON -DCMAKE_BUILD_TYPE=Debug ``` ### Building on macOS with Qt 5 -When running CMake with the super build script add the Qt location to -`CMAKE_PREFIX_PATH` (place double quotes around the list of paths), +Add the Qt location to `CMAKE_PREFIX_PATH` (place double quotes around the list of paths) and enable `TLRENDER_QT5`: ``` -cmake ../etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/5.15.2/clang_64" -DTLRENDER_QT5=ON -DCMAKE_BUILD_TYPE=Debug +cmake ../tlRender/etc/SuperBuild -DCMAKE_INSTALL_PREFIX=$PWD/install -DCMAKE_PREFIX_PATH="$PWD/install;$HOME/Qt/5.15.2/clang_64" -DTLRENDER_QT5=ON -DCMAKE_BUILD_TYPE=Debug ``` ### Notes for building on macOS @@ -246,18 +238,14 @@ Dependencies: Clone the repository: ``` git clone https://github.com/darbyjohnston/tlRender.git -cd tlRender -git submodule init -git submodule update ``` Create a build directory: ``` -mkdir build -cd build +mkdir build && cd build ``` Run CMake with the super build script: ``` -cmake ..\etc\SuperBuild -DCMAKE_INSTALL_PREFIX=%CD%\install -DCMAKE_PREFIX_PATH=%CD%\install -DCMAKE_BUILD_TYPE=Debug +cmake ..\tlRender\etc\SuperBuild -DCMAKE_INSTALL_PREFIX=%CD%\install -DCMAKE_PREFIX_PATH=%CD%\install -DCMAKE_BUILD_TYPE=Debug ``` Start the build: ``` @@ -266,23 +254,23 @@ cmake --build . -j 4 --config Debug Try running the `tlplay` application: ``` set PATH=%CD%\install\bin;%PATH% -.\tlRender\src\tlRender-build\bin\tlplay\Debug\tlplay ..\etc\SampleData\MultipleClips.otio +``` +``` +.\tlRender\src\tlRender-build\bin\tlplay\Debug\tlplay ..\tlRender\etc\SampleData\MultipleClips.otio ``` ### Building on Windows with Qt 6 -When running CMake with the super build script add the Qt location to -`CMAKE_PREFIX_PATH` (place double quotes around the list of paths), +Add the Qt location to `CMAKE_PREFIX_PATH` (place double quotes around the list of paths) and enable `TLRENDER_QT6`: ``` -cmake ..\etc\SuperBuild -DCMAKE_INSTALL_PREFIX=%CD%\install -DCMAKE_PREFIX_PATH="%CD%\install;C:\Qt\6.5.0\msvc2019_64" -DTLRENDER_QT6=ON -DCMAKE_BUILD_TYPE=Debug +cmake ..\tlRender\etc\SuperBuild -DCMAKE_INSTALL_PREFIX=%CD%\install -DCMAKE_PREFIX_PATH="%CD%\install;C:\Qt\6.5.3\msvc2019_64" -DTLRENDER_QT6=ON -DCMAKE_BUILD_TYPE=Debug ``` ### Building on Windows with Qt 5 -When running CMake with the super build script add the Qt location to -`CMAKE_PREFIX_PATH` (place double quotes around the list of paths), +Add the Qt location to `CMAKE_PREFIX_PATH` (place double quotes around the list of paths) and enable `TLRENDER_QT5`: ``` -cmake ..\etc\SuperBuild -DCMAKE_INSTALL_PREFIX=%CD%\install -DCMAKE_PREFIX_PATH="%CD%\install;C:\Qt\5.15.2\msvc2019_64" -DTLRENDER_QT5=ON -DCMAKE_BUILD_TYPE=Debug +cmake ..\tlRender\etc\SuperBuild -DCMAKE_INSTALL_PREFIX=%CD%\install -DCMAKE_PREFIX_PATH="%CD%\install;C:\Qt\5.15.2\msvc2019_64" -DTLRENDER_QT5=ON -DCMAKE_BUILD_TYPE=Debug ``` diff --git a/cmake/Modules/Package.cmake b/cmake/Modules/Package.cmake index c4e3a5b63..5ea5d2836 100644 --- a/cmake/Modules/Package.cmake +++ b/cmake/Modules/Package.cmake @@ -1,4 +1,4 @@ -set(CPACK_PACKAGE_FILE_NAME + set(CPACK_PACKAGE_FILE_NAME ${CMAKE_PROJECT_NAME}-${CMAKE_PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}) if(WIN32) @@ -186,9 +186,9 @@ elseif(APPLE) ${CMAKE_INSTALL_PREFIX}/lib/libtbbmalloc_proxy.dylib ${CMAKE_INSTALL_PREFIX}/lib/libtbbmalloc_proxy_debug.dylib) set(OSD_DYLIBS - ${CMAKE_INSTALL_PREFIX}/lib/libosdCPU.3.5.1.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libosdCPU.3.6.0.dylib ${CMAKE_INSTALL_PREFIX}/lib/libosdCPU.dylib - ${CMAKE_INSTALL_PREFIX}/lib/libosdGPU.3.5.1.dylib + ${CMAKE_INSTALL_PREFIX}/lib/libosdGPU.3.6.0.dylib ${CMAKE_INSTALL_PREFIX}/lib/libosdGPU.dylib) set(USD_DYLIBS ${CMAKE_INSTALL_PREFIX}/lib/libusd_ar.dylib @@ -351,9 +351,9 @@ else() ${CMAKE_INSTALL_PREFIX}/lib/libtbb.so.2) set(OSD_LIBS ${CMAKE_INSTALL_PREFIX}/lib/libosdCPU.so - ${CMAKE_INSTALL_PREFIX}/lib/libosdCPU.so.3.5.1 + ${CMAKE_INSTALL_PREFIX}/lib/libosdCPU.so.3.6.0 ${CMAKE_INSTALL_PREFIX}/lib/libosdGPU.so - ${CMAKE_INSTALL_PREFIX}/lib/libosdGPU.so.3.5.1) + ${CMAKE_INSTALL_PREFIX}/lib/libosdGPU.so.3.6.0) set(USD_LIBS ${CMAKE_INSTALL_PREFIX}/lib/libusd_arch.so ${CMAKE_INSTALL_PREFIX}/lib/libusd_ar.so diff --git a/etc/Windows/windows-build-gha.bat b/etc/Windows/windows-build-gha.bat index abae70bd6..c7aa3a0fd 100644 --- a/etc/Windows/windows-build-gha.bat +++ b/etc/Windows/windows-build-gha.bat @@ -20,6 +20,7 @@ cmake ..\etc\SuperBuild ^ -DTLRENDER_PNG=%TLRENDER_PNG% ^ -DTLRENDER_EXR=%TLRENDER_EXR% ^ -DTLRENDER_FFMPEG=%TLRENDER_FFMPEG% ^ + -DTLRENDER_USD=%TLRENDER_USD% ^ -DTLRENDER_NFD=%TLRENDER_NFD% ^ -DTLRENDER_QT5=%TLRENDER_QT5% ^ -DTLRENDER_PROGRAMS=%TLRENDER_PROGRAMS% ^ diff --git a/examples/panorama-qtwidget/PanoramaTimelineViewport.cpp b/examples/panorama-qtwidget/PanoramaTimelineViewport.cpp index b9ec8a071..2dbf3e190 100644 --- a/examples/panorama-qtwidget/PanoramaTimelineViewport.cpp +++ b/examples/panorama-qtwidget/PanoramaTimelineViewport.cpp @@ -53,32 +53,32 @@ namespace tl update(); } - void PanoramaTimelineViewport::setTimelinePlayer(qt::TimelinePlayer* timelinePlayer) + void PanoramaTimelineViewport::setPlayer(const QSharedPointer& player) { - _videoData = timeline::VideoData(); - if (_timelinePlayer) + if (_player) { disconnect( - _timelinePlayer, - SIGNAL(currentVideoChanged(const tl::timeline::VideoData&)), + _player.get(), + SIGNAL(currentVideoChanged(const std::vector&)), this, - SLOT(_currentVideoCallback(const tl::timeline::VideoData&))); + SLOT(_currentVideoCallback(const std::vector&))); } - _timelinePlayer = timelinePlayer; - if (_timelinePlayer) + _player = player; + _videoData.clear(); + if (_player) { - const auto& ioInfo = _timelinePlayer->ioInfo(); + const auto& ioInfo = _player->ioInfo(); _videoSize = !ioInfo.video.empty() ? ioInfo.video[0].size : image::Size(); - _videoData = _timelinePlayer->currentVideo(); + _videoData = _player->currentVideo(); connect( - _timelinePlayer, - SIGNAL(currentVideoChanged(const tl::timeline::VideoData&)), - SLOT(_currentVideoCallback(const tl::timeline::VideoData&))); + _player.get(), + SIGNAL(currentVideoChanged(const std::vector&)), + SLOT(_currentVideoCallback(const std::vector&))); } update(); } - void PanoramaTimelineViewport::_currentVideoCallback(const timeline::VideoData& value) + void PanoramaTimelineViewport::_currentVideoCallback(const std::vector& value) { _videoData = value; update(); diff --git a/examples/panorama-qtwidget/PanoramaTimelineViewport.h b/examples/panorama-qtwidget/PanoramaTimelineViewport.h index 55bf36a50..555311601 100644 --- a/examples/panorama-qtwidget/PanoramaTimelineViewport.h +++ b/examples/panorama-qtwidget/PanoramaTimelineViewport.h @@ -46,10 +46,10 @@ namespace tl void setImageOptions(const timeline::ImageOptions&); //! Set the timeline player. - void setTimelinePlayer(qt::TimelinePlayer*); + void setPlayer(const QSharedPointer&); private Q_SLOTS: - void _currentVideoCallback(const tl::timeline::VideoData&); + void _currentVideoCallback(const std::vector&); protected: void initializeGL() override; @@ -63,9 +63,9 @@ namespace tl timeline::OCIOOptions _ocioOptions; timeline::LUTOptions _lutOptions; timeline::ImageOptions _imageOptions; - qt::TimelinePlayer* _timelinePlayer = nullptr; + QSharedPointer _player; image::Size _videoSize; - timeline::VideoData _videoData; + std::vector _videoData; math::Vector2f _cameraRotation; float _cameraFOV = 45.F; geom::TriangleMesh3 _sphereMesh; diff --git a/examples/panorama-qtwidget/main.cpp b/examples/panorama-qtwidget/main.cpp index d84272c7c..8cbe47971 100644 --- a/examples/panorama-qtwidget/main.cpp +++ b/examples/panorama-qtwidget/main.cpp @@ -45,19 +45,19 @@ int main(int argc, char* argv[]) auto timeline = tl::timeline::Timeline::create(argv[1], context); // Create the timeline player. - QScopedPointer timelinePlayer( + QSharedPointer player( new tl::qt::TimelinePlayer( tl::timeline::Player::create(timeline, context), context)); // Create the panorama timeline viewport. auto timelineViewport = new tl::examples::panorama_qtwidget::PanoramaTimelineViewport(context); - timelineViewport->setTimelinePlayer(timelinePlayer.get()); + timelineViewport->setPlayer(player); timelineViewport->setAttribute(Qt::WA_DeleteOnClose); timelineViewport->show(); // Start playback. - timelinePlayer->setPlayback(tl::timeline::Playback::Forward); + player->setPlayback(tl::timeline::Playback::Forward); // Start the application. r = app.exec(); diff --git a/examples/player-qtwidget/player-qtwidget.cpp b/examples/player-qtwidget/player-qtwidget.cpp index 0177b81b1..ce9a52c04 100644 --- a/examples/player-qtwidget/player-qtwidget.cpp +++ b/examples/player-qtwidget/player-qtwidget.cpp @@ -44,19 +44,19 @@ int main(int argc, char* argv[]) auto timeline = tl::timeline::Timeline::create(argv[1], context); // Create the timeline player. - QSharedPointer timelinePlayer( + QSharedPointer player( new tl::qt::TimelinePlayer( tl::timeline::Player::create(timeline, context), context)); // Create the timeline viewport. auto timelineViewport = new tl::qtwidget::TimelineViewport(context); - timelineViewport->setTimelinePlayers({ timelinePlayer }); + timelineViewport->setPlayer(player); timelineViewport->setAttribute(Qt::WA_DeleteOnClose); timelineViewport->show(); // Start playback. - timelinePlayer->setPlayback(tl::timeline::Playback::Forward); + player->setPlayback(tl::timeline::Playback::Forward); // Start the application. r = app.exec(); diff --git a/examples/player/player.cpp b/examples/player/player.cpp index 2206931d2..73e9fb44f 100644 --- a/examples/player/player.cpp +++ b/examples/player/player.cpp @@ -46,7 +46,7 @@ namespace tl timeline::BackgroundOptions backgroundOptions; backgroundOptions.type = timeline::Background::Checkers; viewport->setBackgroundOptions(backgroundOptions); - viewport->setPlayers({ _player }); + viewport->setPlayer(_player); _window->show(); } diff --git a/examples/render/render.cpp b/examples/render/render.cpp index 22268fa3f..bcc9fa352 100644 --- a/examples/render/render.cpp +++ b/examples/render/render.cpp @@ -128,29 +128,21 @@ namespace tl { // Read the timelines. auto timeline = timeline::Timeline::create(_input, _context); - auto player = timeline::Player::create(timeline, _context); - _players.push_back(player); - auto ioInfo = player->getIOInfo(); - if (!ioInfo.video.empty()) - { - _videoSizes.push_back(ioInfo.video[0].size); - } - _videoData.push_back(timeline::VideoData()); + _player = timeline::Player::create(timeline, _context); + std::vector > compare; if (!_options.compareFileName.empty()) { - timeline = timeline::Timeline::create( - _options.compareFileName, - _context); - player = timeline::Player::create(timeline, _context); - player->setExternalTime(_players[0]); - _players.push_back(player); - ioInfo = player->getIOInfo(); - if (!ioInfo.video.empty()) - { - _videoSizes.push_back(ioInfo.video[0].size); - } - _videoData.push_back(timeline::VideoData()); + compare.push_back(timeline::Timeline::create(_options.compareFileName, _context)); } + _player->setCompare(compare); + _videoSizes = _player->getSizes(); + _videoDataObserver = observer::ListObserver::create( + _player->observeCurrentVideo(), + [this](const std::vector& value) + { + _videoData = value; + _renderDirty = true; + }); // Create the window. _window = gl::GLFWWindow::create( @@ -188,14 +180,14 @@ namespace tl _hud = _options.hud; if (time::isValid(_options.inOutRange)) { - _players[0]->setInOutRange(_options.inOutRange); - _players[0]->seek(_options.inOutRange.start_time()); + _player->setInOutRange(_options.inOutRange); + _player->seek(_options.inOutRange.start_time()); } if (time::isValid(_options.seek)) { - _players[0]->seek(_options.seek); + _player->seek(_options.seek); } - _players[0]->setPlayback(_options.playback); + _player->setPlayback(_options.playback); _startTime = std::chrono::steady_clock::now(); while (_running && !_window->shouldClose()) { @@ -228,21 +220,21 @@ namespace tl break; case GLFW_KEY_SPACE: _playbackCallback( - timeline::Playback::Stop == _players[0]->observePlayback()->get() ? + timeline::Playback::Stop == _player->observePlayback()->get() ? timeline::Playback::Forward : timeline::Playback::Stop); break; case GLFW_KEY_HOME: - _players[0]->start(); + _player->start(); break; case GLFW_KEY_END: - _players[0]->end(); + _player->end(); break; case GLFW_KEY_LEFT: - _players[0]->framePrev(); + _player->framePrev(); break; case GLFW_KEY_RIGHT: - _players[0]->frameNext(); + _player->frameNext(); break; } } @@ -266,21 +258,11 @@ namespace tl void App::_tick() { + const auto t0 = std::chrono::steady_clock::now(); + // Update. _context->tick(); - for (const auto& player : _players) - { - player->tick(); - } - for (size_t i = 0; i < _players.size(); ++i) - { - const auto& videoData = _players[i]->observeCurrentVideo()->get(); - if (!timeline::isTimeEqual(videoData, _videoData[i])) - { - _videoData[i] = videoData; - _renderDirty = true; - } - } + _player->tick(); // Render the video. if (_renderDirty) @@ -293,17 +275,17 @@ namespace tl _window->swap(); _renderDirty = false; } - else - { - time::sleep(std::chrono::milliseconds(5)); - } - const auto now = std::chrono::steady_clock::now(); - const std::chrono::duration diff = now - _startTime; + // Update the animation. + const auto t1 = std::chrono::steady_clock::now(); + const std::chrono::duration diff = t1 - _startTime; const float v = (sinf(diff.count()) + 1.F) / 2.F; _compareOptions.wipeCenter.x = v; _compareOptions.overlay = v; _rotation = diff.count() * 2.F; + + // Sleep. + time::sleep(std::chrono::milliseconds(5), t0, t1); } void App::_draw() @@ -500,8 +482,8 @@ namespace tl void App::_playbackCallback(timeline::Playback value) { - _players[0]->setPlayback(value); - _log(string::Format("Playback: {0}").arg(_players[0]->observePlayback()->get())); + _player->setPlayback(value); + _log(string::Format("Playback: {0}").arg(_player->observePlayback()->get())); } } } diff --git a/examples/render/render.h b/examples/render/render.h index 1843b8f4d..b7dfe42a7 100644 --- a/examples/render/render.h +++ b/examples/render/render.h @@ -80,7 +80,7 @@ namespace tl std::string _input; Options _options; - std::vector > _players; + std::shared_ptr _player; std::vector _videoSizes; std::shared_ptr _window; @@ -92,6 +92,7 @@ namespace tl std::shared_ptr _render; bool _renderDirty = true; std::vector _videoData; + std::shared_ptr > _videoDataObserver; std::chrono::steady_clock::time_point _startTime; bool _running = true; diff --git a/lib/tlBakeApp/App.cpp b/lib/tlBakeApp/App.cpp index 2e1c9ce94..93e8209a6 100644 --- a/lib/tlBakeApp/App.cpp +++ b/lib/tlBakeApp/App.cpp @@ -364,7 +364,7 @@ namespace tl _render->begin(_renderSize); _render->setOCIOOptions(_options.ocioOptions); _render->setLUTOptions(_options.lutOptions); - const auto videoData = _timeline->getVideo(_inputTime).get(); + const auto videoData = _timeline->getVideo(_inputTime).future.get(); _render->drawVideo( { videoData }, { math::Box2i(0, 0, _renderSize.w, _renderSize.h) }); diff --git a/lib/tlDevice/BMDOutputDevice.cpp b/lib/tlDevice/BMDOutputDevice.cpp index f1a5510e8..7cf061337 100644 --- a/lib/tlDevice/BMDOutputDevice.cpp +++ b/lib/tlDevice/BMDOutputDevice.cpp @@ -46,10 +46,10 @@ namespace tl std::shared_ptr > size; std::shared_ptr > frameRate; - std::vector > players; + std::shared_ptr player; std::shared_ptr > playbackObserver; std::shared_ptr > currentTimeObserver; - std::vector > > videoObservers; + std::shared_ptr > videoObserver; std::shared_ptr > audioObserver; std::shared_ptr window; @@ -378,24 +378,24 @@ namespace tl p.thread.cv.notify_one(); } - void OutputDevice::setPlayers(const std::vector >& value) + void OutputDevice::setPlayer(const std::shared_ptr& value) { TLRENDER_P(); - if (value == p.players) + if (value == p.player) return; p.playbackObserver.reset(); p.currentTimeObserver.reset(); - p.videoObservers.clear(); + p.videoObserver.reset(); p.audioObserver.reset(); - p.players = value; + p.player = value; - if (!p.players.empty() && p.players.front()) + if (p.player) { auto weak = std::weak_ptr(shared_from_this()); p.playbackObserver = observer::ValueObserver::create( - p.players.front()->observePlayback(), + p.player->observePlayback(), [weak](timeline::Playback value) { if (auto device = weak.lock()) @@ -409,7 +409,7 @@ namespace tl }, observer::CallbackAction::Suppress); p.currentTimeObserver = observer::ValueObserver::create( - p.players.front()->observeCurrentTime(), + p.player->observeCurrentTime(), [weak](const otime::RationalTime& value) { if (auto device = weak.lock()) @@ -422,31 +422,22 @@ namespace tl } }, observer::CallbackAction::Suppress); - for (size_t i = 0; i < p.players.size(); ++i) - { - if (p.players[i]) + p.videoObserver = observer::ListObserver::create( + p.player->observeCurrentVideo(), + [weak](const std::vector& value) { - p.videoObservers.push_back(observer::ValueObserver::create( - p.players[i]->observeCurrentVideo(), - [weak, i](const timeline::VideoData& value) + if (auto device = weak.lock()) + { { - if (auto device = weak.lock()) - { - { - std::unique_lock lock(device->_p->mutex.mutex); - if (i < device->_p->mutex.videoData.size()) - { - device->_p->mutex.videoData[i] = value; - } - } - device->_p->thread.cv.notify_one(); - } - }, - observer::CallbackAction::Suppress)); - } - } + std::unique_lock lock(device->_p->mutex.mutex); + device->_p->mutex.videoData = value; + } + device->_p->thread.cv.notify_one(); + } + }, + observer::CallbackAction::Suppress); p.audioObserver = observer::ListObserver::create( - p.players.front()->observeCurrentAudio(), + p.player->observeCurrentAudio(), [weak](const std::vector& value) { if (auto device = weak.lock()) @@ -463,11 +454,11 @@ namespace tl { std::unique_lock lock(p.mutex.mutex); - if (!p.players.empty() && p.players.front()) + if (p.player) { - p.mutex.timeRange = p.players.front()->getTimeRange(); - p.mutex.playback = p.players.front()->getPlayback(); - p.mutex.currentTime = p.players.front()->getCurrentTime(); + p.mutex.timeRange = p.player->getTimeRange(); + p.mutex.playback = p.player->getPlayback(); + p.mutex.currentTime = p.player->getCurrentTime(); } else { @@ -477,22 +468,12 @@ namespace tl } p.mutex.sizes.clear(); p.mutex.videoData.clear(); - for (const auto& player : p.players) - { - if (player) - { - const auto& ioInfo = player->getIOInfo(); - if (!ioInfo.video.empty()) - { - p.mutex.sizes.push_back(ioInfo.video[0].size); - } - p.mutex.videoData.push_back(player->getCurrentVideo()); - } - } p.mutex.audioData.clear(); - if (!p.players.empty() && p.players.front()) + if (p.player) { - p.mutex.audioData = p.players.front()->getCurrentAudio(); + p.mutex.sizes = p.player->getSizes(); + p.mutex.videoData = p.player->getCurrentVideo(); + p.mutex.audioData = p.player->getCurrentAudio(); } } } diff --git a/lib/tlDevice/BMDOutputDevice.h b/lib/tlDevice/BMDOutputDevice.h index af1715fa1..4ec53a1e4 100644 --- a/lib/tlDevice/BMDOutputDevice.h +++ b/lib/tlDevice/BMDOutputDevice.h @@ -104,8 +104,8 @@ namespace tl //! Set the audio sync offset. void setAudioOffset(double); - //! Set the timeline players. - void setPlayers(const std::vector >&); + //! Set the timeline player. + void setPlayer(const std::shared_ptr&); //! Tick the output device. void tick(); diff --git a/lib/tlPlay/FilesModel.cpp b/lib/tlPlay/FilesModel.cpp index 2e49d07d2..3e6c0e335 100644 --- a/lib/tlPlay/FilesModel.cpp +++ b/lib/tlPlay/FilesModel.cpp @@ -21,6 +21,7 @@ namespace tl std::shared_ptr > > active; std::shared_ptr > layers; std::shared_ptr > compareOptions; + std::shared_ptr > compareTime; }; void FilesModel::_init(const std::shared_ptr& context) @@ -37,6 +38,7 @@ namespace tl p.active = observer::List >::create(); p.layers = observer::List::create(); p.compareOptions = observer::Value::create(); + p.compareTime = observer::Value::create(); } FilesModel::FilesModel() : @@ -521,6 +523,22 @@ namespace tl } } + timeline::CompareTimeMode FilesModel::getCompareTime() const + { + return _p->compareTime->get(); + } + + std::shared_ptr > FilesModel::observeCompareTime() const + { + return _p->compareTime; + } + + void FilesModel::setCompareTime(timeline::CompareTimeMode value) + { + TLRENDER_P(); + p.compareTime->setIfChanged(value); + } + int FilesModel::_index(const std::shared_ptr& item) const { TLRENDER_P(); diff --git a/lib/tlPlay/FilesModel.h b/lib/tlPlay/FilesModel.h index ea18501bd..cedf16db6 100644 --- a/lib/tlPlay/FilesModel.h +++ b/lib/tlPlay/FilesModel.h @@ -149,6 +149,15 @@ namespace tl //! Observe the compare options. std::shared_ptr > observeCompareOptions() const; + //! Set the compare time mode. + void setCompareTime(timeline::CompareTimeMode); + + //! Get the compare time mode. + timeline::CompareTimeMode getCompareTime() const; + + //! Observe the compare time mode. + std::shared_ptr > observeCompareTime() const; + //! Set the compare options. void setCompareOptions(const timeline::CompareOptions&); diff --git a/lib/tlPlayApp/App.cpp b/lib/tlPlayApp/App.cpp index 27c98e879..75f697451 100644 --- a/lib/tlPlayApp/App.cpp +++ b/lib/tlPlayApp/App.cpp @@ -48,8 +48,8 @@ namespace tl std::shared_ptr filesModel; std::vector > files; std::vector > activeFiles; - std::vector > players; - std::shared_ptr > > activePlayers; + std::vector > timelines; + std::shared_ptr > > player; std::shared_ptr viewportModel; std::shared_ptr colorModel; std::shared_ptr audioModel; @@ -71,6 +71,7 @@ namespace tl std::shared_ptr > > filesObserver; std::shared_ptr > > activeObserver; std::shared_ptr > layersObserver; + std::shared_ptr > compareTimeObserver; std::shared_ptr > recentFilesMaxObserver; std::shared_ptr > recentFilesObserver; std::shared_ptr > mainWindowObserver; @@ -206,9 +207,9 @@ namespace tl return _p->filesModel; } - std::shared_ptr > > App::observeActivePlayers() const + std::shared_ptr > > App::observePlayer() const { - return _p->activePlayers; + return _p->player; } const std::shared_ptr& App::getViewportModel() const @@ -317,12 +318,9 @@ namespace tl void App::_tick() { TLRENDER_P(); - for (const auto& player : p.players) + if (auto player = p.player->get()) { - if (player) - { - player->tick(); - } + player->tick(); } #if defined(TLRENDER_BMD) if (p.bmdOutputDevice) @@ -465,6 +463,9 @@ namespace tl void App::_observersInit() { TLRENDER_P(); + + p.player = observer::Value >::create(); + p.settingsObserver = observer::ValueObserver::create( p.settings->observeValues(), [this](const std::string& name) @@ -472,32 +473,31 @@ namespace tl _settingsUpdate(name); }); - p.activePlayers = observer::List >::create(); - p.filesObserver = observer::ListObserver >::create( p.filesModel->observeFiles(), [this](const std::vector >& value) { - _filesCallback(value); + _filesUpdate(value); }); p.activeObserver = observer::ListObserver >::create( p.filesModel->observeActive(), [this](const std::vector >& value) { - _activeCallback(value); + _activeUpdate(value); }); p.layersObserver = observer::ListObserver::create( p.filesModel->observeLayers(), [this](const std::vector& value) { - for (size_t i = 0; i < value.size() && i < _p->players.size(); ++i) + _layersUpdate(value); + }); + p.compareTimeObserver = observer::ValueObserver::create( + p.filesModel->observeCompareTime(), + [this](timeline::CompareTimeMode value) + { + if (auto player = _p->player->get()) { - if (auto player = _p->players[i]) - { - auto ioOptions = _getIOOptions(); - ioOptions["Layer"] = string::Format("{0}").arg(value[i]); - player->setIOOptions(ioOptions); - } + player->setCompareTime(value); } }); @@ -560,11 +560,7 @@ namespace tl timeline::DisplayOptions displayOptions = p.colorModel->getDisplayOptions(); displayOptions.videoLevels = p.bmdOutputVideoLevels; std::vector displayOptionsList; - for (const auto& player : p.players) - { - displayOptionsList.push_back(displayOptions); - } - p.bmdOutputDevice->setDisplayOptions(displayOptionsList); + p.bmdOutputDevice->setDisplayOptions({ displayOptionsList }); p.bmdOutputDevice->setHDR(value.hdrMode, value.hdrData); p.settings->setValue("BMD/DeviceIndex", value.deviceIndex); @@ -613,12 +609,7 @@ namespace tl p.colorModel->observeImageOptions(), [this](const timeline::ImageOptions& value) { - std::vector imageOptions; - for (const auto& player : _p->players) - { - imageOptions.push_back(value); - } - _p->bmdOutputDevice->setImageOptions(imageOptions); + _p->bmdOutputDevice->setImageOptions({ value }); }); p.displayOptionsObserver = observer::ValueObserver::create( p.colorModel->observeDisplayOptions(), @@ -626,12 +617,7 @@ namespace tl { timeline::DisplayOptions tmp = value; tmp.videoLevels = _p->bmdOutputVideoLevels; - std::vector displayOptions; - for (const auto& player : _p->players) - { - displayOptions.push_back(tmp); - } - _p->bmdOutputDevice->setDisplayOptions(displayOptions); + _p->bmdOutputDevice->setDisplayOptions({ tmp }); }); p.compareOptionsObserver = observer::ValueObserver::create( @@ -666,26 +652,23 @@ namespace tl file::Path(p.options.fileName), file::Path(p.options.audioFileName)); - if (!p.players.empty()) + if (auto player = p.player->get()) { - if (auto player = p.players[0]) + if (p.options.speed > 0.0) { - if (p.options.speed > 0.0) - { - player->setSpeed(p.options.speed); - } - if (time::isValid(p.options.inOutRange)) - { - player->setInOutRange(p.options.inOutRange); - player->seek(p.options.inOutRange.start_time()); - } - if (time::isValid(p.options.seek)) - { - player->seek(p.options.seek); - } - player->setLoop(p.options.loop); - player->setPlayback(p.options.playback); + player->setSpeed(p.options.speed); + } + if (time::isValid(p.options.inOutRange)) + { + player->setInOutRange(p.options.inOutRange); + player->seek(p.options.inOutRange.start_time()); + } + if (time::isValid(p.options.seek)) + { + player->seek(p.options.seek); } + player->setLoop(p.options.loop); + player->setPlayback(p.options.playback); } } } @@ -793,77 +776,96 @@ namespace tl return out; } - std::vector > App::_getActivePlayers() const + void App::_settingsUpdate(const std::string& name) { TLRENDER_P(); - std::vector > out; - for (size_t i = 0; i < p.activeFiles.size(); ++i) + const auto split = string::split(name, '/'); + if (!split.empty() || name.empty()) { - const auto j = std::find( - p.files.begin(), - p.files.end(), - p.activeFiles[i]); - if (j != p.files.end()) + auto ioSystem = _context->getSystem(); + const auto& names = ioSystem->getNames(); + bool match = false; + if (!split.empty()) { - const auto k = j - p.files.begin(); - out.push_back(p.players[k]); + match = std::find(names.begin(), names.end(), split[0]) != names.end(); + } + if (match || name.empty()) + { + const auto ioOptions = _getIOOptions(); + if (auto player = p.player->get()) + { + player->setIOOptions(ioOptions); + } } } - return out; - } - - otime::RationalTime App::_getCacheReadAhead() const - { - TLRENDER_P(); - const size_t activeCount = p.activeFiles.size(); - const double readAhead = p.settings->getValue("Cache/ReadAhead"); - return otime::RationalTime( - activeCount > 0 ? (readAhead / static_cast(activeCount)) : 0.0, - 1.0); - } - - otime::RationalTime App::_getCacheReadBehind() const - { - TLRENDER_P(); - const size_t activeCount = p.activeFiles.size(); - const double readBehind = p.settings->getValue("Cache/ReadBehind"); - return otime::RationalTime( - activeCount > 0 ? (readBehind / static_cast(activeCount)) : 0.0, - 1.0); + if ("Cache/Size" == name || + "Cache/ReadAhead" == name || + "Cache/ReadBehind" == name || + name.empty()) + { + _cacheUpdate(); + } + if ("FileBrowser/Path" == name || name.empty()) + { + auto fileBrowserSystem = _context->getSystem(); + fileBrowserSystem->setPath( + p.settings->getValue("FileBrowser/Path")); + } + if ("FileBrowser/Options" == name || name.empty()) + { + auto fileBrowserSystem = _context->getSystem(); + auto options = p.settings->getValue("FileBrowser/Options"); + fileBrowserSystem->setOptions(options); + } + if ("FileBrowser/NativeFileDialog" == name || name.empty()) + { + auto fileBrowserSystem = _context->getSystem(); + fileBrowserSystem->setNativeFileDialog( + p.settings->getValue("FileBrowser/NativeFileDialog")); + } + if ("Files/RecentMax" == name || name.empty()) + { + auto fileBrowserSystem = _context->getSystem(); + auto recentFilesModel = fileBrowserSystem->getRecentFilesModel(); + recentFilesModel->setRecentMax( + p.settings->getValue("Files/RecentMax")); + } + if ("Files/Recent" == name || name.empty()) + { + std::vector recentPaths; + for (const auto& recentFile : + p.settings->getValue >("Files/Recent")) + { + recentPaths.push_back(file::Path(recentFile)); + } + auto fileBrowserSystem = _context->getSystem(); + auto recentFilesModel = fileBrowserSystem->getRecentFilesModel(); + recentFilesModel->setRecent(recentPaths); + } + if ("Style/Palette" == name || name.empty()) + { + getStyle()->setColorRoles(getStylePalette( + p.settings->getValue("Style/Palette"))); + } } - void App::_filesCallback(const std::vector >& items) + void App::_filesUpdate(const std::vector >& files) { TLRENDER_P(); - // Create the new list of players. - std::vector > players(items.size()); - for (size_t i = 0; i < items.size(); ++i) + std::vector > timelines(files.size()); + for (size_t i = 0; i < files.size(); ++i) { - const auto j = std::find(p.files.begin(), p.files.end(), items[i]); + const auto j = std::find(p.files.begin(), p.files.end(), files[i]); if (j != p.files.end()) { - const size_t k = j - p.files.begin(); - players[i] = p.players[k]; + timelines[i] = p.timelines[j - p.files.begin()]; } } - // Find players to destroy. - std::vector > destroy; - for (size_t i = 0; i < p.files.size(); ++i) + for (size_t i = 0; i < files.size(); ++i) { - const auto j = std::find(items.begin(), items.end(), p.files[i]); - if (j == items.end()) - { - destroy.push_back(p.players[i]); - } - } - - // Create new timeline players. - auto audioSystem = _context->getSystem(); - for (size_t i = 0; i < players.size(); ++i) - { - if (!players[i]) + if (!timelines[i]) { try { @@ -881,23 +883,13 @@ namespace tl options.ioOptions = _getIOOptions(); options.pathOptions.maxNumberDigits = p.settings->getValue("FileSequence/MaxDigits"); - auto otioTimeline = items[i]->audioPath.isEmpty() ? - timeline::create(items[i]->path, _context, options) : - timeline::create(items[i]->path, items[i]->audioPath, _context, options); - auto timeline = timeline::Timeline::create(otioTimeline, _context, options); - - timeline::PlayerOptions playerOptions; - playerOptions.cache.readAhead = time::invalidTime; - playerOptions.cache.readBehind = time::invalidTime; - playerOptions.timerMode = - p.settings->getValue("Performance/TimerMode"); - playerOptions.audioBufferFrameCount = - p.settings->getValue("Performance/AudioBufferFrameCount"); - players[i] = timeline::Player::create(timeline, _context, playerOptions); - - for (const auto& video : players[i]->getIOInfo().video) + auto otioTimeline = files[i]->audioPath.isEmpty() ? + timeline::create(files[i]->path, _context, options) : + timeline::create(files[i]->path, files[i]->audioPath, _context, options); + timelines[i] = timeline::Timeline::create(otioTimeline, _context, options); + for (const auto& video : timelines[i]->getIOInfo().video) { - items[i]->videoLayers.push_back(video.name); + files[i]->videoLayers.push_back(video.name); } } catch (const std::exception& e) @@ -907,125 +899,96 @@ namespace tl } } - p.files = items; - p.players = players; + p.files = files; + p.timelines = timelines; } - void App::_activeCallback(const std::vector >& items) + void App::_activeUpdate(const std::vector >& activeFiles) { TLRENDER_P(); - - auto activePlayers = _getActivePlayers(); - if (!activePlayers.empty() && activePlayers[0]) + std::shared_ptr player; + if (!activeFiles.empty()) { - activePlayers[0]->setPlayback(timeline::Playback::Stop); - } - - p.activeFiles = items; - - activePlayers = _getActivePlayers(); - p.activePlayers->setIfChanged(activePlayers); - std::shared_ptr first; - if (!activePlayers.empty()) - { - first = activePlayers[0]; - if (first) + if (!p.activeFiles.empty() && activeFiles[0] == p.activeFiles[0]) { - first->setExternalTime(nullptr); + player = p.player->get(); + } + else + { + auto i = std::find(p.files.begin(), p.files.end(), activeFiles[0]); + if (i != p.files.end()) + { + try + { + timeline::PlayerOptions playerOptions; + playerOptions.cache.readAhead = time::invalidTime; + playerOptions.cache.readBehind = time::invalidTime; + playerOptions.timerMode = + p.settings->getValue("Performance/TimerMode"); + playerOptions.audioBufferFrameCount = + p.settings->getValue("Performance/AudioBufferFrameCount"); + auto timeline = p.timelines[i - p.files.begin()]; + player = timeline::Player::create(timeline, _context, playerOptions); + } + catch (const std::exception& e) + { + _log(e.what(), log::Type::Error); + } + } } } - for (size_t i = 1; i < activePlayers.size(); ++i) + if (player) { - if (auto player = activePlayers[i]) + std::vector > compare; + for (size_t i = 1; i < activeFiles.size(); ++i) { - activePlayers[i]->setExternalTime( - first.get() != player.get() ? - first : - nullptr); + auto j = std::find(p.files.begin(), p.files.end(), activeFiles[i]); + if (j != p.files.end()) + { + auto timeline = p.timelines[j - p.files.begin()]; + compare.push_back(timeline); + } } + player->setCompare(compare); + player->setCompareTime(p.filesModel->getCompareTime()); } + p.activeFiles = activeFiles; + p.player->setIfChanged(player); #if defined(TLRENDER_BMD) - p.bmdOutputDevice->setPlayers(activePlayers); + p.bmdOutputDevice->setPlayer(player); #endif // TLRENDER_BMD + _layersUpdate(p.filesModel->observeLayers()->get()); _cacheUpdate(); _audioUpdate(); } - void App::_settingsUpdate(const std::string& name) + void App::_layersUpdate(const std::vector& value) { TLRENDER_P(); - const auto split = string::split(name, '/'); - if (!split.empty() || name.empty()) + if (auto player = p.player->get()) { - auto ioSystem = _context->getSystem(); - const auto& names = ioSystem->getNames(); - bool match = false; - if (!split.empty()) - { - match = std::find(names.begin(), names.end(), split[0]) != names.end(); - } - if (match || name.empty()) + int videoLayer = 0; + std::vector compareVideoLayers; + if (!value.empty() && value.size() == p.files.size() && !p.activeFiles.empty()) { - const auto ioOptions = _getIOOptions(); - for (const auto& player : p.players) + auto i = std::find(p.files.begin(), p.files.end(), p.activeFiles.front()); + if (i != p.files.end()) { - if (player) + videoLayer = value[i - p.files.begin()]; + } + for (size_t j = 1; j < p.activeFiles.size(); ++j) + { + i = std::find(p.files.begin(), p.files.end(), p.activeFiles[j]); + if (i != p.files.end()) { - player->setIOOptions(ioOptions); + compareVideoLayers.push_back(value[i - p.files.begin()]); } } } - } - if ("Cache/Size" == name || - "Cache/ReadAhead" == name || - "Cache/ReadBehind" == name || - name.empty()) - { - _cacheUpdate(); - } - if ("FileBrowser/Path" == name || name.empty()) - { - auto fileBrowserSystem = _context->getSystem(); - fileBrowserSystem->setPath( - p.settings->getValue("FileBrowser/Path")); - } - if ("FileBrowser/Options" == name || name.empty()) - { - auto fileBrowserSystem = _context->getSystem(); - auto options = p.settings->getValue("FileBrowser/Options"); - fileBrowserSystem->setOptions(options); - } - if ("FileBrowser/NativeFileDialog" == name || name.empty()) - { - auto fileBrowserSystem = _context->getSystem(); - fileBrowserSystem->setNativeFileDialog( - p.settings->getValue("FileBrowser/NativeFileDialog")); - } - if ("Files/RecentMax" == name || name.empty()) - { - auto fileBrowserSystem = _context->getSystem(); - auto recentFilesModel = fileBrowserSystem->getRecentFilesModel(); - recentFilesModel->setRecentMax( - p.settings->getValue("Files/RecentMax")); - } - if ("Files/Recent" == name || name.empty()) - { - std::vector recentPaths; - for (const auto& recentFile : - p.settings->getValue >("Files/Recent")) - { - recentPaths.push_back(file::Path(recentFile)); - } - auto fileBrowserSystem = _context->getSystem(); - auto recentFilesModel = fileBrowserSystem->getRecentFilesModel(); - recentFilesModel->setRecent(recentPaths); - } - if ("Style/Palette" == name || name.empty()) - { - getStyle()->setColorRoles(getStylePalette( - p.settings->getValue("Style/Palette"))); + player->setVideoLayer(videoLayer); + player->setCompareVideoLayers(compareVideoLayers); } } @@ -1033,40 +996,20 @@ namespace tl { TLRENDER_P(); - // Update the I/O cache. auto ioSystem = _context->getSystem(); ioSystem->getCache()->setMax( p.settings->getValue("Cache/Size") * memory::gigabyte); - // Update inactive players. timeline::PlayerCacheOptions cacheOptions; - cacheOptions.readAhead = time::invalidTime; - cacheOptions.readBehind = time::invalidTime; - const auto activePlayers = _getActivePlayers(); - for (const auto& player : p.players) - { - const auto j = std::find( - activePlayers.begin(), - activePlayers.end(), - player); - if (j == activePlayers.end()) - { - if (player) - { - player->setCacheOptions(cacheOptions); - } - } - } - - // Update active players. - cacheOptions.readAhead = _getCacheReadAhead(); - cacheOptions.readBehind = _getCacheReadBehind(); - for (const auto& player : activePlayers) + cacheOptions.readAhead = otime::RationalTime( + p.settings->getValue("Cache/ReadAhead"), + 1.0); + cacheOptions.readBehind = otime::RationalTime( + p.settings->getValue("Cache/ReadBehind"), + 1.0); + if (auto player = p.player->get()) { - if (player) - { - player->setCacheOptions(cacheOptions); - } + player->setCacheOptions(cacheOptions); } } @@ -1101,14 +1044,11 @@ namespace tl const float volume = p.audioModel->getVolume(); const bool mute = p.audioModel->isMuted(); const double audioOffset = p.audioModel->getSyncOffset(); - for (const auto& player : p.players) + if (auto player = p.player->get()) { - if (player) - { - player->setVolume(volume); - player->setMute(mute || p.bmdDeviceActive); - player->setAudioOffset(audioOffset); - } + player->setVolume(volume); + player->setMute(mute || p.bmdDeviceActive); + player->setAudioOffset(audioOffset); } #if defined(TLRENDER_BMD) p.bmdOutputDevice->setVolume(volume); diff --git a/lib/tlPlayApp/App.h b/lib/tlPlayApp/App.h index 1e4a3e913..15670e895 100644 --- a/lib/tlPlayApp/App.h +++ b/lib/tlPlayApp/App.h @@ -77,8 +77,8 @@ namespace tl //! Get the files model. const std::shared_ptr& getFilesModel() const; - //! Observe the active timeline players. - std::shared_ptr > > observeActivePlayers() const; + //! Observe the timeline player. + std::shared_ptr > > observePlayer() const; //! Get the viewport model. const std::shared_ptr& getViewportModel() const; @@ -122,14 +122,11 @@ namespace tl void _windowsInit(); io::Options _getIOOptions() const; - std::vector > _getActivePlayers() const; - otime::RationalTime _getCacheReadAhead() const; - otime::RationalTime _getCacheReadBehind() const; - - void _filesCallback(const std::vector >&); - void _activeCallback(const std::vector >&); void _settingsUpdate(const std::string&); + void _filesUpdate(const std::vector >&); + void _activeUpdate(const std::vector >&); + void _layersUpdate(const std::vector&); void _cacheUpdate(); void _viewUpdate(const math::Vector2i& pos, double zoom, bool frame); void _audioUpdate(); diff --git a/lib/tlPlayApp/CompareActions.cpp b/lib/tlPlayApp/CompareActions.cpp index 6c4c50eee..aab41cc40 100644 --- a/lib/tlPlayApp/CompareActions.cpp +++ b/lib/tlPlayApp/CompareActions.cpp @@ -52,7 +52,7 @@ namespace tl } }); - const std::array(timeline::CompareMode::Count)> icons = + const std::array(timeline::CompareMode::Count)> compareIcons = { "CompareA", "CompareB", @@ -63,7 +63,7 @@ namespace tl "CompareVertical", "CompareTile" }; - const std::array(timeline::CompareMode::Count)> shortcuts = + const std::array(timeline::CompareMode::Count)> compareShortcuts = { ui::Key::A, ui::Key::B, @@ -74,21 +74,21 @@ namespace tl ui::Key::Unknown, ui::Key::T }; - const std::array(timeline::CompareMode::Count)> toolTips = + const std::array(timeline::CompareMode::Count)> compareToolTips = { string::Format( "Show the A file\n" "\n" "Shortcut: {0}"). arg(ui::getLabel( - shortcuts[static_cast(timeline::CompareMode::A)], + compareShortcuts[static_cast(timeline::CompareMode::A)], static_cast(ui::KeyModifier::Control))), string::Format( "Show the B file\n" "\n" "Shortcut: {0}"). arg(ui::getLabel( - shortcuts[static_cast(timeline::CompareMode::B)], + compareShortcuts[static_cast(timeline::CompareMode::B)], static_cast(ui::KeyModifier::Control))), string::Format( "Wipe between the A and B files\n" @@ -97,7 +97,7 @@ namespace tl "\n" "Shortcut: {0}"). arg(ui::getLabel( - shortcuts[static_cast(timeline::CompareMode::Wipe)], + compareShortcuts[static_cast(timeline::CompareMode::Wipe)], static_cast(ui::KeyModifier::Control))), "Show the A file over the B file with transparency", "Show the difference between the A and B files", @@ -108,18 +108,18 @@ namespace tl "\n" "Shortcut: {0}"). arg(ui::getLabel( - shortcuts[static_cast(timeline::CompareMode::Tile)], + compareShortcuts[static_cast(timeline::CompareMode::Tile)], static_cast(ui::KeyModifier::Control))), }; - const auto enums = timeline::getCompareModeEnums(); - const auto labels = timeline::getCompareModeLabels(); - for (size_t i = 0; i < enums.size(); ++i) + const auto compareEnums = timeline::getCompareModeEnums(); + const auto comapreLabels = timeline::getCompareModeLabels(); + for (size_t i = 0; i < compareEnums.size(); ++i) { - const auto mode = enums[i]; - p.actions[labels[i]] = std::make_shared( + const auto mode = compareEnums[i]; + p.actions[comapreLabels[i]] = std::make_shared( timeline::getLabel(mode), - icons[i], - shortcuts[i], + compareIcons[i], + compareShortcuts[i], static_cast(ui::KeyModifier::Control), [appWeak, mode] { @@ -130,7 +130,29 @@ namespace tl app->getFilesModel()->setCompareOptions(options); } }); - p.actions[labels[i]]->toolTip = toolTips[i]; + p.actions[comapreLabels[i]]->toolTip = compareToolTips[i]; + } + + const auto compareTimeEnums = timeline::getCompareTimeModeEnums(); + const auto comapreTimeLabels = timeline::getCompareTimeModeLabels(); + const std::array(timeline::CompareMode::Count)> compareTimeToolTips = + { + "Compare relative times", + "Compare absolute times" + }; + for (size_t i = 0; i < compareTimeEnums.size(); ++i) + { + const auto mode = compareTimeEnums[i]; + p.actions[comapreTimeLabels[i]] = std::make_shared( + comapreTimeLabels[i], + [appWeak, mode] + { + if (auto app = appWeak.lock()) + { + app->getFilesModel()->setCompareTime(mode); + } + }); + p.actions[comapreTimeLabels[i]]->toolTip = compareTimeToolTips[i]; } } diff --git a/lib/tlPlayApp/CompareMenu.cpp b/lib/tlPlayApp/CompareMenu.cpp index a86207de6..a56519673 100644 --- a/lib/tlPlayApp/CompareMenu.cpp +++ b/lib/tlPlayApp/CompareMenu.cpp @@ -17,10 +17,12 @@ namespace tl std::map > actions; std::shared_ptr bMenu; std::vector > bActions; + std::shared_ptr timeMenu; std::shared_ptr > > filesObserver; std::shared_ptr > bIndexesObserver; std::shared_ptr > compareOptionsObserver; + std::shared_ptr > compareTimeObserver; }; void CompareMenu::_init( @@ -39,11 +41,18 @@ namespace tl addItem(p.actions["Next"]); addItem(p.actions["Prev"]); addDivider(); - const auto labels = timeline::getCompareModeLabels(); - for (const auto& label : labels) + const auto compareLabels = timeline::getCompareModeLabels(); + for (const auto& label : compareLabels) { addItem(p.actions[label]); } + addDivider(); + p.timeMenu = addSubMenu("Time"); + const auto timeLabels = timeline::getCompareTimeModeLabels(); + for (const auto& label : timeLabels) + { + p.timeMenu->addItem(p.actions[label]); + } p.filesObserver = observer::ListObserver >::create( app->getFilesModel()->observeFiles(), @@ -65,6 +74,13 @@ namespace tl { _compareUpdate(value); }); + + p.compareTimeObserver = observer::ValueObserver::create( + app->getFilesModel()->observeCompareTime(), + [this](timeline::CompareTimeMode value) + { + _compareTimeUpdate(value); + }); } CompareMenu::CompareMenu() : @@ -147,5 +163,16 @@ namespace tl setItemChecked(p.actions[labels[i]], enums[i] == value.mode); } } + + void CompareMenu::_compareTimeUpdate(timeline::CompareTimeMode value) + { + TLRENDER_P(); + const auto enums = timeline::getCompareTimeModeEnums(); + const auto labels = timeline::getCompareTimeModeLabels(); + for (size_t i = 0; i < enums.size(); ++i) + { + p.timeMenu->setItemChecked(p.actions[labels[i]], enums[i] == value); + } + } } } diff --git a/lib/tlPlayApp/CompareMenu.h b/lib/tlPlayApp/CompareMenu.h index d2f138f45..f58c4e9e9 100644 --- a/lib/tlPlayApp/CompareMenu.h +++ b/lib/tlPlayApp/CompareMenu.h @@ -44,6 +44,7 @@ namespace tl const std::vector >&); void _bUpdate(const std::vector&); void _compareUpdate(const timeline::CompareOptions&); + void _compareTimeUpdate(timeline::CompareTimeMode); TLRENDER_PRIVATE(); }; diff --git a/lib/tlPlayApp/FrameActions.cpp b/lib/tlPlayApp/FrameActions.cpp index 0e7d5566f..b06149191 100644 --- a/lib/tlPlayApp/FrameActions.cpp +++ b/lib/tlPlayApp/FrameActions.cpp @@ -35,10 +35,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->start(); + player->start(); } } }); @@ -59,10 +58,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->end(); + player->end(); } } }); @@ -83,10 +81,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->framePrev(); + player->framePrev(); } } }); @@ -106,10 +103,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::FramePrevX10); + player->timeAction(timeline::TimeAction::FramePrevX10); } } }); @@ -122,10 +118,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::FramePrevX100); + player->timeAction(timeline::TimeAction::FramePrevX100); } } }); @@ -139,10 +134,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->frameNext(); + player->frameNext(); } } }); @@ -162,10 +156,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::FrameNextX10); + player->timeAction(timeline::TimeAction::FrameNextX10); } } }); @@ -178,10 +171,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::FrameNextX100); + player->timeAction(timeline::TimeAction::FrameNextX100); } } }); diff --git a/lib/tlPlayApp/InfoTool.cpp b/lib/tlPlayApp/InfoTool.cpp index b582ef2f0..fd1fc2bbd 100644 --- a/lib/tlPlayApp/InfoTool.cpp +++ b/lib/tlPlayApp/InfoTool.cpp @@ -24,7 +24,7 @@ namespace tl std::shared_ptr searchBox; std::shared_ptr layout; - std::shared_ptr > > playerObserver; + std::shared_ptr > > playerObserver; }; void InfoTool::_init( @@ -59,13 +59,11 @@ namespace tl p.searchBox->setParent(hLayout); _setWidget(layout); - p.playerObserver = observer::ListObserver >::create( - app->observeActivePlayers(), - [this](const std::vector >& value) + p.playerObserver = observer::ValueObserver >::create( + app->observePlayer(), + [this](const std::shared_ptr& value) { - _p->info = (!value.empty() && value[0]) ? - value[0]->getIOInfo() : - io::Info(); + _p->info = value ? value->getIOInfo() : io::Info(); _widgetUpdate(); }); diff --git a/lib/tlPlayApp/MainWindow.cpp b/lib/tlPlayApp/MainWindow.cpp index ac929776a..4f1f91880 100644 --- a/lib/tlPlayApp/MainWindow.cpp +++ b/lib/tlPlayApp/MainWindow.cpp @@ -99,7 +99,7 @@ namespace tl std::shared_ptr timeUnitsModel; std::shared_ptr speedModel; timelineui::ItemOptions itemOptions; - std::vector > players; + std::shared_ptr player; std::shared_ptr timelineViewport; std::shared_ptr timelineWidget; @@ -151,7 +151,7 @@ namespace tl std::shared_ptr statusLayout; std::shared_ptr layout; - std::shared_ptr > > playersObserver; + std::shared_ptr > > playerObserver; std::shared_ptr > speedObserver; std::shared_ptr > speedObserver2; std::shared_ptr > playbackObserver; @@ -466,11 +466,11 @@ namespace tl p.currentTimeEdit->setCallback( [this](const otime::RationalTime& value) { - if (!_p->players.empty() && _p->players[0]) + if (_p->player) { - _p->players[0]->setPlayback(timeline::Playback::Stop); - _p->players[0]->seek(value); - _p->currentTimeEdit->setValue(_p->players[0]->getCurrentTime()); + _p->player->setPlayback(timeline::Playback::Stop); + _p->player->seek(value); + _p->currentTimeEdit->setValue(_p->player->getCurrentTime()); } }); @@ -484,30 +484,30 @@ namespace tl p.playbackButtonGroup->setCheckedCallback( [this](int index, bool value) { - if (!_p->players.empty() && _p->players[0]) + if (_p->player) { - _p->players[0]->setPlayback(static_cast(index)); + _p->player->setPlayback(static_cast(index)); } }); p.frameButtonGroup->setClickedCallback( [this](int index) { - if (!_p->players.empty() && _p->players[0]) + if (_p->player) { switch (index) { case 0: - _p->players[0]->timeAction(timeline::TimeAction::Start); + _p->player->timeAction(timeline::TimeAction::Start); break; case 1: - _p->players[0]->timeAction(timeline::TimeAction::FramePrev); + _p->player->timeAction(timeline::TimeAction::FramePrev); break; case 2: - _p->players[0]->timeAction(timeline::TimeAction::FrameNext); + _p->player->timeAction(timeline::TimeAction::FrameNext); break; case 3: - _p->players[0]->timeAction(timeline::TimeAction::End); + _p->player->timeAction(timeline::TimeAction::End); break; } } @@ -533,20 +533,20 @@ namespace tl } }); - p.playersObserver = observer::ListObserver >::create( - app->observeActivePlayers(), - [this](const std::vector >& value) + p.playerObserver = observer::ValueObserver >::create( + app->observePlayer(), + [this](const std::shared_ptr& value) { - _playersUpdate(value); + _playerUpdate(value); }); p.speedObserver2 = observer::ValueObserver::create( p.speedModel->observeValue(), [this](double value) { - if (!_p->players.empty() && _p->players[0]) + if (_p->player) { - _p->players[0]->setSpeed(value); + _p->player->setSpeed(value); } }); @@ -568,24 +568,14 @@ namespace tl app->getColorModel()->observeImageOptions(), [this](const timeline::ImageOptions& value) { - std::vector imageOptions; - for (const auto& player : _p->players) - { - imageOptions.push_back(value); - } - _p->timelineViewport->setImageOptions(imageOptions); + _p->timelineViewport->setImageOptions({ value }); }); p.displayOptionsObserver = observer::ValueObserver::create( app->getColorModel()->observeDisplayOptions(), [this](const timeline::DisplayOptions& value) { - std::vector displayOptions; - for (const auto& player : _p->players) - { - displayOptions.push_back(value); - } - _p->timelineViewport->setDisplayOptions(displayOptions); + _p->timelineViewport->setDisplayOptions({ value }); }); p.compareOptionsObserver = observer::ValueObserver::create( @@ -724,7 +714,7 @@ namespace tl } } - void MainWindow::_playersUpdate(const std::vector >& value) + void MainWindow::_playerUpdate(const std::shared_ptr& value) { TLRENDER_P(); @@ -732,37 +722,34 @@ namespace tl p.playbackObserver.reset(); p.currentTimeObserver.reset(); - p.players = value; + p.player = value; - p.timelineViewport->setPlayers(p.players); - p.timelineWidget->setPlayer( - !p.players.empty() ? - p.players[0] : - nullptr); + p.timelineViewport->setPlayer(p.player); + p.timelineWidget->setPlayer(p.player); p.durationLabel->setValue( - (!p.players.empty() && p.players[0]) ? - p.players[0]->getTimeRange().duration() : + p.player ? + p.player->getTimeRange().duration() : time::invalidTime); _infoUpdate(); - if (!p.players.empty() && p.players[0]) + if (p.player) { p.speedObserver = observer::ValueObserver::create( - p.players[0]->observeSpeed(), + p.player->observeSpeed(), [this](double value) { _p->speedModel->setValue(value); }); p.playbackObserver = observer::ValueObserver::create( - p.players[0]->observePlayback(), + p.player->observePlayback(), [this](timeline::Playback value) { _p->playbackButtonGroup->setChecked(static_cast(value), true); }); p.currentTimeObserver = observer::ValueObserver::create( - p.players[0]->observeCurrentTime(), + p.player->observeCurrentTime(), [this](const otime::RationalTime& value) { _p->currentTimeEdit->setValue(value); @@ -786,8 +773,8 @@ namespace tl if (!p.speedPopup) { const double defaultSpeed = - !p.players.empty() && p.players[0] ? - p.players[0]->getDefaultSpeed() : + p.player ? + p.player->getDefaultSpeed() : 0.0; p.speedPopup = SpeedPopup::create(defaultSpeed, context); p.speedPopup->open(window, p.speedButton->getGeometry()); @@ -797,10 +784,9 @@ namespace tl { if (auto widget = weak.lock()) { - if (!widget->_p->players.empty() && - widget->_p->players[0]) + if (widget->_p->player) { - widget->_p->players[0]->setSpeed(value); + widget->_p->player->setSpeed(value); } widget->_p->speedPopup->close(); } @@ -920,10 +906,10 @@ namespace tl TLRENDER_P(); std::string text; std::string toolTip; - if (!p.players.empty() && p.players[0]) + if (p.player) { - const file::Path& path = p.players[0]->getPath(); - const io::Info& info = p.players[0]->getIOInfo(); + const file::Path& path = p.player->getPath(); + const io::Info& info = p.player->getIOInfo(); text = play::infoLabel(path, info); toolTip = play::infoToolTip(path, info); } diff --git a/lib/tlPlayApp/MainWindow.h b/lib/tlPlayApp/MainWindow.h index e0a6e9590..6512a5851 100644 --- a/lib/tlPlayApp/MainWindow.h +++ b/lib/tlPlayApp/MainWindow.h @@ -85,7 +85,7 @@ namespace tl void _drop(const std::vector&) override; private: - void _playersUpdate(const std::vector >&); + void _playerUpdate(const std::shared_ptr&); void _showSpeedPopup(); void _showAudioPopup(); void _windowOptionsUpdate(); diff --git a/lib/tlPlayApp/PlaybackActions.cpp b/lib/tlPlayApp/PlaybackActions.cpp index 0ac4a8e1a..2202491ab 100644 --- a/lib/tlPlayApp/PlaybackActions.cpp +++ b/lib/tlPlayApp/PlaybackActions.cpp @@ -36,10 +36,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setPlayback(timeline::Playback::Stop); + player->setPlayback(timeline::Playback::Stop); } } }); @@ -60,10 +59,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setPlayback(timeline::Playback::Forward); + player->setPlayback(timeline::Playback::Forward); } } }); @@ -84,10 +82,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setPlayback(timeline::Playback::Reverse); + player->setPlayback(timeline::Playback::Reverse); } } }); @@ -107,11 +104,10 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - const timeline::Playback playback = players[0]->observePlayback()->get(); - players[0]->setPlayback( + const timeline::Playback playback = player->observePlayback()->get(); + player->setPlayback( timeline::Playback::Stop == playback ? _p->playbackPrev : timeline::Playback::Stop); @@ -131,10 +127,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::JumpBack1s); + player->timeAction(timeline::TimeAction::JumpBack1s); } } }); @@ -147,10 +142,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::JumpBack10s); + player->timeAction(timeline::TimeAction::JumpBack10s); } } }); @@ -163,10 +157,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::JumpForward1s); + player->timeAction(timeline::TimeAction::JumpForward1s); } } }); @@ -179,10 +172,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->timeAction(timeline::TimeAction::JumpForward10s); + player->timeAction(timeline::TimeAction::JumpForward10s); } } }); @@ -193,10 +185,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setLoop(timeline::Loop::Loop); + player->setLoop(timeline::Loop::Loop); } } }); @@ -207,10 +198,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setLoop(timeline::Loop::Once); + player->setLoop(timeline::Loop::Once); } } }); @@ -221,10 +211,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setLoop(timeline::Loop::PingPong); + player->setLoop(timeline::Loop::PingPong); } } }); @@ -237,10 +226,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setInPoint(); + player->setInPoint(); } } }); @@ -253,10 +241,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->resetInPoint(); + player->resetInPoint(); } } }); @@ -269,10 +256,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->setOutPoint(); + player->setOutPoint(); } } }); @@ -285,10 +271,9 @@ namespace tl { if (auto app = appWeak.lock()) { - auto players = app->observeActivePlayers()->get(); - if (!players.empty() && players[0]) + if (auto player = app->observePlayer()->get()) { - players[0]->resetOutPoint(); + player->resetOutPoint(); } } }); diff --git a/lib/tlPlayApp/PlaybackMenu.cpp b/lib/tlPlayApp/PlaybackMenu.cpp index 240ba7960..a7a27c76a 100644 --- a/lib/tlPlayApp/PlaybackMenu.cpp +++ b/lib/tlPlayApp/PlaybackMenu.cpp @@ -20,7 +20,7 @@ namespace tl std::map > playbackItems; std::map > loopItems; - std::shared_ptr > > playerObserver; + std::shared_ptr > > playerObserver; std::shared_ptr > playbackObserver; std::shared_ptr > loopObserver; }; @@ -65,11 +65,11 @@ namespace tl _playbackUpdate(); _loopUpdate(); - p.playerObserver = observer::ListObserver >::create( - app->observeActivePlayers(), - [this](const std::vector >& value) + p.playerObserver = observer::ValueObserver >::create( + app->observePlayer(), + [this](const std::shared_ptr& value) { - _setPlayer(!value.empty() ? value[0] : nullptr); + _setPlayer(value); }); } diff --git a/lib/tlPlayApp/SecondaryWindow.cpp b/lib/tlPlayApp/SecondaryWindow.cpp index aaa2434b0..78b9be612 100644 --- a/lib/tlPlayApp/SecondaryWindow.cpp +++ b/lib/tlPlayApp/SecondaryWindow.cpp @@ -21,7 +21,7 @@ namespace tl { std::shared_ptr viewport; - std::shared_ptr > > playersObserver; + std::shared_ptr > > playerObserver; std::shared_ptr > ocioOptionsObserver; std::shared_ptr > lutOptionsObserver; std::shared_ptr > imageOptionsObserver; @@ -42,11 +42,11 @@ namespace tl p.viewport = timelineui::TimelineViewport::create(context); p.viewport->setParent(shared_from_this()); - p.playersObserver = observer::ListObserver >::create( - app->observeActivePlayers(), - [this](const std::vector >& value) + p.playerObserver = observer::ValueObserver >::create( + app->observePlayer(), + [this](const std::shared_ptr& value) { - _p->viewport->setPlayers(value); + _p->viewport->setPlayer(value); }); p.ocioOptionsObserver = observer::ValueObserver::create( diff --git a/lib/tlPlayApp/ViewToolBar.cpp b/lib/tlPlayApp/ViewToolBar.cpp index 4537baf7a..4f21e069a 100644 --- a/lib/tlPlayApp/ViewToolBar.cpp +++ b/lib/tlPlayApp/ViewToolBar.cpp @@ -19,13 +19,11 @@ namespace tl struct ViewToolBar::Private { std::weak_ptr app; - std::shared_ptr player; std::map > actions; std::map > buttons; std::shared_ptr layout; - std::shared_ptr > > playerObserver; std::shared_ptr > frameViewObserver; }; @@ -78,13 +76,6 @@ namespace tl } }); - p.playerObserver = observer::ListObserver >::create( - app->observeActivePlayers(), - [this](const std::vector >& value) - { - _p->player = !value.empty() ? value[0] : nullptr; - }); - p.frameViewObserver = observer::ValueObserver::create( mainWindow->getTimelineViewport()->observeFrameView(), [this](bool value) diff --git a/lib/tlPlayApp/WindowToolBar.cpp b/lib/tlPlayApp/WindowToolBar.cpp index af5be85e0..cd64a75eb 100644 --- a/lib/tlPlayApp/WindowToolBar.cpp +++ b/lib/tlPlayApp/WindowToolBar.cpp @@ -17,13 +17,11 @@ namespace tl struct WindowToolBar::Private { std::weak_ptr app; - std::shared_ptr player; std::map > actions; std::map > buttons; std::shared_ptr layout; - std::shared_ptr > > playerObserver; std::shared_ptr > fullScreenObserver; std::shared_ptr > secondaryObserver; }; @@ -78,13 +76,6 @@ namespace tl } }); - p.playerObserver = observer::ListObserver >::create( - app->observeActivePlayers(), - [this](const std::vector >& value) - { - _p->player = !value.empty() ? value[0] : nullptr; - }); - p.fullScreenObserver = observer::ValueObserver::create( mainWindow->observeFullScreen(), [this](bool value) diff --git a/lib/tlPlayQtApp/App.cpp b/lib/tlPlayQtApp/App.cpp index f065f7c1b..30d8c94da 100644 --- a/lib/tlPlayQtApp/App.cpp +++ b/lib/tlPlayQtApp/App.cpp @@ -74,7 +74,8 @@ namespace tl std::shared_ptr filesModel; std::vector > files; std::vector > activeFiles; - QVector > players; + std::vector > timelines; + QSharedPointer player; std::shared_ptr recentFilesModel; std::shared_ptr viewportModel; std::shared_ptr colorModel; @@ -88,7 +89,7 @@ namespace tl #if defined(TLRENDER_BMD) std::shared_ptr bmdDevicesModel; std::shared_ptr bmdOutputDevice; - image::VideoLevels bmdOutputVideoLevels; + image::VideoLevels bmdOutputVideoLevels = image::VideoLevels::First; #endif // TLRENDER_BMD std::unique_ptr timer; @@ -96,6 +97,7 @@ namespace tl std::shared_ptr > > filesObserver; std::shared_ptr > > activeObserver; std::shared_ptr > layersObserver; + std::shared_ptr > compareTimeObserver; std::shared_ptr > recentFilesMaxObserver; std::shared_ptr > recentFilesObserver; std::shared_ptr > volumeObserver; @@ -158,7 +160,7 @@ namespace tl p.timer.reset(new QTimer); p.timer->setTimerType(Qt::PreciseTimer); - connect(p.timer.get(), &QTimer::timeout, this, &App::_timerCallback); + connect(p.timer.get(), &QTimer::timeout, this, &App::_timerUpdate); p.timer->start(timeout); } @@ -185,28 +187,9 @@ namespace tl return _p->filesModel; } - const QVector >& App::players() const + const QSharedPointer& App::player() const { - return _p->players; - } - - QVector > App::activePlayers() const - { - TLRENDER_P(); - QVector > out; - for (size_t i = 0; i < p.activeFiles.size(); ++i) - { - const auto j = std::find( - p.files.begin(), - p.files.end(), - p.activeFiles[i]); - if (j != p.files.end()) - { - const auto k = j - p.files.begin(); - out.push_back(p.players[k]); - } - } - return out; + return _p->player; } const std::shared_ptr& App::recentFilesModel() const @@ -315,8 +298,12 @@ namespace tl connect( p.secondaryWindow.get(), - SIGNAL(destroyed(QObject*)), - SLOT(_secondaryWindowDestroyedCallback())); + &QObject::destroyed, + [this](QObject*) + { + _p->secondaryWindow.take(); + Q_EMIT secondaryWindowChanged(false); + }); p.secondaryWindow->show(); } @@ -327,169 +314,6 @@ namespace tl Q_EMIT secondaryWindowChanged(value); } - void App::_filesCallback(const std::vector >& items) - { - TLRENDER_P(); - - // Create the new list of players. - QVector > players(items.size(), nullptr); - for (size_t i = 0; i < items.size(); ++i) - { - const auto j = std::find(p.files.begin(), p.files.end(), items[i]); - if (j != p.files.end()) - { - const size_t k = j - p.files.begin(); - players[i] = p.players[k]; - } - } - - // Find players to destroy. - QVector > destroy; - for (size_t i = 0; i < p.files.size(); ++i) - { - const auto j = std::find(items.begin(), items.end(), p.files[i]); - if (j == items.end()) - { - destroy.push_back(p.players[i]); - } - } - - // Create new timeline players. - auto audioSystem = _context->getSystem(); - for (size_t i = 0; i < players.size(); ++i) - { - if (!players[i]) - { - try - { - timeline::Options options; - options.fileSequenceAudio = - p.settings->getValue("FileSequence/Audio"); - options.fileSequenceAudioFileName = - p.settings->getValue("FileSequence/AudioFileName"); - options.fileSequenceAudioDirectory = - p.settings->getValue("FileSequence/AudioDirectory"); - options.videoRequestCount = - p.settings->getValue("Performance/VideoRequestCount"); - options.audioRequestCount = - p.settings->getValue("Performance/AudioRequestCount"); - options.ioOptions = _ioOptions(); - options.pathOptions.maxNumberDigits = - p.settings->getValue("FileSequence/MaxDigits"); - auto otioTimeline = items[i]->audioPath.isEmpty() ? - timeline::create(items[i]->path, _context, options) : - timeline::create(items[i]->path, items[i]->audioPath, _context, options); - if (0) - { - timeline::toMemoryReferences( - otioTimeline, - items[i]->path.getDirectory(), - timeline::ToMemoryReference::Shared, - options.pathOptions); - } - auto timeline = timeline::Timeline::create(otioTimeline, _context, options); - const otime::TimeRange& timeRange = timeline->getTimeRange(); - - timeline::PlayerOptions playerOptions; - playerOptions.cache.readAhead = time::invalidTime; - playerOptions.cache.readBehind = time::invalidTime; - playerOptions.timerMode = - p.settings->getValue("Performance/TimerMode"); - playerOptions.audioBufferFrameCount = - p.settings->getValue("Performance/AudioBufferFrameCount"); - auto player = timeline::Player::create(timeline, _context, playerOptions); - players[i].reset(new qt::TimelinePlayer(player, _context, this)); - - for (const auto& video : player->getIOInfo().video) - { - items[i]->videoLayers.push_back(video.name); - } - } - catch (const std::exception& e) - { - _log(e.what(), log::Type::Error); - } - } - } - - p.files = items; - p.players = players; - } - - void App::_activeCallback(const std::vector >& items) - { - TLRENDER_P(); - - auto activePlayers = this->activePlayers(); - if (!activePlayers.empty() && activePlayers[0]) - { - activePlayers[0]->setPlayback(timeline::Playback::Stop); - } - - p.activeFiles = items; - activePlayers = this->activePlayers(); - QSharedPointer first; - if (!activePlayers.empty()) - { - first = activePlayers[0]; - if (first) - { - first->player()->setExternalTime(nullptr); - } - } - for (size_t i = 1; i < activePlayers.size(); ++i) - { - if (auto player = activePlayers[i]) - { - player->player()->setExternalTime( - (first && first != player) ? - first->player() : - nullptr); - } - } - -#if defined(TLRENDER_BMD) - std::vector > activePlayers2; - for (const auto& player : activePlayers) - { - activePlayers2.push_back(player->player()); - } - p.bmdOutputDevice->setPlayers(activePlayers2); -#endif // TLRENDER_BMD - - _cacheUpdate(); - _audioUpdate(); - - Q_EMIT activePlayersChanged(activePlayers); - } - - void App::_mainWindowDestroyedCallback() - { - TLRENDER_P(); - p.mainWindow.take(); - if (p.secondaryWindow) - { - p.secondaryWindow->close(); - } - } - - void App::_secondaryWindowDestroyedCallback() - { - TLRENDER_P(); - p.secondaryWindow.take(); - Q_EMIT secondaryWindowChanged(false); - } - - void App::_timerCallback() - { -#if defined(TLRENDER_BMD) - if (_p && _p->bmdOutputDevice) - { - _p->bmdOutputDevice->tick(); - } -#endif // TLRENDER_BMD - } - void App::_fileLogInit(const std::string& logFileName) { TLRENDER_P(); @@ -659,26 +483,27 @@ namespace tl p.filesModel->observeFiles(), [this](const std::vector >& value) { - _filesCallback(value); + _filesUpdate(value); }); p.activeObserver = observer::ListObserver >::create( p.filesModel->observeActive(), [this](const std::vector >& value) { - _activeCallback(value); + _activeUpdate(value); }); p.layersObserver = observer::ListObserver::create( p.filesModel->observeLayers(), [this](const std::vector& value) { - for (size_t i = 0; i < value.size() && i < _p->players.size(); ++i) + _layersUpdate(value); + }); + p.compareTimeObserver = observer::ValueObserver::create( + p.filesModel->observeCompareTime(), + [this](timeline::CompareTimeMode value) + { + if (_p->player) { - if (_p->players[i]) - { - io::Options ioOptions; - ioOptions["Layer"] = string::Format("{0}").arg(value[i]); - _p->players[i]->setIOOptions(ioOptions); - } + _p->player->setCompareTime(value); } }); @@ -739,12 +564,7 @@ namespace tl p.bmdOutputVideoLevels = value.videoLevels; timeline::DisplayOptions displayOptions = p.colorModel->getDisplayOptions(); displayOptions.videoLevels = p.bmdOutputVideoLevels; - std::vector displayOptionsList; - for (const auto& player : p.players) - { - displayOptionsList.push_back(displayOptions); - } - p.bmdOutputDevice->setDisplayOptions(displayOptionsList); + p.bmdOutputDevice->setDisplayOptions({ displayOptions }); p.bmdOutputDevice->setHDR(value.hdrMode, value.hdrData); p.settings->setValue("BMD/DeviceIndex", value.deviceIndex); @@ -793,12 +613,7 @@ namespace tl p.colorModel->observeImageOptions(), [this](const timeline::ImageOptions& value) { - std::vector imageOptions; - for (const auto& player : _p->players) - { - imageOptions.push_back(value); - } - _p->bmdOutputDevice->setImageOptions(imageOptions); + _p->bmdOutputDevice->setImageOptions({ value }); }); p.displayOptionsObserver = observer::ValueObserver::create( p.colorModel->observeDisplayOptions(), @@ -806,12 +621,7 @@ namespace tl { timeline::DisplayOptions tmp = value; tmp.videoLevels = _p->bmdOutputVideoLevels; - std::vector displayOptions; - for (const auto& player : _p->players) - { - displayOptions.push_back(tmp); - } - _p->bmdOutputDevice->setDisplayOptions(displayOptions); + _p->bmdOutputDevice->setDisplayOptions({ tmp }); }); p.compareOptionsObserver = observer::ValueObserver::create( @@ -846,23 +656,23 @@ namespace tl QString::fromUtf8(p.options.fileName.c_str()), QString::fromUtf8(p.options.audioFileName.c_str())); - if (!p.players.empty() && p.players[0]) + if (p.player) { if (p.options.speed > 0.0) { - p.players[0]->setSpeed(p.options.speed); + p.player->setSpeed(p.options.speed); } if (time::isValid(p.options.inOutRange)) { - p.players[0]->setInOutRange(p.options.inOutRange); - p.players[0]->seek(p.options.inOutRange.start_time()); + p.player->setInOutRange(p.options.inOutRange); + p.player->seek(p.options.inOutRange.start_time()); } if (time::isValid(p.options.seek)) { - p.players[0]->seek(p.options.seek); + p.player->seek(p.options.seek); } - p.players[0]->setLoop(p.options.loop); - p.players[0]->setPlayback(p.options.playback); + p.player->setLoop(p.options.loop); + p.player->setPlayback(p.options.playback); } } } @@ -878,8 +688,15 @@ namespace tl connect( p.mainWindow.get(), - SIGNAL(destroyed(QObject*)), - SLOT(_mainWindowDestroyedCallback())); + &QObject::destroyed, + [this](QObject*) + { + _p->mainWindow.take(); + if (_p->secondaryWindow) + { + _p->secondaryWindow->close(); + } + }); connect( p.mainWindow->timelineViewport(), @@ -959,24 +776,14 @@ namespace tl return out; } - otime::RationalTime App::_cacheReadAhead() const + void App::_timerUpdate() { - TLRENDER_P(); - const size_t activeCount = p.filesModel->observeActive()->getSize(); - const double readAhead = p.settings->getValue("Cache/ReadAhead"); - return otime::RationalTime( - activeCount > 0 ? (readAhead / static_cast(activeCount)) : 0.0, - 1.0); - } - - otime::RationalTime App::_cacheReadBehind() const - { - TLRENDER_P(); - const size_t activeCount = p.filesModel->observeActive()->getSize(); - const double readBehind = p.settings->getValue("Cache/ReadBehind"); - return otime::RationalTime( - activeCount > 0 ? (readBehind / static_cast(activeCount)) : 0.0, - 1.0); +#if defined(TLRENDER_BMD) + if (_p && _p->bmdOutputDevice) + { + _p->bmdOutputDevice->tick(); + } +#endif // TLRENDER_BMD } void App::_settingsUpdate(const std::string& name) @@ -995,12 +802,9 @@ namespace tl if (match || name.empty()) { const auto ioOptions = _ioOptions(); - for (const auto& player : p.players) + if (p.player) { - if (player) - { - player->setIOOptions(ioOptions); - } + p.player->setIOOptions(ioOptions); } } } @@ -1047,45 +851,181 @@ namespace tl } } - void App::_cacheUpdate() + void App::_filesUpdate(const std::vector >& files) { TLRENDER_P(); - const auto activePlayers = this->activePlayers(); + std::vector > timelines(files.size()); + for (size_t i = 0; i < files.size(); ++i) + { + const auto j = std::find(p.files.begin(), p.files.end(), files[i]); + if (j != p.files.end()) + { + timelines[i] = p.timelines[j - p.files.begin()]; + } + } - // Update the I/O cache. - auto ioSystem = _context->getSystem(); - ioSystem->getCache()->setMax( - p.settings->getValue("Cache/Size") * memory::gigabyte); + for (size_t i = 0; i < files.size(); ++i) + { + if (!timelines[i]) + { + try + { + timeline::Options options; + options.fileSequenceAudio = + p.settings->getValue("FileSequence/Audio"); + options.fileSequenceAudioFileName = + p.settings->getValue("FileSequence/AudioFileName"); + options.fileSequenceAudioDirectory = + p.settings->getValue("FileSequence/AudioDirectory"); + options.videoRequestCount = + p.settings->getValue("Performance/VideoRequestCount"); + options.audioRequestCount = + p.settings->getValue("Performance/AudioRequestCount"); + options.ioOptions = _ioOptions(); + options.pathOptions.maxNumberDigits = + p.settings->getValue("FileSequence/MaxDigits"); + auto otioTimeline = files[i]->audioPath.isEmpty() ? + timeline::create(files[i]->path, _context, options) : + timeline::create(files[i]->path, files[i]->audioPath, _context, options); + if (0) + { + timeline::toMemoryReferences( + otioTimeline, + files[i]->path.getDirectory(), + timeline::ToMemoryReference::Shared, + options.pathOptions); + } + timelines[i] = timeline::Timeline::create(otioTimeline, _context, options); + for (const auto& video : timelines[i]->getIOInfo().video) + { + files[i]->videoLayers.push_back(video.name); + } + } + catch (const std::exception& e) + { + _log(e.what(), log::Type::Error); + } + } + } - // Update inactive players. - timeline::PlayerCacheOptions cacheOptions; - cacheOptions.readAhead = time::invalidTime; - cacheOptions.readBehind = time::invalidTime; - for (const auto& player : p.players) + p.files = files; + p.timelines = timelines; + } + + void App::_activeUpdate(const std::vector >& activeFiles) + { + TLRENDER_P(); + + QSharedPointer player; + if (!activeFiles.empty()) { - const auto j = std::find( - activePlayers.begin(), - activePlayers.end(), - player); - if (j == activePlayers.end()) + if (!p.activeFiles.empty() && activeFiles[0] == p.activeFiles[0]) + { + player = p.player; + } + else { - if (player) + auto i = std::find(p.files.begin(), p.files.end(), activeFiles[0]); + if (i != p.files.end()) { - player->setCacheOptions(cacheOptions); + try + { + timeline::PlayerOptions playerOptions; + playerOptions.cache.readAhead = time::invalidTime; + playerOptions.cache.readBehind = time::invalidTime; + playerOptions.timerMode = + p.settings->getValue("Performance/TimerMode"); + playerOptions.audioBufferFrameCount = + p.settings->getValue("Performance/AudioBufferFrameCount"); + auto timeline = p.timelines[i - p.files.begin()]; + player.reset(new qt::TimelinePlayer( + timeline::Player::create(timeline, _context, playerOptions), + _context, + this)); + } + catch (const std::exception& e) + { + _log(e.what(), log::Type::Error); + } + } + } + } + if (player) + { + std::vector > compare; + for (size_t i = 1; i < activeFiles.size(); ++i) + { + auto j = std::find(p.files.begin(), p.files.end(), activeFiles[i]); + if (j != p.files.end()) + { + auto timeline = p.timelines[j - p.files.begin()]; + compare.push_back(timeline); } } + player->setCompare(compare); + player->setCompareTime(p.filesModel->getCompareTime()); } - // Update active players. - cacheOptions.readAhead = _cacheReadAhead(); - cacheOptions.readBehind = _cacheReadBehind(); - for (const auto& player : activePlayers) + p.activeFiles = activeFiles; + p.player = player; +#if defined(TLRENDER_BMD) + p.bmdOutputDevice->setPlayer(p.player ? p.player->player() : nullptr); +#endif // TLRENDER_BMD + + _layersUpdate(p.filesModel->observeLayers()->get()); + _cacheUpdate(); + _audioUpdate(); + + Q_EMIT playerChanged(p.player); + } + + void App::_layersUpdate(const std::vector& value) + { + TLRENDER_P(); + if (auto player = p.player) { - if (player) + int videoLayer = 0; + std::vector compareVideoLayers; + if (!value.empty() && value.size() == p.files.size() && !p.activeFiles.empty()) { - player->setCacheOptions(cacheOptions); + auto i = std::find(p.files.begin(), p.files.end(), p.activeFiles.front()); + if (i != p.files.end()) + { + videoLayer = value[i - p.files.begin()]; + } + for (size_t j = 1; j < p.activeFiles.size(); ++j) + { + i = std::find(p.files.begin(), p.files.end(), p.activeFiles[j]); + if (i != p.files.end()) + { + compareVideoLayers.push_back(value[i - p.files.begin()]); + } + } } + player->setVideoLayer(videoLayer); + player->setCompareVideoLayers(compareVideoLayers); + } + } + + void App::_cacheUpdate() + { + TLRENDER_P(); + + auto ioSystem = _context->getSystem(); + ioSystem->getCache()->setMax( + p.settings->getValue("Cache/Size") * memory::gigabyte); + + timeline::PlayerCacheOptions cacheOptions; + cacheOptions.readAhead = otime::RationalTime( + p.settings->getValue("Cache/ReadAhead"), + 1.0); + cacheOptions.readBehind = otime::RationalTime( + p.settings->getValue("Cache/ReadBehind"), + 1.0); + if (p.player) + { + p.player->setCacheOptions(cacheOptions); } } @@ -1122,14 +1062,11 @@ namespace tl const float volume = p.audioModel->getVolume(); const bool mute = p.audioModel->isMuted(); const double audioOffset = p.audioModel->getSyncOffset(); - for (auto player : p.players) + if (p.player) { - if (player) - { - player->setVolume(volume); - player->setMute(mute || p.bmdDeviceActive); - player->setAudioOffset(audioOffset); - } + p.player->setVolume(volume); + p.player->setMute(mute || p.bmdDeviceActive); + p.player->setAudioOffset(audioOffset); } #if defined(TLRENDER_BMD) p.bmdOutputDevice->setVolume(volume); diff --git a/lib/tlPlayQtApp/App.h b/lib/tlPlayQtApp/App.h index 469628ab8..01d00ece5 100644 --- a/lib/tlPlayQtApp/App.h +++ b/lib/tlPlayQtApp/App.h @@ -79,11 +79,8 @@ namespace tl //! Get the files model. const std::shared_ptr& filesModel() const; - //! Get the timeline players. - const QVector >& players() const; - - //! Get the active timeline players. - QVector > activePlayers() const; + //! Get the timeline player. + const QSharedPointer& player() const; //! Get the recent files model. const std::shared_ptr& recentFilesModel() const; @@ -122,21 +119,13 @@ namespace tl void setSecondaryWindow(bool); Q_SIGNALS: - //! This signal is emitted when the active players are changed. - void activePlayersChanged(const QVector >&); + //! This signal is emitted when the timeline player is changed. + void playerChanged(const QSharedPointer&); //! This signal is emitted when the secondary window active state is changed. void secondaryWindowChanged(bool); - private Q_SLOTS: - void _filesCallback(const std::vector >&); - void _activeCallback(const std::vector >&); - void _mainWindowDestroyedCallback(); - void _secondaryWindowDestroyedCallback(); - private: - void _timerCallback(); - void _fileLogInit(const std::string&); void _settingsInit(const std::string&); void _modelsInit(); @@ -146,10 +135,12 @@ namespace tl void _windowsInit(); io::Options _ioOptions() const; - otime::RationalTime _cacheReadAhead() const; - otime::RationalTime _cacheReadBehind() const; void _settingsUpdate(const std::string&); + void _timerUpdate(); + void _filesUpdate(const std::vector >&); + void _activeUpdate(const std::vector >&); + void _layersUpdate(const std::vector&); void _cacheUpdate(); void _viewUpdate(const math::Vector2i& pos, double zoom, bool frame); void _audioUpdate(); diff --git a/lib/tlPlayQtApp/CompareActions.cpp b/lib/tlPlayQtApp/CompareActions.cpp index e79092c84..cc343cb6d 100644 --- a/lib/tlPlayQtApp/CompareActions.cpp +++ b/lib/tlPlayQtApp/CompareActions.cpp @@ -26,10 +26,12 @@ namespace tl QScopedPointer menu; QScopedPointer bMenu; + QScopedPointer timeMenu; std::shared_ptr > > filesObserver; std::shared_ptr > bIndexesObserver; std::shared_ptr > compareOptionsObserver; + std::shared_ptr > compareTimeObserver; }; CompareActions::CompareActions(App* app, QObject* parent) : @@ -40,6 +42,18 @@ namespace tl p.app = app; + p.actions["Next"] = new QAction(this); + p.actions["Next"]->setText(tr("Next")); + p.actions["Next"]->setIcon(QIcon(":/Icons/Next.svg")); + p.actions["Next"]->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_PageDown)); + p.actions["Next"]->setToolTip(tr("Change to the next file")); + + p.actions["Prev"] = new QAction(this); + p.actions["Prev"]->setText(tr("Previous")); + p.actions["Prev"]->setIcon(QIcon(":/Icons/Prev.svg")); + p.actions["Prev"]->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_PageUp)); + p.actions["Prev"]->setToolTip(tr("Change to the previous file")); + p.actions["A"] = new QAction(this); p.actions["A"]->setData(QVariant::fromValue(timeline::CompareMode::A)); p.actions["A"]->setCheckable(true); @@ -102,17 +116,17 @@ namespace tl p.actions["Tile"]->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_T)); p.actions["Tile"]->setToolTip(tr("Tile the A and B files")); - p.actions["Next"] = new QAction(this); - p.actions["Next"]->setText(tr("Next")); - p.actions["Next"]->setIcon(QIcon(":/Icons/Next.svg")); - p.actions["Next"]->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_PageDown)); - p.actions["Next"]->setToolTip(tr("Change to the next file")); + p.actions["Relative"] = new QAction(this); + p.actions["Relative"]->setData(QVariant::fromValue(timeline::CompareTimeMode::Relative)); + p.actions["Relative"]->setCheckable(true); + p.actions["Relative"]->setText(tr("Relative")); + p.actions["Relative"]->setToolTip(tr("Compare relative times")); - p.actions["Prev"] = new QAction(this); - p.actions["Prev"]->setText(tr("Previous")); - p.actions["Prev"]->setIcon(QIcon(":/Icons/Prev.svg")); - p.actions["Prev"]->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_PageUp)); - p.actions["Prev"]->setToolTip(tr("Change to the previous file")); + p.actions["Absolute"] = new QAction(this); + p.actions["Absolute"]->setData(QVariant::fromValue(timeline::CompareTimeMode::Absolute)); + p.actions["Absolute"]->setCheckable(true); + p.actions["Absolute"]->setText(tr("Absolute")); + p.actions["Absolute"]->setToolTip(tr("Compare absolute times")); p.actionGroups["B"] = new QActionGroup(this); @@ -127,11 +141,18 @@ namespace tl p.actionGroups["Compare"]->addAction(p.actions["Vertical"]); p.actionGroups["Compare"]->addAction(p.actions["Tile"]); + p.actionGroups["Time"] = new QActionGroup(this); + p.actionGroups["Time"]->setExclusive(true); + p.actionGroups["Time"]->addAction(p.actions["Relative"]); + p.actionGroups["Time"]->addAction(p.actions["Absolute"]); + p.menu.reset(new QMenu); p.menu->setTitle(tr("&Compare")); p.bMenu.reset(new QMenu); p.bMenu->setTitle(tr("&B")); p.menu->addMenu(p.bMenu.get()); + p.menu->addAction(p.actions["Next"]); + p.menu->addAction(p.actions["Prev"]); p.menu->addSeparator(); p.menu->addAction(p.actions["A"]); p.menu->addAction(p.actions["B"]); @@ -142,8 +163,11 @@ namespace tl p.menu->addAction(p.actions["Vertical"]); p.menu->addAction(p.actions["Tile"]); p.menu->addSeparator(); - p.menu->addAction(p.actions["Next"]); - p.menu->addAction(p.actions["Prev"]); + p.timeMenu.reset(new QMenu); + p.timeMenu->setTitle(tr("&Time")); + p.menu->addMenu(p.timeMenu.get()); + p.timeMenu->addAction(p.actions["Relative"]); + p.timeMenu->addAction(p.actions["Absolute"]); _actionsUpdate(); @@ -183,6 +207,15 @@ namespace tl app->filesModel()->setCompareOptions(options); }); + connect( + p.actionGroups["Time"], + &QActionGroup::triggered, + [this, app](QAction* action) + { + const timeline::CompareTimeMode value = action->data().value(); + app->filesModel()->setCompareTime(value); + }); + p.filesObserver = observer::ListObserver >::create( app->filesModel()->observeFiles(), [this](const std::vector >&) @@ -203,6 +236,13 @@ namespace tl { _actionsUpdate(); }); + + p.compareTimeObserver = observer::ValueObserver::create( + app->filesModel()->observeCompareTime(), + [this](timeline::CompareTimeMode) + { + _actionsUpdate(); + }); } CompareActions::~CompareActions() @@ -242,14 +282,29 @@ namespace tl p.bMenu->addAction(action); } - const auto options = p.app->filesModel()->getCompareOptions(); - QSignalBlocker blocker(p.actionGroups["Compare"]); - for (auto action : p.actionGroups["Compare"]->actions()) { - if (action->data().value() == options.mode) + const auto options = p.app->filesModel()->getCompareOptions(); + QSignalBlocker blocker(p.actionGroups["Compare"]); + for (auto action : p.actionGroups["Compare"]->actions()) + { + if (action->data().value() == options.mode) + { + action->setChecked(true); + break; + } + } + } + + { + const timeline::CompareTimeMode time = p.app->filesModel()->getCompareTime(); + QSignalBlocker blocker(p.actionGroups["Time"]); + for (auto action : p.actionGroups["Time"]->actions()) { - action->setChecked(true); - break; + if (action->data().value() == time) + { + action->setChecked(true); + break; + } } } } diff --git a/lib/tlPlayQtApp/FilesTool.cpp b/lib/tlPlayQtApp/FilesTool.cpp index a1fe8e83f..d2c4f7cd2 100644 --- a/lib/tlPlayQtApp/FilesTool.cpp +++ b/lib/tlPlayQtApp/FilesTool.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -28,6 +29,8 @@ namespace tl App* app = nullptr; std::vector > items; + QButtonGroup* aButtonGroup = nullptr; + QButtonGroup* bButtonGroup = nullptr; std::vector aButtons; std::vector bButtons; std::vector layerComboBoxes; @@ -55,6 +58,12 @@ namespace tl p.app = app; + p.aButtonGroup = new QButtonGroup; + p.aButtonGroup->setExclusive(true); + + p.bButtonGroup = new QButtonGroup; + p.bButtonGroup->setExclusive(false); + p.wipeXSlider = new qtwidget::FloatEditSlider; p.wipeYSlider = new qtwidget::FloatEditSlider; @@ -87,6 +96,33 @@ namespace tl addStretch(); + connect( + p.aButtonGroup, + QOverload::of(&QButtonGroup::buttonToggled), + [this, app](QAbstractButton* button, bool value) + { + if (value) + { + auto i = std::find(_p->aButtons.begin(), _p->aButtons.end(), button); + if (i != _p->aButtons.end()) + { + app->filesModel()->setA(i - _p->aButtons.begin()); + } + } + }); + + connect( + p.bButtonGroup, + QOverload::of(&QButtonGroup::buttonToggled), + [this, app](QAbstractButton* button, bool value) + { + auto i = std::find(_p->bButtons.begin(), _p->bButtons.end(), button); + if (i != _p->bButtons.end()) + { + app->filesModel()->setB(i - _p->bButtons.begin(), value); + } + }); + connect( p.wipeXSlider, &qtwidget::FloatEditSlider::valueChanged, @@ -169,11 +205,13 @@ namespace tl for (auto i : p.aButtons) { + p.aButtonGroup->removeButton(i); delete i; } p.aButtons.clear(); for (auto i : p.bButtons) { + p.bButtonGroup->removeButton(i); delete i; } p.bButtons.clear(); @@ -201,6 +239,7 @@ namespace tl aButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); aButton->setToolTip(QString::fromUtf8(item->path.get().c_str())); p.aButtons.push_back(aButton); + p.aButtonGroup->addButton(aButton); auto bButton = new QToolButton; bButton->setText("B"); @@ -210,6 +249,7 @@ namespace tl bButton->setAutoRaise(true); bButton->setToolTip("Set the B file(s)"); p.bButtons.push_back(bButton); + p.bButtonGroup->addButton(bButton); auto layerComboBox = new QComboBox; for (const auto& layer : item->videoLayers) @@ -224,22 +264,6 @@ namespace tl p.itemsLayout->addWidget(bButton, i, 1); p.itemsLayout->addWidget(layerComboBox, i, 2); - connect( - aButton, - &QToolButton::toggled, - [this, i](bool value) - { - _p->app->filesModel()->setA(i); - }); - - connect( - bButton, - &QToolButton::toggled, - [this, i](bool value) - { - _p->app->filesModel()->setB(i, value); - }); - connect( layerComboBox, QOverload::of(&QComboBox::currentIndexChanged), diff --git a/lib/tlPlayQtApp/FrameActions.cpp b/lib/tlPlayQtApp/FrameActions.cpp index bafe17c8d..aaab6034e 100644 --- a/lib/tlPlayQtApp/FrameActions.cpp +++ b/lib/tlPlayQtApp/FrameActions.cpp @@ -17,7 +17,7 @@ namespace tl { struct FrameActions::Private { - QVector > timelinePlayers; + QSharedPointer player; QMap actions; QMap actionGroups; @@ -100,7 +100,7 @@ namespace tl p.menu->addSeparator(); p.menu->addAction(p.actions["FocusCurrentFrame"]); - _playersUpdate(app->players()); + _playerUpdate(app->player()); _actionsUpdate(); connect( @@ -108,9 +108,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->start(); + _p->player->start(); } }); connect( @@ -118,9 +118,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->end(); + _p->player->end(); } }); connect( @@ -128,9 +128,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->framePrev(); + _p->player->framePrev(); } }); connect( @@ -138,9 +138,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::FramePrevX10); + _p->player->timeAction(timeline::TimeAction::FramePrevX10); } }); connect( @@ -148,9 +148,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::FramePrevX100); + _p->player->timeAction(timeline::TimeAction::FramePrevX100); } }); connect( @@ -158,9 +158,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->frameNext(); + _p->player->frameNext(); } }); connect( @@ -168,9 +168,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::FrameNextX10); + _p->player->timeAction(timeline::TimeAction::FrameNextX10); } }); connect( @@ -178,9 +178,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::FrameNextX100); + _p->player->timeAction(timeline::TimeAction::FrameNextX100); } }); @@ -189,9 +189,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->setInPoint(); + _p->player->setInPoint(); } }); connect( @@ -199,9 +199,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->resetInPoint(); + _p->player->resetInPoint(); } }); connect( @@ -209,9 +209,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->setOutPoint(); + _p->player->setOutPoint(); } }); connect( @@ -219,18 +219,18 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->resetOutPoint(); + _p->player->resetOutPoint(); } }); connect( app, - &App::activePlayersChanged, - [this](const QVector >& value) + &App::playerChanged, + [this](const QSharedPointer& value) { - _playersUpdate(value); + _playerUpdate(value); }); } @@ -247,22 +247,20 @@ namespace tl return _p->menu.get(); } - void FrameActions::_playersUpdate(const QVector >& timelinePlayers) + void FrameActions::_playerUpdate(const QSharedPointer& player) { TLRENDER_P(); - p.timelinePlayers = timelinePlayers; + p.player = player; _actionsUpdate(); } void FrameActions::_actionsUpdate() { TLRENDER_P(); - - const size_t count = p.timelinePlayers.size(); QList keys = p.actions.keys(); for (auto i : keys) { - p.actions[i]->setEnabled(count > 0); + p.actions[i]->setEnabled(p.player.get()); } } } diff --git a/lib/tlPlayQtApp/FrameActions.h b/lib/tlPlayQtApp/FrameActions.h index 8228c24f8..be0e8c915 100644 --- a/lib/tlPlayQtApp/FrameActions.h +++ b/lib/tlPlayQtApp/FrameActions.h @@ -35,7 +35,7 @@ namespace tl QMenu* menu() const; private: - void _playersUpdate(const QVector >&); + void _playerUpdate(const QSharedPointer&); void _actionsUpdate(); TLRENDER_PRIVATE(); diff --git a/lib/tlPlayQtApp/MainWindow.cpp b/lib/tlPlayQtApp/MainWindow.cpp index 1e4c2f903..be37eef82 100644 --- a/lib/tlPlayQtApp/MainWindow.cpp +++ b/lib/tlPlayQtApp/MainWindow.cpp @@ -78,7 +78,7 @@ namespace tl { App* app = nullptr; - QVector > timelinePlayers; + QSharedPointer player; bool floatOnTop = false; FileActions* fileActions = nullptr; @@ -404,7 +404,7 @@ namespace tl p.timelineViewport->setFocus(); - _playersUpdate(app->players()); + _playerUpdate(app->player()); _widgetUpdate(); p.filesObserver = observer::ListObserver >::create( @@ -576,11 +576,11 @@ namespace tl &qtwidget::TimeSpinBox::valueChanged, [this](const otime::RationalTime& value) { - if (!_p->timelinePlayers.isEmpty()) + if (_p->player) { - _p->timelinePlayers[0]->setPlayback(timeline::Playback::Stop); - _p->timelinePlayers[0]->seek(value); - _p->currentTimeSpinBox->setValue(_p->timelinePlayers[0]->currentTime()); + _p->player->setPlayback(timeline::Playback::Stop); + _p->player->seek(value); + _p->currentTimeSpinBox->setValue(_p->player->currentTime()); } }); @@ -589,9 +589,9 @@ namespace tl QOverload::of(&QDoubleSpinBox::valueChanged), [this](double value) { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->setSpeed(value); + _p->player->setSpeed(value); } }); @@ -626,10 +626,10 @@ namespace tl connect( app, - &App::activePlayersChanged, - [this](const QVector >& value) + &App::playerChanged, + [this](const QSharedPointer& value) { - _playersUpdate(value); + _playerUpdate(value); }); connect( @@ -738,47 +738,47 @@ namespace tl p.app->audioModel()->setVolume(value / static_cast(sliderSteps)); } - void MainWindow::_playersUpdate(const QVector >& timelinePlayers) + void MainWindow::_playerUpdate(const QSharedPointer& player) { TLRENDER_P(); - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { disconnect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(speedChanged(double)), this, SLOT(_speedCallback(double))); disconnect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(playbackChanged(tl::timeline::Playback)), this, SLOT(_playbackCallback(tl::timeline::Playback))); disconnect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(currentTimeChanged(const otime::RationalTime&)), this, SLOT(_currentTimeCallback(const otime::RationalTime&))); } - p.timelinePlayers = timelinePlayers; + p.player = player; - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { connect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(speedChanged(double)), SLOT(_speedCallback(double))); connect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(playbackChanged(tl::timeline::Playback)), SLOT(_playbackCallback(tl::timeline::Playback))); connect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(currentTimeChanged(const otime::RationalTime&)), SLOT(_currentTimeCallback(const otime::RationalTime&))); } - p.timelineViewport->setTimelinePlayers(p.timelinePlayers); + p.timelineViewport->setPlayer(p.player); _widgetUpdate(); } @@ -796,24 +796,24 @@ namespace tl p.speedSpinBox->setEnabled(count > 0); p.volumeSlider->setEnabled(count > 0); - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { { QSignalBlocker blocker(p.currentTimeSpinBox); - p.currentTimeSpinBox->setValue(p.timelinePlayers[0]->currentTime()); + p.currentTimeSpinBox->setValue(p.player->currentTime()); } { QSignalBlocker blocker(p.speedSpinBox); - p.speedSpinBox->setValue(p.timelinePlayers[0]->speed()); + p.speedSpinBox->setValue(p.player->speed()); } - const auto& timeRange = p.timelinePlayers[0]->timeRange(); + const auto& timeRange = p.player->timeRange(); p.durationLabel->setValue(timeRange.duration()); { QSignalBlocker blocker(p.volumeSlider); - p.volumeSlider->setValue(p.timelinePlayers[0]->volume() * sliderSteps); + p.volumeSlider->setValue(p.player->volume() * sliderSteps); } } else @@ -847,22 +847,14 @@ namespace tl p.timelineViewport->setLUTOptions(colorModel->getLUTOptions()); std::vector imageOptions; std::vector displayOptions; - for (const auto& i : p.timelinePlayers) - { - imageOptions.push_back(colorModel->getImageOptions()); - displayOptions.push_back(colorModel->getDisplayOptions()); - } - p.timelineViewport->setImageOptions(imageOptions); - p.timelineViewport->setDisplayOptions(displayOptions); + p.timelineViewport->setImageOptions({ colorModel->getImageOptions() }); + p.timelineViewport->setDisplayOptions({ colorModel->getDisplayOptions() }); p.timelineViewport->setCompareOptions( p.app->filesModel()->getCompareOptions()); p.timelineViewport->setBackgroundOptions( p.app->viewportModel()->getBackgroundOptions()); - p.timelineWidget->setPlayer( - (!p.timelinePlayers.empty() && p.timelinePlayers[0]) ? - p.timelinePlayers[0]->player() : - nullptr); + p.timelineWidget->setPlayer(p.player ? p.player->player() : nullptr); { const QSignalBlocker blocker(p.volumeSlider); @@ -870,17 +862,14 @@ namespace tl p.volumeSlider->setValue(volume * sliderSteps); } - p.infoTool->setInfo( - (!p.timelinePlayers.empty() && p.timelinePlayers[0]) ? - p.timelinePlayers[0]->ioInfo() : - io::Info()); + p.infoTool->setInfo(p.player ? p.player->ioInfo() : io::Info()); std::string infoLabel; std::string infoToolTip; - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { - const file::Path& path = p.timelinePlayers[0]->path(); - const io::Info& ioInfo = p.timelinePlayers[0]->ioInfo(); + const file::Path& path = p.player->path(); + const io::Info& ioInfo = p.player->ioInfo(); infoLabel = play::infoLabel(path, ioInfo); infoToolTip = play::infoToolTip(path, ioInfo); } diff --git a/lib/tlPlayQtApp/MainWindow.h b/lib/tlPlayQtApp/MainWindow.h index 76ae62002..1fd01aaa7 100644 --- a/lib/tlPlayQtApp/MainWindow.h +++ b/lib/tlPlayQtApp/MainWindow.h @@ -49,7 +49,7 @@ namespace tl void _volumeCallback(int); private: - void _playersUpdate(const QVector >&); + void _playerUpdate(const QSharedPointer&); void _widgetUpdate(); TLRENDER_PRIVATE(); diff --git a/lib/tlPlayQtApp/PlaybackActions.cpp b/lib/tlPlayQtApp/PlaybackActions.cpp index a1ea944a3..7d06c6729 100644 --- a/lib/tlPlayQtApp/PlaybackActions.cpp +++ b/lib/tlPlayQtApp/PlaybackActions.cpp @@ -19,7 +19,7 @@ namespace tl { App* app = nullptr; - QVector > timelinePlayers; + QSharedPointer player; QMap actions; QMap actionGroups; @@ -168,7 +168,7 @@ namespace tl p.speedMenu->addSeparator(); p.speedMenu->addAction(p.actions["Speed/Default"]); - _playersUpdate(app->players()); + _playerUpdate(app->player()); _actionsUpdate(); connect( @@ -176,9 +176,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->togglePlayback(); + _p->player->togglePlayback(); } }); @@ -187,9 +187,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::JumpBack1s); + _p->player->timeAction(timeline::TimeAction::JumpBack1s); } }); connect( @@ -197,9 +197,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::JumpBack10s); + _p->player->timeAction(timeline::TimeAction::JumpBack10s); } }); connect( @@ -207,9 +207,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::JumpForward1s); + _p->player->timeAction(timeline::TimeAction::JumpForward1s); } }); connect( @@ -217,9 +217,9 @@ namespace tl &QAction::triggered, [this] { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->timeAction(timeline::TimeAction::JumpForward10s); + _p->player->timeAction(timeline::TimeAction::JumpForward10s); } }); @@ -228,9 +228,9 @@ namespace tl &QActionGroup::triggered, [this](QAction* action) { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->setPlayback(action->data().value()); + _p->player->setPlayback(action->data().value()); } }); @@ -239,9 +239,9 @@ namespace tl &QActionGroup::triggered, [this](QAction* action) { - if (!_p->timelinePlayers.empty()) + if (_p->player) { - _p->timelinePlayers[0]->setLoop(action->data().value()); + _p->player->setLoop(action->data().value()); } }); @@ -259,19 +259,19 @@ namespace tl &QActionGroup::triggered, [this](QAction* action) { - if (!_p->timelinePlayers.empty()) + if (_p->player) { const float speed = action->data().toFloat(); - _p->timelinePlayers[0]->setSpeed(speed > 0.F ? speed : _p->timelinePlayers[0]->defaultSpeed()); + _p->player->setSpeed(speed > 0.F ? speed : _p->player->defaultSpeed()); } }); connect( app, - &App::activePlayersChanged, - [this](const QVector >& value) + &App::playerChanged, + [this](const QSharedPointer& value) { - _playersUpdate(value); + _playerUpdate(value); }); } @@ -321,33 +321,33 @@ namespace tl } } - void PlaybackActions::_playersUpdate(const QVector >& timelinePlayers) + void PlaybackActions::_playerUpdate(const QSharedPointer& player) { TLRENDER_P(); - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { disconnect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(playbackChanged(tl::timeline::Playback)), this, SLOT(_playbackCallback(tl::timeline::Playback))); disconnect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(loopChanged(tl::timeline::Loop)), this, SLOT(_loopCallback(tl::timeline::Loop))); } - p.timelinePlayers = timelinePlayers; + p.player = player; - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { connect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(playbackChanged(tl::timeline::Playback)), SLOT(_playbackCallback(tl::timeline::Playback))); connect( - p.timelinePlayers[0].get(), + p.player.get(), SIGNAL(loopChanged(tl::timeline::Loop)), SLOT(_loopCallback(tl::timeline::Loop))); } @@ -359,20 +359,19 @@ namespace tl { TLRENDER_P(); - const size_t count = p.timelinePlayers.size(); QList keys = p.actions.keys(); for (auto i : keys) { - p.actions[i]->setEnabled(count > 0); + p.actions[i]->setEnabled(p.player.get()); } - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { { QSignalBlocker blocker(p.actionGroups["Playback"]); for (auto action : p.actionGroups["Playback"]->actions()) { - if (action->data().value() == p.timelinePlayers[0]->playback()) + if (action->data().value() == p.player->playback()) { action->setChecked(true); break; @@ -383,7 +382,7 @@ namespace tl QSignalBlocker blocker(p.actionGroups["Loop"]); for (auto action : p.actionGroups["Loop"]->actions()) { - if (action->data().value() == p.timelinePlayers[0]->loop()) + if (action->data().value() == p.player->loop()) { action->setChecked(true); break; diff --git a/lib/tlPlayQtApp/PlaybackActions.h b/lib/tlPlayQtApp/PlaybackActions.h index 57e23e51c..81efca87e 100644 --- a/lib/tlPlayQtApp/PlaybackActions.h +++ b/lib/tlPlayQtApp/PlaybackActions.h @@ -42,7 +42,7 @@ namespace tl void _loopCallback(tl::timeline::Loop); private: - void _playersUpdate(const QVector >&); + void _playerUpdate(const QSharedPointer&); void _actionsUpdate(); TLRENDER_PRIVATE(); diff --git a/lib/tlPlayQtApp/SecondaryWindow.cpp b/lib/tlPlayQtApp/SecondaryWindow.cpp index f77751430..e1c7e4c9c 100644 --- a/lib/tlPlayQtApp/SecondaryWindow.cpp +++ b/lib/tlPlayQtApp/SecondaryWindow.cpp @@ -60,14 +60,14 @@ namespace tl const math::Size2i size = settings->getValue("SecondaryWindow/Size"); resize(size.w, size.h); - p.viewport->setTimelinePlayers(app->activePlayers()); + p.viewport->setPlayer(app->player()); connect( app, - &App::activePlayersChanged, - [this](const QVector >& value) + &App::playerChanged, + [this](const QSharedPointer& value) { - _p->viewport->setTimelinePlayers(value); + _p->viewport->setPlayer(value); }); p.ocioOptionsObserver = observer::ValueObserver::create( diff --git a/lib/tlQt/Init.cpp b/lib/tlQt/Init.cpp index 0fd81cb62..1d299a1e1 100644 --- a/lib/tlQt/Init.cpp +++ b/lib/tlQt/Init.cpp @@ -101,6 +101,7 @@ namespace tl qRegisterMetaType("tl::timeline::Channels"); qRegisterMetaType("tl::timeline::Color"); qRegisterMetaType("tl::timeline::CompareMode"); + qRegisterMetaType("tl::timeline::CompareTimeMode"); qRegisterMetaType("tl::timeline::CompareOptions"); qRegisterMetaType("tl::timeline::EXRDisplay"); qRegisterMetaType("tl::timeline::FileSequenceAudio"); @@ -126,6 +127,7 @@ namespace tl QMetaType::registerComparators(); QMetaType::registerComparators(); QMetaType::registerComparators(); + QMetaType::registerComparators(); QMetaType::registerComparators(); QMetaType::registerComparators(); QMetaType::registerComparators(); diff --git a/lib/tlQt/MetaTypes.h b/lib/tlQt/MetaTypes.h index b24a327b5..975a3e5fa 100644 --- a/lib/tlQt/MetaTypes.h +++ b/lib/tlQt/MetaTypes.h @@ -33,6 +33,7 @@ Q_DECLARE_METATYPE(tl::io::FileType); Q_DECLARE_METATYPE(tl::timeline::AlphaBlend); Q_DECLARE_METATYPE(tl::timeline::Channels); Q_DECLARE_METATYPE(tl::timeline::CompareMode); +Q_DECLARE_METATYPE(tl::timeline::CompareTimeMode); Q_DECLARE_METATYPE(tl::timeline::FileSequenceAudio); Q_DECLARE_METATYPE(tl::timeline::ImageFilter); Q_DECLARE_METATYPE(tl::timeline::InputVideoLevels); diff --git a/lib/tlQt/TimelinePlayer.cpp b/lib/tlQt/TimelinePlayer.cpp index 44afa6f06..c623dee1e 100644 --- a/lib/tlQt/TimelinePlayer.cpp +++ b/lib/tlQt/TimelinePlayer.cpp @@ -31,8 +31,12 @@ namespace tl std::shared_ptr > loopObserver; std::shared_ptr > currentTimeObserver; std::shared_ptr > inOutRangeObserver; + std::shared_ptr > > compareObserver; + std::shared_ptr > compareTimeObserver; std::shared_ptr > ioOptionsObserver; - std::shared_ptr > currentVideoObserver; + std::shared_ptr > videoLayerObserver; + std::shared_ptr > compareVideoLayersObserver; + std::shared_ptr > currentVideoObserver; std::shared_ptr > volumeObserver; std::shared_ptr > muteObserver; std::shared_ptr > audioOffsetObserver; @@ -84,6 +88,20 @@ namespace tl Q_EMIT inOutRangeChanged(value); }); + p.compareObserver = observer::ListObserver >::create( + p.player->observeCompare(), + [this](const std::vector >& value) + { + Q_EMIT compareChanged(value); + }); + + p.compareTimeObserver = observer::ValueObserver::create( + p.player->observeCompareTime(), + [this](timeline::CompareTimeMode value) + { + Q_EMIT compareTimeChanged(value); + }); + p.ioOptionsObserver = observer::ValueObserver::create( p.player->observeIOOptions(), [this](const io::Options& value) @@ -91,9 +109,23 @@ namespace tl Q_EMIT ioOptionsChanged(value); }); - p.currentVideoObserver = observer::ValueObserver::create( + p.videoLayerObserver = observer::ValueObserver::create( + p.player->observeVideoLayer(), + [this](int value) + { + Q_EMIT videoLayerChanged(value); + }); + + p.compareVideoLayersObserver = observer::ListObserver::create( + p.player->observeCompareVideoLayers(), + [this](const std::vector& value) + { + Q_EMIT compareVideoLayersChanged(value); + }); + + p.currentVideoObserver = observer::ListObserver::create( p.player->observeCurrentVideo(), - [this](const timeline::VideoData& value) + [this](const std::vector& value) { Q_EMIT currentVideoChanged(value); }, @@ -205,6 +237,11 @@ namespace tl return _p->player->getIOInfo(); } + std::vector TimelinePlayer::sizes() const + { + return _p->player->getSizes(); + } + double TimelinePlayer::defaultSpeed() const { return _p->player->getDefaultSpeed(); @@ -235,12 +272,32 @@ namespace tl return _p->player->observeInOutRange()->get(); } + const std::vector >& TimelinePlayer::compare() const + { + return _p->player->getCompare(); + } + + timeline::CompareTimeMode TimelinePlayer::compareTime() const + { + return _p->player->getCompareTime(); + } + const io::Options& TimelinePlayer::ioOptions() const { return _p->player->observeIOOptions()->get(); } - const timeline::VideoData& TimelinePlayer::currentVideo() const + int TimelinePlayer::videoLayer() const + { + return _p->player->getVideoLayer(); + } + + const std::vector& TimelinePlayer::compareVideoLayers() const + { + return _p->player->getCompareVideoLayers(); + } + + const std::vector& TimelinePlayer::currentVideo() const { return _p->player->getCurrentVideo(); } @@ -373,6 +430,26 @@ namespace tl _p->player->setIOOptions(value); } + void TimelinePlayer::setCompare(const std::vector >& value) + { + _p->player->setCompare(value); + } + + void TimelinePlayer::setCompareTime(timeline::CompareTimeMode value) + { + _p->player->setCompareTime(value); + } + + void TimelinePlayer::setVideoLayer(int value) + { + _p->player->setVideoLayer(value); + } + + void TimelinePlayer::setCompareVideoLayers(const std::vector& value) + { + _p->player->setCompareVideoLayers(value); + } + void TimelinePlayer::setVolume(float value) { _p->player->setVolume(value); diff --git a/lib/tlQt/TimelinePlayer.h b/lib/tlQt/TimelinePlayer.h index 13c95cb81..3fa2214a9 100644 --- a/lib/tlQt/TimelinePlayer.h +++ b/lib/tlQt/TimelinePlayer.h @@ -46,13 +46,34 @@ namespace tl READ inOutRange WRITE setInOutRange NOTIFY inOutRangeChanged) + Q_PROPERTY( + std::vector > + compare + READ compare + WRITE setCompare + NOTIFY compareChanged) + Q_PROPERTY( + tl::timeline::CompareTimeMode compareTime + READ compareTime + WRITE setCompareTime + NOTIFY compareTimeChanged) Q_PROPERTY( tl::io::Options ioOptions READ ioOptions WRITE setIOOptions NOTIFY ioOptionsChanged) Q_PROPERTY( - tl::timeline::VideoData + int videoLayer + READ videoLayer + WRITE setVideoLayer + NOTIFY videoLayerChanged) + Q_PROPERTY( + std::vector compareVideoLayers + READ compareVideoLayers + WRITE setCompareVideoLayers + NOTIFY compareVideoLayersChanged) + Q_PROPERTY( + std::vector currentVideo READ currentVideo NOTIFY currentVideoChanged) @@ -129,6 +150,9 @@ namespace tl //! the first clip in the timeline. const io::Info& ioInfo() const; + //! Get the timeline sizes. + std::vector sizes() const; + ///@} //! \name Playback @@ -172,11 +196,28 @@ namespace tl ///@} + //! \name Comparison + ///@{ + + //! Get the timelines for comparison. + const std::vector >& compare() const; + + //! Get the comparison time mode. + timeline::CompareTimeMode compareTime() const; + + ///@} + //! \name Video ///@{ + //! Get the video layer. + int videoLayer() const; + + //! Get the comparison video layers. + const std::vector& compareVideoLayers() const; + //! Get the current video data. - const timeline::VideoData& currentVideo() const; + const std::vector& currentVideo() const; ///@} @@ -286,6 +327,28 @@ namespace tl ///@} + //! \name Comparison + ///@{ + + //! Set the timelines for comparison. + void setCompare(const std::vector >&); + + //! Set the comparison time mode. + void setCompareTime(timeline::CompareTimeMode); + + ///@} + + //! \name Video + ///@{ + + //! Set the video layer. + void setVideoLayer(int); + + //! Set the comparison video layers. + void setCompareVideoLayers(const std::vector&); + + ///@} + //! \name Audio ///@{ @@ -329,6 +392,17 @@ namespace tl ///@} + //! \name Comparison + ///@{ + + //! This signal is emitted when the timelines for comparison are changed. + void compareChanged(const std::vector >&); + + //! This signal is emitted when the comparison time mode is changed. + void compareTimeChanged(tl::timeline::CompareTimeMode); + + ///@} + //! \name I/O ///@{ @@ -340,8 +414,14 @@ namespace tl //! \name Video ///@{ + //! This signal is emitted when the video layer is changed. + void videoLayerChanged(int); + + //! This signal is emitted when the comparison video layers are changed. + void compareVideoLayersChanged(const std::vector&); + //! This signal is emitted when the current video data is changed. - void currentVideoChanged(const tl::timeline::VideoData&); + void currentVideoChanged(const std::vector&); ///@} diff --git a/lib/tlQtWidget/TimelineViewport.cpp b/lib/tlQtWidget/TimelineViewport.cpp index 7a86ed1ce..d162ce823 100644 --- a/lib/tlQtWidget/TimelineViewport.cpp +++ b/lib/tlQtWidget/TimelineViewport.cpp @@ -33,8 +33,7 @@ namespace tl std::vector displayOptions; timeline::CompareOptions compareOptions; timeline::BackgroundOptions backgroundOptions; - QVector > timelinePlayers; - std::vector timelineSizes; + QSharedPointer player; std::vector videoData; math::Vector2i viewPos; double viewZoom = 1.0; @@ -146,66 +145,44 @@ namespace tl update(); } - void TimelineViewport::setTimelinePlayers(const QVector >& value) + void TimelineViewport::setPlayer(const QSharedPointer& value) { TLRENDER_P(); - for (const auto& player : p.timelinePlayers) + if (value) { - if (player) - { - disconnect( - player.get(), - SIGNAL(playbackChanged(tl::timeline::Playback)), - this, - SLOT(_playbackUpdate(tl::timeline::Playback))); - disconnect( - player.get(), - SIGNAL(currentVideoChanged(const tl::timeline::VideoData&)), - this, - SLOT(_videoDataUpdate(const tl::timeline::VideoData&))); - } + disconnect( + value.get(), + SIGNAL(playbackChanged(tl::timeline::Playback)), + this, + SLOT(_playbackUpdate(tl::timeline::Playback))); + disconnect( + value.get(), + SIGNAL(currentVideoChanged(const std::vector&)), + this, + SLOT(_videoDataUpdate(const std::vector&))); } - p.timelinePlayers = value; - - p.timelineSizes.clear(); - for (const auto& player : p.timelinePlayers) - { - if (player) - { - const auto& ioInfo = player->ioInfo(); - if (!ioInfo.video.empty()) - { - p.timelineSizes.push_back(ioInfo.video[0].size); - } - } - } + p.player = value; p.videoData.clear(); - for (const auto& player : p.timelinePlayers) + if (p.player) { - if (player) - { - p.videoData.push_back(player->currentVideo()); - } + p.videoData = p.player->currentVideo(); } p.doRender = true; update(); - for (const auto& player : p.timelinePlayers) + if (p.player) { - if (player) - { - connect( - player.get(), - SIGNAL(playbackChanged(tl::timeline::Playback)), - SLOT(_playbackUpdate(tl::timeline::Playback))); - connect( - player.get(), - SIGNAL(currentVideoChanged(const tl::timeline::VideoData&)), - SLOT(_videoDataUpdate(const tl::timeline::VideoData&))); - } + connect( + p.player.get(), + SIGNAL(playbackChanged(tl::timeline::Playback)), + SLOT(_playbackUpdate(tl::timeline::Playback))); + connect( + p.player.get(), + SIGNAL(currentVideoChanged(const std::vector&)), + SLOT(_videoDataUpdate(const std::vector&))); } } @@ -288,27 +265,10 @@ namespace tl } } - void TimelineViewport::_videoDataUpdate(const timeline::VideoData& value) + void TimelineViewport::_videoDataUpdate(const std::vector& value) { TLRENDER_P(); - if (p.videoData.size() != p.timelinePlayers.size()) - { - p.videoData = std::vector(p.timelinePlayers.size()); - } - for (size_t i = 0; i < p.videoData.size(); ++i) - { - if (!p.timelinePlayers[i]->timeRange().contains(p.videoData[i].time)) - { - p.videoData[i] = timeline::VideoData(); - } - } - for (size_t i = 0; i < p.timelinePlayers.size(); ++i) - { - if (p.timelinePlayers[i] == sender()) - { - p.videoData[i] = value; - } - } + p.videoData = value; p.doRender = true; update(); } @@ -419,7 +379,7 @@ namespace tl p.render->begin(viewportSize); p.render->setOCIOOptions(p.ocioOptions); p.render->setLUTOptions(p.lutOptions); - if (!p.videoData.empty()) + if (p.player && !p.videoData.empty()) { math::Matrix4x4f vm; vm = vm * math::translate(math::Vector3f(p.viewPos.x, p.viewPos.y, 0.F)); @@ -434,7 +394,7 @@ namespace tl p.render->setTransform(pm * vm); p.render->drawVideo( p.videoData, - timeline::getBoxes(p.compareOptions.mode, p.timelineSizes), + timeline::getBoxes(p.compareOptions.mode, p.player->sizes()), p.imageOptions, p.displayOptions, p.compareOptions, @@ -565,9 +525,9 @@ namespace tl setFrameView(false); break; case Private::MouseMode::Wipe: - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { - const auto& ioInfo = p.timelinePlayers[0]->ioInfo(); + const auto& ioInfo = p.player->ioInfo(); if (!ioInfo.video.empty()) { const auto& imageInfo = ioInfo.video[0]; @@ -602,11 +562,11 @@ namespace tl else if (event->modifiers() & Qt::ControlModifier) { event->accept(); - if (!p.timelinePlayers.empty() && p.timelinePlayers[0]) + if (p.player) { - const auto t = p.timelinePlayers[0]->currentTime(); + const auto t = p.player->currentTime(); const float delta = event->angleDelta().y() / 8.F / 15.F; - p.timelinePlayers[0]->seek(t + otime::RationalTime(delta, t.rate())); + p.player->seek(t + otime::RationalTime(delta, t.rate())); } } } @@ -656,7 +616,12 @@ namespace tl math::Size2i TimelineViewport::_renderSize() const { TLRENDER_P(); - return timeline::getRenderSize(p.compareOptions.mode, p.timelineSizes); + math::Size2i out; + if (p.player) + { + out = timeline::getRenderSize(p.compareOptions.mode, p.player->sizes()); + } + return out; } void TimelineViewport::_frameView() diff --git a/lib/tlQtWidget/TimelineViewport.h b/lib/tlQtWidget/TimelineViewport.h index 16c0a2a79..e0daa9b66 100644 --- a/lib/tlQtWidget/TimelineViewport.h +++ b/lib/tlQtWidget/TimelineViewport.h @@ -53,8 +53,8 @@ namespace tl //! Set the background options. void setBackgroundOptions(const timeline::BackgroundOptions&); - //! Set the timeline players. - void setTimelinePlayers(const QVector >&); + //! Set the timeline player. + void setPlayer(const QSharedPointer&); //! Get the view position. const math::Vector2i& viewPos() const; @@ -99,7 +99,7 @@ namespace tl private Q_SLOTS: void _playbackUpdate(timeline::Playback); - void _videoDataUpdate(const tl::timeline::VideoData&); + void _videoDataUpdate(const std::vector&); protected: void initializeGL() override; diff --git a/lib/tlTimeline/CMakeLists.txt b/lib/tlTimeline/CMakeLists.txt index 876978f47..45a3c78c8 100644 --- a/lib/tlTimeline/CMakeLists.txt +++ b/lib/tlTimeline/CMakeLists.txt @@ -21,7 +21,6 @@ set(HEADERS PlayerInline.h PlayerOptions.h PlayerOptionsInline.h - ReadCache.h RenderOptions.h RenderOptionsInline.h RenderUtil.h @@ -50,7 +49,6 @@ set(SOURCE Player.cpp PlayerOptions.cpp PlayerPrivate.cpp - ReadCache.cpp RenderUtil.cpp TimeUnits.cpp Timeline.cpp diff --git a/lib/tlTimeline/CompareOptions.cpp b/lib/tlTimeline/CompareOptions.cpp index e9a21ffe4..572f90a08 100644 --- a/lib/tlTimeline/CompareOptions.cpp +++ b/lib/tlTimeline/CompareOptions.cpp @@ -28,6 +28,9 @@ namespace tl "Tile"); TLRENDER_ENUM_SERIALIZE_IMPL(CompareMode); + TLRENDER_ENUM_IMPL(CompareTimeMode, "Relative", "Absolute"); + TLRENDER_ENUM_SERIALIZE_IMPL(CompareTimeMode); + std::vector getBoxes(CompareMode mode, const std::vector& sizes) { std::vector out; @@ -161,5 +164,32 @@ namespace tl } return out; } + + otime::RationalTime getCompareTime( + const otime::RationalTime& sourceTime, + const otime::TimeRange& sourceTimeRange, + const otime::TimeRange& compareTimeRange, + CompareTimeMode mode) + { + otime::RationalTime out; + switch (mode) + { + case CompareTimeMode::Relative: + { + const otime::RationalTime relativeTime = + sourceTime - sourceTimeRange.start_time(); + const otime::RationalTime relativeTimeRescaled = time::floor( + relativeTime.rescaled_to(compareTimeRange.duration().rate())); + out = compareTimeRange.start_time() + relativeTimeRescaled; + break; + } + case CompareTimeMode::Absolute: + out = time::floor(sourceTime.rescaled_to( + compareTimeRange.duration().rate())); + break; + default: break; + } + return out; + } } } diff --git a/lib/tlTimeline/CompareOptions.h b/lib/tlTimeline/CompareOptions.h index ba5efb2ef..d67e21136 100644 --- a/lib/tlTimeline/CompareOptions.h +++ b/lib/tlTimeline/CompareOptions.h @@ -6,6 +6,7 @@ #include #include +#include namespace tl { @@ -29,6 +30,18 @@ namespace tl TLRENDER_ENUM(CompareMode); TLRENDER_ENUM_SERIALIZE(CompareMode); + //! Comparison time mode. + enum class CompareTimeMode + { + Relative, + Absolute, + + Count, + First = Relative + }; + TLRENDER_ENUM(CompareTimeMode); + TLRENDER_ENUM_SERIALIZE(CompareTimeMode); + //! Comparison options. struct CompareOptions { @@ -46,6 +59,13 @@ namespace tl //! Get the render size for the given compare mode and sizes. math::Size2i getRenderSize(CompareMode, const std::vector&); + + //! Get a compare time. + otime::RationalTime getCompareTime( + const otime::RationalTime& sourceTime, + const otime::TimeRange& sourceTimeRange, + const otime::TimeRange& compareTimeRange, + CompareTimeMode); } } diff --git a/lib/tlTimeline/Player.cpp b/lib/tlTimeline/Player.cpp index e5d4ecfed..e00f46780 100644 --- a/lib/tlTimeline/Player.cpp +++ b/lib/tlTimeline/Player.cpp @@ -97,8 +97,12 @@ namespace tl playerOptions.currentTime : p.timeline->getTimeRange().start_time()); p.inOutRange = observer::Value::create(p.timeline->getTimeRange()); + p.compare = observer::List >::create(); + p.compareTime = observer::Value::create(CompareTimeMode::Relative); p.ioOptions = observer::Value::create(); - p.currentVideoData = observer::Value::create(); + p.videoLayer = observer::Value::create(0); + p.compareVideoLayers = observer::List::create(); + p.currentVideoData = observer::List::create(); p.volume = observer::Value::create(1.F); p.mute = observer::Value::create(false); p.audioOffset = observer::Value::create(0.0); @@ -193,34 +197,47 @@ namespace tl while (p.thread.running) { // Get mutex protected values. - Playback playback = Playback::Stop; - otime::RationalTime currentTime = time::invalidTime; - otime::TimeRange inOutRange = time::invalidTimeRange; - io::Options ioOptions; - double audioOffset = 0.0; bool clearRequests = false; bool clearCache = false; - CacheDirection cacheDirection = CacheDirection::Forward; - PlayerCacheOptions cacheOptions; { std::unique_lock lock(p.mutex.mutex); - playback = p.mutex.playback; - currentTime = p.mutex.currentTime; - inOutRange = p.mutex.inOutRange; - ioOptions = p.mutex.ioOptions; - audioOffset = p.mutex.audioOffset; + p.thread.playback = p.mutex.playback; + p.thread.currentTime = p.mutex.currentTime; + p.thread.inOutRange = p.mutex.inOutRange; + p.thread.compare = p.mutex.compare; + p.thread.compareTime = p.mutex.compareTime; + p.thread.ioOptions = p.mutex.ioOptions; + p.thread.videoLayer = p.mutex.videoLayer; + p.thread.compareVideoLayers = p.mutex.compareVideoLayers; + p.thread.audioOffset = p.mutex.audioOffset; clearRequests = p.mutex.clearRequests; p.mutex.clearRequests = false; clearCache = p.mutex.clearCache; p.mutex.clearCache = false; - cacheDirection = p.mutex.cacheDirection; - cacheOptions = p.mutex.cacheOptions; + p.thread.cacheDirection = p.mutex.cacheDirection; + p.thread.cacheOptions = p.mutex.cacheOptions; } // Clear requests. if (clearRequests) { - p.timeline->cancelRequests(); + std::vector > ids(1 + p.thread.compare.size()); + for (const auto& i : p.thread.videoDataRequests) + { + for (size_t j = 0; j < i.second.size() && j < ids.size(); ++j) + { + ids[j].push_back(i.second[j].id); + } + } + for (const auto& i : p.thread.audioDataRequests) + { + ids[0].push_back(i.second.id); + } + p.timeline->cancelRequests(ids[0]); + for (size_t i = 0; i < p.thread.compare.size(); ++i) + { + p.thread.compare[i]->cancelRequests(ids[i + 1]); + } p.thread.videoDataRequests.clear(); p.thread.audioDataRequests.clear(); } @@ -240,33 +257,27 @@ namespace tl } // Update the cache. - p.cacheUpdate( - currentTime, - inOutRange, - ioOptions, - audioOffset, - cacheDirection, - cacheOptions); + p.cacheUpdate(); // Update the current video data. const auto& timeRange = p.timeline->getTimeRange(); if (!p.ioInfo.video.empty()) { - const auto i = p.thread.videoDataCache.find(currentTime); + const auto i = p.thread.videoDataCache.find(p.thread.currentTime); if (i != p.thread.videoDataCache.end()) { std::unique_lock lock(p.mutex.mutex); p.mutex.currentVideoData = i->second; } - else if (playback != Playback::Stop) + else if (p.thread.playback != Playback::Stop) { { std::unique_lock lock(p.mutex.mutex); - p.mutex.playbackStartTime = currentTime; + p.mutex.playbackStartTime = p.thread.currentTime; p.mutex.playbackStartTimer = std::chrono::steady_clock::now(); - if (!timeRange.contains(currentTime)) + if (!timeRange.contains(p.thread.currentTime)) { - p.mutex.currentVideoData = VideoData(); + p.mutex.currentVideoData.clear(); } } p.resetAudioTime(); @@ -279,9 +290,9 @@ namespace tl else { std::unique_lock lock(p.mutex.mutex); - if (!timeRange.contains(currentTime)) + if (!timeRange.contains(p.thread.currentTime)) { - p.mutex.currentVideoData = VideoData(); + p.mutex.currentVideoData.clear(); } } } @@ -291,7 +302,7 @@ namespace tl { std::vector audioDataList; { - const int64_t seconds = currentTime.rescaled_to(1.0).value() - + const int64_t seconds = p.thread.currentTime.rescaled_to(1.0).value() - timeRange.start_time().rescaled_to(1.0).value(); std::unique_lock lock(p.audioMutex.mutex); for (int64_t s : { seconds - 1, seconds, seconds + 1 }) @@ -405,6 +416,22 @@ namespace tl return _p->ioInfo; } + std::vector Player::getSizes() const + { + TLRENDER_P(); + std::vector out; + out.push_back(!_p->ioInfo.video.empty() ? + _p->ioInfo.video.front().size : + image::Size()); + for (const auto& compare : p.compare->get()) + { + out.push_back(compare && !compare->getIOInfo().video.empty() ? + compare->getIOInfo().video.front().size : + image::Size()); + } + return out; + } + double Player::getDefaultSpeed() const { return _p->timeline->getTimeRange().duration().rate(); @@ -652,53 +679,6 @@ namespace tl timeAction(TimeAction::FrameNext); } - void Player::setExternalTime(const std::shared_ptr& value) - { - TLRENDER_P(); - if (value == p.externalTime.player) - return; - p.externalTime.player = value; - if (p.externalTime.player) - { - p.externalTime.timeRange = p.externalTime.player->getTimeRange(); - - auto weak = std::weak_ptr(shared_from_this()); - p.externalTime.playbackObserver = observer::ValueObserver::create( - p.externalTime.player->observePlayback(), - [weak](Playback value) - { - if (auto player = weak.lock()) - { - player->setPlayback(value); - } - }); - p.externalTime.currentTimeObserver = observer::ValueObserver::create( - p.externalTime.player->observeCurrentTime(), - [weak](const otime::RationalTime& value) - { - if (auto player = weak.lock()) - { - const otime::RationalTime externalTime = getExternalTime( - value, - player->_p->externalTime.timeRange, - player->getTimeRange(), - player->_p->playerOptions.externalTimeMode); - player->_p->currentTime->setIfChanged(externalTime); - } - }); - } - else - { - p.externalTime.timeRange = time::invalidTimeRange; - p.externalTime.playbackObserver.reset(); - p.externalTime.currentTimeObserver.reset(); - } - { - std::unique_lock lock(p.mutex.mutex); - p.mutex.externalTime = p.externalTime.player.get(); - } - } - otime::TimeRange Player::getInOutRange() const { return _p->inOutRange->get(); @@ -752,6 +732,50 @@ namespace tl p.timeline->getTimeRange().end_time_inclusive())); } + const std::vector >& Player::getCompare() const + { + return _p->compare->get(); + } + + std::shared_ptr > > Player::observeCompare() const + { + return _p->compare; + } + + void Player::setCompare(const std::vector >& value) + { + TLRENDER_P(); + if (p.compare->setIfChanged(value)) + { + std::unique_lock lock(p.mutex.mutex); + p.mutex.compare = value; + p.mutex.clearRequests = true; + p.mutex.clearCache = true; + } + } + + CompareTimeMode Player::getCompareTime() const + { + return _p->compareTime->get(); + } + + std::shared_ptr > Player::observeCompareTime() const + { + return _p->compareTime; + } + + void Player::setCompareTime(CompareTimeMode value) + { + TLRENDER_P(); + if (p.compareTime->setIfChanged(value)) + { + std::unique_lock lock(p.mutex.mutex); + p.mutex.compareTime = value; + p.mutex.clearRequests = true; + p.mutex.clearCache = true; + } + } + const io::Options& Player::getIOOptions() const { return _p->ioOptions->get(); @@ -774,12 +798,56 @@ namespace tl } } - const VideoData& Player::getCurrentVideo() const + int Player::getVideoLayer() const + { + return _p->videoLayer->get(); + } + + std::shared_ptr > Player::observeVideoLayer() const + { + return _p->videoLayer; + } + + void Player::setVideoLayer(int value) + { + TLRENDER_P(); + if (p.videoLayer->setIfChanged(value)) + { + std::unique_lock lock(p.mutex.mutex); + p.mutex.videoLayer = value; + p.mutex.clearRequests = true; + p.mutex.clearCache = true; + } + } + + const std::vector& Player::getCompareVideoLayers() const + { + return _p->compareVideoLayers->get(); + } + + std::shared_ptr > Player::observeCompareVideoLayers() const + { + return _p->compareVideoLayers; + } + + void Player::setCompareVideoLayers(const std::vector& value) + { + TLRENDER_P(); + if (p.compareVideoLayers->setIfChanged(value)) + { + std::unique_lock lock(p.mutex.mutex); + p.mutex.compareVideoLayers = value; + p.mutex.clearRequests = true; + p.mutex.clearCache = true; + } + } + + const std::vector& Player::getCurrentVideo() const { return _p->currentVideoData->get(); } - std::shared_ptr > Player::observeCurrentVideo() const + std::shared_ptr > Player::observeCurrentVideo() const { return _p->currentVideoData; } @@ -897,7 +965,7 @@ namespace tl // Calculate the current time. const auto& timeRange = p.timeline->getTimeRange(); const auto playback = p.playback->get(); - if (playback != Playback::Stop && !p.externalTime.player) + if (playback != Playback::Stop) { const double timelineSpeed = timeRange.duration().rate(); const double speed = p.speed->get(); @@ -940,7 +1008,7 @@ namespace tl } // Sync with the thread. - VideoData currentVideoData; + std::vector currentVideoData; std::vector currentAudioData; PlayerCacheInfo cacheInfo; { diff --git a/lib/tlTimeline/Player.h b/lib/tlTimeline/Player.h index bbf3118ab..9944ae318 100644 --- a/lib/tlTimeline/Player.h +++ b/lib/tlTimeline/Player.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include @@ -97,7 +98,7 @@ namespace tl static std::shared_ptr create( const std::shared_ptr&, const std::shared_ptr&, - const PlayerOptions& = PlayerOptions()); + const PlayerOptions & = PlayerOptions()); //! Get the context. const std::weak_ptr& getContext() const; @@ -127,6 +128,9 @@ namespace tl //! the first clip in the timeline. const io::Info& getIOInfo() const; + //! Get the timeline sizes. + std::vector getSizes() const; + ///@} //! \name Playback @@ -191,9 +195,6 @@ namespace tl //! Go to the next frame. void frameNext(); - //! Use the time from a separate timeline player. - void setExternalTime(const std::shared_ptr&); - ///@} //! \name In/Out Points @@ -222,6 +223,29 @@ namespace tl ///@} + //! \name Comparison + ///@{ + + //! Get the timelines for comparison. + const std::vector >& getCompare() const; + + //! Observe the timelines for comparison. + std::shared_ptr > > observeCompare() const; + + //! Set the timelines for comparison. + void setCompare(const std::vector >&); + + //! Get the comparison time mode. + CompareTimeMode getCompareTime() const; + + //! Observe the comparison time mode. + std::shared_ptr > observeCompareTime() const; + + //! Set the comparison time mode. + void setCompareTime(CompareTimeMode); + + ///@} + //! \name I/O ///@{ @@ -239,11 +263,29 @@ namespace tl //! \name Video ///@{ + //! Get the video layer. + int getVideoLayer() const; + + //! Observer the video layer. + std::shared_ptr > observeVideoLayer() const; + + //! Set the video layer. + void setVideoLayer(int); + + //! Get the comparison video layers. + const std::vector& getCompareVideoLayers() const; + + //! Observe the comparison video layers. + std::shared_ptr > observeCompareVideoLayers() const; + + //! Set the comparison video layers. + void setCompareVideoLayers(const std::vector&); + //! Get the current video data. - const VideoData& getCurrentVideo() const; + const std::vector& getCurrentVideo() const; //! Observe the current video data. - std::shared_ptr > observeCurrentVideo() const; + std::shared_ptr > observeCurrentVideo() const; ///@} diff --git a/lib/tlTimeline/PlayerOptions.cpp b/lib/tlTimeline/PlayerOptions.cpp index 7d2c2df98..b6f8f8061 100644 --- a/lib/tlTimeline/PlayerOptions.cpp +++ b/lib/tlTimeline/PlayerOptions.cpp @@ -13,35 +13,5 @@ namespace tl { TLRENDER_ENUM_IMPL(TimerMode, "System", "Audio"); TLRENDER_ENUM_SERIALIZE_IMPL(TimerMode); - - TLRENDER_ENUM_IMPL(ExternalTimeMode, "Relative", "Absolute"); - TLRENDER_ENUM_SERIALIZE_IMPL(ExternalTimeMode); - - otime::RationalTime getExternalTime( - const otime::RationalTime& sourceTime, - const otime::TimeRange& sourceTimeRange, - const otime::TimeRange& externalTimeRange, - ExternalTimeMode mode) - { - otime::RationalTime out; - switch (mode) - { - case ExternalTimeMode::Relative: - { - const otime::RationalTime relativeTime = - sourceTime - sourceTimeRange.start_time(); - const otime::RationalTime relativeTimeRescaled = time::floor( - relativeTime.rescaled_to(externalTimeRange.duration().rate())); - out = externalTimeRange.start_time() + relativeTimeRescaled; - break; - } - case ExternalTimeMode::Absolute: - out = time::floor(sourceTime.rescaled_to( - externalTimeRange.duration().rate())); - break; - default: break; - } - return out; - } } } diff --git a/lib/tlTimeline/PlayerOptions.h b/lib/tlTimeline/PlayerOptions.h index 75407ba07..6f9f4b076 100644 --- a/lib/tlTimeline/PlayerOptions.h +++ b/lib/tlTimeline/PlayerOptions.h @@ -22,18 +22,6 @@ namespace tl TLRENDER_ENUM(TimerMode); TLRENDER_ENUM_SERIALIZE(TimerMode); - //! External time mode. - enum class ExternalTimeMode - { - Relative, - Absolute, - - Count, - First = Relative - }; - TLRENDER_ENUM(ExternalTimeMode); - TLRENDER_ENUM_SERIALIZE(ExternalTimeMode); - //! Timeline player cache options. struct PlayerCacheOptions { @@ -68,19 +56,9 @@ namespace tl //! Current time. otime::RationalTime currentTime = time::invalidTime; - //! External time mode. - ExternalTimeMode externalTimeMode = ExternalTimeMode::Relative; - bool operator == (const PlayerOptions&) const; bool operator != (const PlayerOptions&) const; }; - - //! Get an external time from a source time. - otime::RationalTime getExternalTime( - const otime::RationalTime& sourceTime, - const otime::TimeRange& sourceTimeRange, - const otime::TimeRange& externalTimeRange, - ExternalTimeMode); } } diff --git a/lib/tlTimeline/PlayerOptionsInline.h b/lib/tlTimeline/PlayerOptionsInline.h index d52f12d5e..eb1688932 100644 --- a/lib/tlTimeline/PlayerOptionsInline.h +++ b/lib/tlTimeline/PlayerOptionsInline.h @@ -26,8 +26,7 @@ namespace tl audioBufferFrameCount == other.audioBufferFrameCount && muteTimeout == other.muteTimeout && sleepTimeout == other.sleepTimeout && - currentTime == other.currentTime && - externalTimeMode == other.externalTimeMode; + currentTime == other.currentTime; } inline bool PlayerOptions::operator != (const PlayerOptions& other) const diff --git a/lib/tlTimeline/PlayerPrivate.cpp b/lib/tlTimeline/PlayerPrivate.cpp index 33c2c003e..281c3c185 100644 --- a/lib/tlTimeline/PlayerPrivate.cpp +++ b/lib/tlTimeline/PlayerPrivate.cpp @@ -108,33 +108,32 @@ namespace tl return out; } - void Player::Private::cacheUpdate( - const otime::RationalTime& currentTime, - const otime::TimeRange& inOutRange, - const io::Options& ioOptions, - double audioOffset, - CacheDirection cacheDirection, - const PlayerCacheOptions& cacheOptions) + void Player::Private::cacheUpdate() { // Get the video ranges to be cached. const otime::TimeRange& timeRange = timeline->getTimeRange(); - otime::RationalTime readAheadRescaled = - time::floor(cacheOptions.readAhead.rescaled_to(timeRange.duration().rate())); - otime::RationalTime readBehindRescaled = - time::floor(cacheOptions.readBehind.rescaled_to(timeRange.duration().rate())); - + const otime::RationalTime readAheadDivided( + thread.cacheOptions.readAhead.value() / (1 + thread.compare.size()), + thread.cacheOptions.readAhead.rate()); + const otime::RationalTime readAheadRescaled = time::floor( + readAheadDivided.rescaled_to(timeRange.duration().rate())); + const otime::RationalTime readBehindDivided( + thread.cacheOptions.readBehind.value() / (1 + thread.compare.size()), + thread.cacheOptions.readBehind.rate()); + const otime::RationalTime readBehindRescaled = time::floor( + readBehindDivided.rescaled_to(timeRange.duration().rate())); otime::TimeRange videoRange = time::invalidTimeRange; - switch (cacheDirection) + switch (thread.cacheDirection) { case CacheDirection::Forward: videoRange = otime::TimeRange::range_from_start_end_time_inclusive( - currentTime - readBehindRescaled, - currentTime + readAheadRescaled); + thread.currentTime - readBehindRescaled, + thread.currentTime + readAheadRescaled); break; case CacheDirection::Reverse: videoRange = otime::TimeRange::range_from_start_end_time_inclusive( - currentTime - readAheadRescaled, - currentTime + readBehindRescaled); + thread.currentTime - readAheadRescaled, + thread.currentTime + readBehindRescaled); break; default: break; } @@ -142,20 +141,20 @@ namespace tl //std::cout << "video range: " << videoRange << std::endl; auto videoRanges = timeline::loopCache( videoRange, - inOutRange, - cacheDirection); + thread.inOutRange, + thread.cacheDirection); videoRanges.insert( videoRanges.begin(), otime::TimeRange( - currentTime, - otime::RationalTime(1.0, currentTime.rate()))); + thread.currentTime, + otime::RationalTime(1.0, thread.currentTime.rate()))); //for (const auto& i : videoRanges) //{ // std::cout << "video ranges: " << i << std::endl; //} // Get the audio ranges to be cached. - const otime::RationalTime audioOffsetTime = otime::RationalTime(audioOffset, 1.0). + const otime::RationalTime audioOffsetTime = otime::RationalTime(thread.audioOffset, 1.0). rescaled_to(timeRange.duration().rate()); //std::cout << "audio offset: " << audioOffsetTime << std::endl; const otime::RationalTime audioOffsetAhead = time::round( @@ -165,36 +164,36 @@ namespace tl //std::cout << "audio offset ahead: " << audioOffsetAhead << std::endl; //std::cout << "audio offset behind: " << audioOffsetBehind << std::endl; otime::TimeRange audioRange = time::invalidTimeRange; - switch (cacheDirection) + switch (thread.cacheDirection) { case CacheDirection::Forward: audioRange = otime::TimeRange::range_from_start_end_time_inclusive( - currentTime - readBehindRescaled - audioOffsetBehind, - currentTime + readAheadRescaled + audioOffsetAhead); + thread.currentTime - readBehindRescaled - audioOffsetBehind, + thread.currentTime + readAheadRescaled + audioOffsetAhead); break; case CacheDirection::Reverse: audioRange = otime::TimeRange::range_from_start_end_time_inclusive( - currentTime - readAheadRescaled - audioOffsetAhead, - currentTime + readBehindRescaled + audioOffsetBehind); + thread.currentTime - readAheadRescaled - audioOffsetAhead, + thread.currentTime + readBehindRescaled + audioOffsetBehind); break; default: break; } //std::cout << "audio range: " << audioRange << std::endl; const otime::TimeRange inOutAudioRange = otime::TimeRange::range_from_start_end_time_inclusive( - inOutRange.start_time() - audioOffsetBehind, - inOutRange.end_time_inclusive() + audioOffsetAhead). + thread.inOutRange.start_time() - audioOffsetBehind, + thread.inOutRange.end_time_inclusive() + audioOffsetAhead). clamped(timeRange); //std::cout << "in out audio range: " << inOutAudioRange << std::endl; const auto audioRanges = timeline::loopCache( audioRange, inOutAudioRange, - cacheDirection); + thread.cacheDirection); // Remove old video from the cache. auto videoCacheIt = thread.videoDataCache.begin(); while (videoCacheIt != thread.videoDataCache.end()) { - const otime::RationalTime t = videoCacheIt->second.time; + const otime::RationalTime t = videoCacheIt->first; const auto j = std::find_if( videoRanges.begin(), videoRanges.end(), @@ -247,14 +246,14 @@ namespace tl { for (const auto& range : videoRanges) { - switch (cacheDirection) + switch (thread.cacheDirection) { case CacheDirection::Forward: { - const auto start = range.start_time(); - const auto end = range.end_time_inclusive(); - const auto inc = otime::RationalTime(1.0, range.duration().rate()); - for (auto time = start; time <= end; time += inc) + const otime::RationalTime start = range.start_time(); + const otime::RationalTime end = range.end_time_inclusive(); + const otime::RationalTime inc = otime::RationalTime(1.0, range.duration().rate()); + for (otime::RationalTime time = start; time <= end; time += inc) { const auto i = thread.videoDataCache.find(time); if (i == thread.videoDataCache.end()) @@ -263,7 +262,24 @@ namespace tl if (j == thread.videoDataRequests.end()) { //std::cout << this << " video request: " << time << std::endl; - thread.videoDataRequests[time] = timeline->getVideo(time, ioOptions); + thread.videoDataRequests[time].clear(); + io::Options ioOptions2 = thread.ioOptions; + ioOptions2["Layer"] = string::Format("{0}").arg(thread.videoLayer); + thread.videoDataRequests[time].push_back(timeline->getVideo(time, ioOptions2)); + for (size_t i = 0; i < thread.compare.size(); ++i) + { + const otime::RationalTime time2 = timeline::getCompareTime( + time, + timeRange, + thread.compare[i]->getTimeRange(), + thread.compareTime); + ioOptions2["Layer"] = string::Format("{0}"). + arg(i < thread.compareVideoLayers.size() ? + thread.compareVideoLayers[i] : + thread.videoLayer); + thread.videoDataRequests[time].push_back( + thread.compare[i]->getVideo(time2, ioOptions2)); + } } } } @@ -283,7 +299,24 @@ namespace tl if (j == thread.videoDataRequests.end()) { //std::cout << this << " video request: " << time << std::endl; - thread.videoDataRequests[time] = timeline->getVideo(time, ioOptions); + thread.videoDataRequests[time].clear(); + io::Options ioOptions2 = thread.ioOptions; + ioOptions2["Layer"] = string::Format("{0}").arg(thread.videoLayer); + thread.videoDataRequests[time].push_back(timeline->getVideo(time, ioOptions2)); + for (size_t i = 0; i < thread.compare.size(); ++i) + { + const otime::RationalTime time2 = timeline::getCompareTime( + time, + timeRange, + thread.compare[i]->getTimeRange(), + thread.compareTime); + ioOptions2["Layer"] = string::Format("{0}"). + arg(i < thread.compareVideoLayers.size() ? + thread.compareVideoLayers[i] : + thread.videoLayer); + thread.videoDataRequests[time].push_back( + thread.compare[i]->getVideo(time2, ioOptions2)); + } } } } @@ -324,18 +357,18 @@ namespace tl } } } - switch (cacheDirection) + switch (thread.cacheDirection) { case CacheDirection::Forward: for (auto i = requests.begin(); i != requests.end(); ++i) { - thread.audioDataRequests[i->first] = timeline->getAudio(i->second, ioOptions); + thread.audioDataRequests[i->first] = timeline->getAudio(i->second, thread.ioOptions); } break; case CacheDirection::Reverse: for (auto i = requests.rbegin(); i != requests.rend(); ++i) { - thread.audioDataRequests[i->first] = timeline->getAudio(i->second, ioOptions); + thread.audioDataRequests[i->first] = timeline->getAudio(i->second, thread.ioOptions); } break; default: break; @@ -356,35 +389,53 @@ namespace tl auto videoDataRequestsIt = thread.videoDataRequests.begin(); while (videoDataRequestsIt != thread.videoDataRequests.end()) { - if (videoDataRequestsIt->second.valid() && - videoDataRequestsIt->second.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + bool ready = true; + for (auto videoDataRequestIt = videoDataRequestsIt->second.begin(); + videoDataRequestIt != videoDataRequestsIt->second.end(); + ++videoDataRequestIt) + { + ready &= videoDataRequestIt->future.valid() && + videoDataRequestIt->future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + } + if (ready) { - auto data = videoDataRequestsIt->second.get(); - data.time = videoDataRequestsIt->first; - thread.videoDataCache[data.time] = data; + const otime::RationalTime time = videoDataRequestsIt->first; + thread.videoDataCache[time].clear(); + for (auto videoDataRequestIt = videoDataRequestsIt->second.begin(); + videoDataRequestIt != videoDataRequestsIt->second.end(); + ++videoDataRequestIt) + { + auto data = videoDataRequestIt->future.get(); + data.time = time; + thread.videoDataCache[data.time].push_back(data); + } videoDataRequestsIt = thread.videoDataRequests.erase(videoDataRequestsIt); - continue; } - ++videoDataRequestsIt; + else + { + ++videoDataRequestsIt; + } } // Check for finished audio. auto audioDataRequestsIt = thread.audioDataRequests.begin(); while (audioDataRequestsIt != thread.audioDataRequests.end()) { - if (audioDataRequestsIt->second.valid() && - audioDataRequestsIt->second.wait_for(std::chrono::seconds(0)) == std::future_status::ready) + if (audioDataRequestsIt->second.future.valid() && + audioDataRequestsIt->second.future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - auto audioData = audioDataRequestsIt->second.get(); + auto audioData = audioDataRequestsIt->second.future.get(); audioData.seconds = audioDataRequestsIt->first; { std::unique_lock lock(audioMutex.mutex); audioMutex.audioDataCache[audioDataRequestsIt->first] = audioData; } audioDataRequestsIt = thread.audioDataRequests.erase(audioDataRequestsIt); - continue; } - ++audioDataRequestsIt; + else + { + ++audioDataRequestsIt; + } } // Update cached frames. @@ -396,11 +447,11 @@ namespace tl std::vector cachedVideoFrames; for (const auto& i : thread.videoDataCache) { - cachedVideoFrames.push_back(i.second.time); + cachedVideoFrames.push_back(i.first); } const float cachedVideoPercentage = cachedVideoFrames.size() / - static_cast(cacheOptions.readAhead.rescaled_to(timeRange.duration().rate()).value() + - cacheOptions.readBehind.rescaled_to(timeRange.duration().rate()).value()) * + static_cast(readAheadDivided.rescaled_to(timeRange.duration().rate()).value() + + readBehindDivided.rescaled_to(timeRange.duration().rate()).value()) * 100.F; std::vector cachedAudioFrames; { @@ -467,13 +518,11 @@ namespace tl Playback playback = Playback::Stop; otime::RationalTime playbackStartTime = time::invalidTime; double audioOffset = 0.0; - bool externalTime = false; { std::unique_lock lock(p->mutex.mutex); playback = p->mutex.playback; playbackStartTime = p->mutex.playbackStartTime; audioOffset = p->mutex.audioOffset; - externalTime = p->mutex.externalTime; } double speed = 0.0; double defaultSpeed = 0.0; @@ -754,7 +803,11 @@ namespace tl // Send audio data to RtAudio. const auto now = std::chrono::steady_clock::now(); +<<<<<<< HEAD if (!externalTime && +======= + if (speed == p->timeline->getTimeRange().duration().rate() && +>>>>>>> 7ddaed7355b0ffa73b983c27c04a326dcce3aac2 !mute && now >= muteTimeout && nFrames <= getSampleCount(p->audioThread.buffer)) diff --git a/lib/tlTimeline/PlayerPrivate.h b/lib/tlTimeline/PlayerPrivate.h index 2645cf9aa..e4a6b544f 100644 --- a/lib/tlTimeline/PlayerPrivate.h +++ b/lib/tlTimeline/PlayerPrivate.h @@ -27,13 +27,7 @@ namespace tl { otime::RationalTime loopPlayback(const otime::RationalTime&); - void cacheUpdate( - const otime::RationalTime& currentTime, - const otime::TimeRange& inOutRange, - const io::Options& ioOptions, - double audioOffset, - CacheDirection, - const PlayerCacheOptions&); + void cacheUpdate(); void resetAudioTime(); #if defined(TLRENDER_AUDIO) @@ -60,8 +54,12 @@ namespace tl std::shared_ptr > loop; std::shared_ptr > currentTime; std::shared_ptr > inOutRange; + std::shared_ptr > > compare; + std::shared_ptr > compareTime; std::shared_ptr > ioOptions; - std::shared_ptr > currentVideoData; + std::shared_ptr > videoLayer; + std::shared_ptr > compareVideoLayers; + std::shared_ptr > currentVideoData; std::shared_ptr > volume; std::shared_ptr > mute; std::shared_ptr > audioOffset; @@ -70,25 +68,19 @@ namespace tl std::shared_ptr > cacheInfo; std::shared_ptr > timelineObserver; - struct ExternalTime - { - std::shared_ptr player; - otime::TimeRange timeRange = time::invalidTimeRange; - std::shared_ptr > playbackObserver; - std::shared_ptr > currentTimeObserver; - }; - ExternalTime externalTime; - struct Mutex { Playback playback = Playback::Stop; otime::RationalTime playbackStartTime = time::invalidTime; std::chrono::steady_clock::time_point playbackStartTimer; otime::RationalTime currentTime = time::invalidTime; - bool externalTime = false; otime::TimeRange inOutRange = time::invalidTimeRange; + std::vector > compare; + CompareTimeMode compareTime = CompareTimeMode::Relative; io::Options ioOptions; - VideoData currentVideoData; + int videoLayer = 0; + std::vector compareVideoLayers; + std::vector currentVideoData; double audioOffset = 0.0; std::vector currentAudioData; bool clearRequests = false; @@ -115,12 +107,24 @@ namespace tl struct Thread { - std::map > videoDataRequests; - std::map videoDataCache; + Playback playback = Playback::Stop; + otime::RationalTime currentTime = time::invalidTime; + otime::TimeRange inOutRange = time::invalidTimeRange; + std::vector > compare; + CompareTimeMode compareTime = CompareTimeMode::Relative; + io::Options ioOptions; + int videoLayer = 0; + std::vector compareVideoLayers; + double audioOffset = 0.0; + CacheDirection cacheDirection = CacheDirection::Forward; + PlayerCacheOptions cacheOptions; + + std::map > videoDataRequests; + std::map > videoDataCache; #if defined(TLRENDER_AUDIO) std::unique_ptr rtAudio; #endif // TLRENDER_AUDIO - std::map > audioDataRequests; + std::map audioDataRequests; std::chrono::steady_clock::time_point cacheTimer; std::chrono::steady_clock::time_point logTimer; std::atomic running; diff --git a/lib/tlTimeline/ReadCache.cpp b/lib/tlTimeline/ReadCache.cpp deleted file mode 100644 index e919a57bc..000000000 --- a/lib/tlTimeline/ReadCache.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2021-2024 Darby Johnston -// All rights reserved. - -#include - -#include -#include -#include - -namespace tl -{ - namespace timeline - { - namespace - { - std::string getKey(const file::Path& path) - { - std::vector out; - out.push_back(path.get()); - out.push_back(path.getNumber()); - return string::join(out, ';'); - } - } - - struct ReadCache::Private - { - memory::LRUCache cache; - }; - - void ReadCache::_init() - { - TLRENDER_P(); - } - - ReadCache::ReadCache() : - _p(new Private) - {} - - ReadCache::~ReadCache() - {} - - std::shared_ptr ReadCache::create() - { - auto out = std::shared_ptr(new ReadCache); - out->_init(); - return out; - } - - void ReadCache::add(const ReadCacheItem& read) - { - const file::Path& path = read.read->getPath(); - const std::string key = getKey(path); - _p->cache.add(key, read); - } - - bool ReadCache::get(const file::Path& path, ReadCacheItem& out) - { - return _p->cache.get(getKey(path), out); - } - - void ReadCache::setMax(size_t value) - { - _p->cache.setMax(value); - } - - size_t ReadCache::getCount() const - { - return _p->cache.getCount(); - } - - void ReadCache::cancelRequests() - { - for (auto& i : _p->cache.getValues()) - { - i.read->cancelRequests(); - } - } - } -} diff --git a/lib/tlTimeline/ReadCache.h b/lib/tlTimeline/ReadCache.h deleted file mode 100644 index 67f0eeee7..000000000 --- a/lib/tlTimeline/ReadCache.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2021-2024 Darby Johnston -// All rights reserved. - -#pragma once - -#include - -namespace tl -{ - namespace timeline - { - //! I/O read cache item. - struct ReadCacheItem - { - std::shared_ptr read; - io::Info ioInfo; - }; - - //! I/O read cache. - class ReadCache - { - TLRENDER_NON_COPYABLE(ReadCache); - - protected: - void _init(); - - ReadCache(); - - public: - ~ReadCache(); - - //! Create a new read cache. - static std::shared_ptr create(); - - //! Add an item to the cache. - void add(const ReadCacheItem&); - - //! Get an item from the cache. - bool get(const file::Path&, ReadCacheItem&); - - //! Set the maximum number of read objects. - void setMax(size_t); - - //! Get the number of read objects. - size_t getCount() const; - - //! Cancel requests. - void cancelRequests(); - - private: - TLRENDER_PRIVATE(); - }; - } -} diff --git a/lib/tlTimeline/Timeline.cpp b/lib/tlTimeline/Timeline.cpp index 83cfb0bbb..79bccea17 100644 --- a/lib/tlTimeline/Timeline.cpp +++ b/lib/tlTimeline/Timeline.cpp @@ -17,6 +17,11 @@ namespace tl { namespace timeline { + namespace + { + const size_t readCacheMax = 10; + } + TLRENDER_ENUM_IMPL( FileSequenceAudio, "None", @@ -45,8 +50,7 @@ namespace tl void Timeline::_init( const otio::SerializableObject::Retainer& otioTimeline, const std::shared_ptr& context, - const Options& options, - const std::shared_ptr& readCache) + const Options& options) { TLRENDER_P(); @@ -80,8 +84,8 @@ namespace tl } p.context = context; - p.options = options; p.otioTimeline = otioTimeline; + p.timelineChanges = observer::Value::create(false); const auto i = otioTimeline->metadata().find("tlRender"); if (i != otioTimeline->metadata().end()) { @@ -102,9 +106,8 @@ namespace tl catch (const std::exception&) {} } - p.timelineChanges = observer::Value::create(false); - p.readCache = readCache ? readCache : ReadCache::create(); - p.readCache->setMax(16); + p.options = options; + p.readCache.setMax(readCacheMax); // Get information about the timeline. p.timeRange = timeline::getTimeRange(p.otioTimeline.value); @@ -250,15 +253,19 @@ namespace tl return _p->ioInfo; } - std::future Timeline::getVideo( + VideoRequest Timeline::getVideo( const otime::RationalTime& time, const io::Options& options) { TLRENDER_P(); + (p.requestId)++; auto request = std::make_shared(); + request->id = p.requestId; request->time = time; request->options = options; - auto future = request->promise.get_future(); + VideoRequest out; + out.id = p.requestId; + out.future = request->promise.get_future(); bool valid = false; { std::unique_lock lock(p.mutex.mutex); @@ -276,18 +283,22 @@ namespace tl { request->promise.set_value(VideoData()); } - return future; + return out; } - std::future Timeline::getAudio( + AudioRequest Timeline::getAudio( double seconds, const io::Options& options) { TLRENDER_P(); + (p.requestId)++; auto request = std::make_shared(); + request->id = p.requestId; request->seconds = seconds; request->options = options; - auto future = request->promise.get_future(); + AudioRequest out; + out.id = p.requestId; + out.future = request->promise.get_future(); bool valid = false; { std::unique_lock lock(p.mutex.mutex); @@ -305,28 +316,43 @@ namespace tl { request->promise.set_value(AudioData()); } - return future; + return out; } - void Timeline::cancelRequests() + void Timeline::cancelRequests(const std::vector& ids) { TLRENDER_P(); - std::list > videoRequests; - std::list > audioRequests; - { - std::unique_lock lock(p.mutex.mutex); - videoRequests = std::move(p.mutex.videoRequests); - audioRequests = std::move(p.mutex.audioRequests); - } - for (auto& request : videoRequests) + std::unique_lock lock(p.mutex.mutex); { - request->promise.set_value(VideoData()); + auto i = p.mutex.videoRequests.begin(); + while (i != p.mutex.videoRequests.end()) + { + const auto j = std::find(ids.begin(), ids.end(), (*i)->id); + if (j != ids.end()) + { + i = p.mutex.videoRequests.erase(i); + } + else + { + ++i; + } + } } - for (auto& request : audioRequests) { - request->promise.set_value(AudioData()); + auto i = p.mutex.audioRequests.begin(); + while (i != p.mutex.audioRequests.end()) + { + const auto j = std::find(ids.begin(), ids.end(), (*i)->id); + if (j != ids.end()) + { + i = p.mutex.audioRequests.erase(i); + } + else + { + ++i; + } + } } - p.readCache->cancelRequests(); } void Timeline::tick() @@ -340,7 +366,6 @@ namespace tl } if (otioTimelineChanged) { - cancelRequests(); p.timelineChanges->setAlways(true); } } diff --git a/lib/tlTimeline/Timeline.h b/lib/tlTimeline/Timeline.h index f1f496f49..3dc977f46 100644 --- a/lib/tlTimeline/Timeline.h +++ b/lib/tlTimeline/Timeline.h @@ -5,14 +5,16 @@ #pragma once #include -#include #include #include +#include #include #include +#include + namespace tl { //! Timelines. @@ -56,8 +58,7 @@ namespace tl otio::SerializableObject::Retainer create( const file::Path&, const std::shared_ptr&, - const Options& = Options(), - const std::shared_ptr& = nullptr); + const Options& = Options()); //! Create a new timeline from a path and audio path. The file name //! can point to an .otio file, .otioz file, movie file, or image @@ -66,8 +67,21 @@ namespace tl const file::Path& path, const file::Path& audioPath, const std::shared_ptr&, - const Options& = Options(), - const std::shared_ptr& = nullptr); + const Options& = Options()); + + //! Video request. + struct VideoRequest + { + uint64_t id = 0; + std::future future; + }; + + //! Audio request. + struct AudioRequest + { + uint64_t id = 0; + std::future future; + }; //! Timeline. class Timeline : public std::enable_shared_from_this @@ -78,8 +92,7 @@ namespace tl void _init( const otio::SerializableObject::Retainer&, const std::shared_ptr&, - const Options&, - const std::shared_ptr&); + const Options&); Timeline(); @@ -90,8 +103,7 @@ namespace tl static std::shared_ptr create( const otio::SerializableObject::Retainer&, const std::shared_ptr&, - const Options& = Options(), - const std::shared_ptr& = nullptr); + const Options& = Options()); //! Create a new timeline from a file name. The file name can point //! to an .otio file, movie file, or image sequence. @@ -161,17 +173,17 @@ namespace tl ///@{ //! Get video data. - std::future getVideo( + VideoRequest getVideo( const otime::RationalTime&, const io::Options& = io::Options()); //! Get audio data. - std::future getAudio( + AudioRequest getAudio( double seconds, const io::Options& = io::Options()); //! Cancel requests. - void cancelRequests(); + void cancelRequests(const std::vector&); ///@} diff --git a/lib/tlTimeline/TimelineCreate.cpp b/lib/tlTimeline/TimelineCreate.cpp index a0d09d225..039506385 100644 --- a/lib/tlTimeline/TimelineCreate.cpp +++ b/lib/tlTimeline/TimelineCreate.cpp @@ -351,18 +351,16 @@ namespace tl otio::SerializableObject::Retainer create( const file::Path& path, const std::shared_ptr& context, - const Options& options, - const std::shared_ptr& readCache) + const Options& options) { - return create(path, file::Path(), context, options, readCache); + return create(path, file::Path(), context, options); } otio::SerializableObject::Retainer create( const file::Path& inputPath, const file::Path& inputAudioPath, const std::shared_ptr& context, - const Options& options, - const std::shared_ptr& readCache) + const Options& options) { otio::SerializableObject::Retainer out; std::string error; @@ -415,13 +413,6 @@ namespace tl if (auto read = ioSystem->read(path, options.ioOptions)) { const auto info = read->getInfo().get(); - if (readCache) - { - ReadCacheItem item; - item.read = read; - item.ioInfo = info; - readCache->add(item); - } otime::RationalTime startTime = time::invalidTime; otio::Track* videoTrack = nullptr; @@ -469,13 +460,6 @@ namespace tl if (auto audioRead = ioSystem->read(audioPath, options.ioOptions)) { const auto audioInfo = audioRead->getInfo().get(); - if (readCache) - { - ReadCacheItem item; - item.read = audioRead; - item.ioInfo = audioInfo; - readCache->add(item); - } auto audioClip = new otio::Clip; audioClip->set_source_range(audioInfo.audioTime); @@ -586,11 +570,10 @@ namespace tl std::shared_ptr Timeline::create( const otio::SerializableObject::Retainer& timeline, const std::shared_ptr& context, - const Options& options, - const std::shared_ptr& readCache) + const Options& options) { auto out = std::shared_ptr(new Timeline); - out->_init(timeline, context, options, readCache); + out->_init(timeline, context, options); return out; } @@ -600,13 +583,11 @@ namespace tl const Options& options) { auto out = std::shared_ptr(new Timeline); - auto readCache = ReadCache::create(); auto otioTimeline = timeline::create( file::Path(fileName, options.pathOptions), context, - options, - readCache); - out->_init(otioTimeline, context, options, readCache); + options); + out->_init(otioTimeline, context, options); return out; } @@ -616,9 +597,8 @@ namespace tl const Options& options) { auto out = std::shared_ptr(new Timeline); - auto readCache = ReadCache::create(); - auto otioTimeline = timeline::create(path, context, options, readCache); - out->_init(otioTimeline, context, options, readCache); + auto otioTimeline = timeline::create(path, context, options); + out->_init(otioTimeline, context, options); return out; } @@ -629,14 +609,12 @@ namespace tl const Options& options) { auto out = std::shared_ptr(new Timeline); - auto readCache = ReadCache::create(); auto otioTimeline = timeline::create( file::Path(fileName, options.pathOptions), file::Path(audioFileName, options.pathOptions), context, - options, - readCache); - out->_init(otioTimeline, context, options, readCache); + options); + out->_init(otioTimeline, context, options); return out; } @@ -647,14 +625,12 @@ namespace tl const Options& options) { auto out = std::shared_ptr(new Timeline); - auto readCache = ReadCache::create(); auto otioTimeline = timeline::create( path, audioPath, context, - options, - readCache); - out->_init(otioTimeline, context, options, readCache); + options); + out->_init(otioTimeline, context, options); return out; } } diff --git a/lib/tlTimeline/TimelinePrivate.cpp b/lib/tlTimeline/TimelinePrivate.cpp index 3ec7c2f17..7874f3d43 100644 --- a/lib/tlTimeline/TimelinePrivate.cpp +++ b/lib/tlTimeline/TimelinePrivate.cpp @@ -24,12 +24,12 @@ namespace tl if (auto context = this->context.lock()) { // The first video clip defines the video information for the timeline. - auto item = getRead(clip, options.ioOptions); - if (item.read) + if (auto read = getRead(clip, options.ioOptions)) { - this->ioInfo.video = item.ioInfo.video; - this->ioInfo.videoTime = item.ioInfo.videoTime; - this->ioInfo.tags.insert(item.ioInfo.tags.begin(), item.ioInfo.tags.end()); + const io::Info& ioInfo = read->getInfo().get(); + this->ioInfo.video = ioInfo.video; + this->ioInfo.videoTime = ioInfo.videoTime; + this->ioInfo.tags.insert(ioInfo.tags.begin(), ioInfo.tags.end()); return true; } } @@ -54,12 +54,12 @@ namespace tl if (auto context = this->context.lock()) { // The first audio clip defines the audio information for the timeline. - auto item = getRead(clip, options.ioOptions); - if (item.read) + if (auto read = getRead(clip, options.ioOptions)) { - this->ioInfo.audio = item.ioInfo.audio; - this->ioInfo.audioTime = item.ioInfo.audioTime; - this->ioInfo.tags.insert(item.ioInfo.tags.begin(), item.ioInfo.tags.end()); + const io::Info& ioInfo = read->getInfo().get(); + this->ioInfo.audio = ioInfo.audio; + this->ioInfo.audioTime = ioInfo.audioTime; + this->ioInfo.tags.insert(ioInfo.tags.begin(), ioInfo.tags.end()); return true; } } @@ -108,16 +108,14 @@ namespace tl "\n" " Path: {0}\n" " Video requests: {1}, {2} in-progress, {3} max\n" - " Audio requests: {4}, {5} in-progress, {6} max\n" - " Read cache: {7}"). + " Audio requests: {4}, {5} in-progress, {6} max"). arg(path.get()). arg(videoRequestsSize). arg(thread.videoRequestsInProgress.size()). arg(options.videoRequestCount). arg(audioRequestsSize). arg(thread.audioRequestsInProgress.size()). - arg(options.audioRequestCount). - arg(readCache->getCount())); + arg(options.audioRequestCount)); } } @@ -494,23 +492,31 @@ namespace tl } } - ReadCacheItem Timeline::Private::getRead( + namespace + { + std::string getKey(const file::Path& path) + { + std::vector out; + out.push_back(path.get()); + out.push_back(path.getNumber()); + return string::join(out, ';'); + } + } + + std::shared_ptr Timeline::Private::getRead( const otio::Clip* clip, const io::Options& ioOptions) { - ReadCacheItem out; + std::shared_ptr out; const auto path = timeline::getPath( clip->media_reference(), this->path.getDirectory(), options.pathOptions); - if (!readCache->get(path, out)) + const std::string key = getKey(path); + if (!readCache.get(key, out)) { if (auto context = this->context.lock()) { - const auto path = timeline::getPath( - clip->media_reference(), - this->path.getDirectory(), - options.pathOptions); const auto memoryRead = getMemoryRead(clip->media_reference()); io::Options options = ioOptions; options["SequenceIO/DefaultSpeed"] = string::Format("{0}").arg(timeRange.duration().rate()); @@ -527,29 +533,8 @@ namespace tl } options["FFmpeg/StartTime"] = string::Format("{0}").arg(startTime); const auto ioSystem = context->getSystem(); - out.read = ioSystem->read(path, memoryRead, options); - if (out.read) - { - out.ioInfo = out.read->getInfo().get(); - readCache->add(out); - context->log( - string::Format("tl::timeline::Timeline {0}").arg(this), - string::Format( - "\n" - " Read: {0}\n" - " Video: {1} {2}\n" - " Video time: {3}\n" - " Audio: {4} {5} {6}\n" - " Audio time: {7}"). - arg(path.get()). - arg(!out.ioInfo.video.empty() ? out.ioInfo.video[0].size : image::Size()). - arg(!out.ioInfo.video.empty() ? out.ioInfo.video[0].pixelType : image::PixelType::None). - arg(out.ioInfo.videoTime). - arg(out.ioInfo.audio.channelCount). - arg(out.ioInfo.audio.dataType). - arg(out.ioInfo.audio.sampleRate). - arg(out.ioInfo.audioTime)); - } + out = ioSystem->read(path, memoryRead, options); + readCache.add(key, out); } } return out; @@ -563,16 +548,17 @@ namespace tl std::future out; io::Options optionsMerged = io::merge(options, this->options.ioOptions); optionsMerged["USD/cameraName"] = clip->name(); - ReadCacheItem item = getRead(clip, optionsMerged); + auto read = getRead(clip, optionsMerged); const auto timeRangeOpt = clip->trimmed_range_in_parent(); - if (item.read && timeRangeOpt.has_value()) + if (read && timeRangeOpt.has_value()) { + const io::Info& ioInfo = read->getInfo().get(); const auto mediaTime = timeline::toVideoMediaTime( time, timeRangeOpt.value(), clip->trimmed_range(), - item.ioInfo.videoTime.duration().rate()); - out = item.read->readVideo(mediaTime, optionsMerged); + ioInfo.videoTime.duration().rate()); + out = read->readVideo(mediaTime, optionsMerged); } return out; } @@ -584,16 +570,17 @@ namespace tl { std::future out; io::Options optionsMerged = io::merge(options, this->options.ioOptions); - ReadCacheItem item = getRead(clip, optionsMerged); + auto read = getRead(clip, optionsMerged); const auto timeRangeOpt = clip->trimmed_range_in_parent(); - if (item.read && timeRangeOpt.has_value()) + if (read && timeRangeOpt.has_value()) { + const io::Info& ioInfo = read->getInfo().get(); const auto mediaRange = timeline::toAudioMediaTime( timeRange, timeRangeOpt.value(), clip->trimmed_range(), - item.ioInfo.audio.sampleRate); - out = item.read->readAudio(mediaRange, optionsMerged); + ioInfo.audio.sampleRate); + out = read->readAudio(mediaRange, optionsMerged); } return out; } diff --git a/lib/tlTimeline/TimelinePrivate.h b/lib/tlTimeline/TimelinePrivate.h index 031c1100b..f8e0d1662 100644 --- a/lib/tlTimeline/TimelinePrivate.h +++ b/lib/tlTimeline/TimelinePrivate.h @@ -6,6 +6,10 @@ #include +#include + +#include + #include #include @@ -28,7 +32,7 @@ namespace tl void requests(); void finishRequests(); - ReadCacheItem getRead( + std::shared_ptr getRead( const otio::Clip*, const io::Options&); std::future readVideo( @@ -51,9 +55,10 @@ namespace tl file::Path path; file::Path audioPath; Options options; - std::shared_ptr readCache; + memory::LRUCache > readCache; otime::TimeRange timeRange = time::invalidTimeRange; io::Info ioInfo; + uint64_t requestId = 0; struct VideoLayerData { @@ -70,6 +75,7 @@ namespace tl VideoRequest() {}; VideoRequest(VideoRequest&&) = default; + uint64_t id = 0; otime::RationalTime time = time::invalidTime; io::Options options; std::promise promise; @@ -94,6 +100,7 @@ namespace tl AudioRequest() {}; AudioRequest(AudioRequest&&) = default; + uint64_t id = 0; double seconds = -1.0; io::Options options; std::promise promise; diff --git a/lib/tlTimeline/Util.h b/lib/tlTimeline/Util.h index a5901ce5f..f7d874105 100644 --- a/lib/tlTimeline/Util.h +++ b/lib/tlTimeline/Util.h @@ -6,6 +6,8 @@ #include +#include + #include #include diff --git a/lib/tlTimelineUI/AudioClipItem.cpp b/lib/tlTimelineUI/AudioClipItem.cpp index b98db0d6d..2b8d785e8 100644 --- a/lib/tlTimelineUI/AudioClipItem.cpp +++ b/lib/tlTimelineUI/AudioClipItem.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include #include diff --git a/lib/tlTimelineUI/TimelineItem.cpp b/lib/tlTimelineUI/TimelineItem.cpp index eb729ba0c..41f14fd95 100644 --- a/lib/tlTimelineUI/TimelineItem.cpp +++ b/lib/tlTimelineUI/TimelineItem.cpp @@ -738,132 +738,134 @@ namespace tl const ui::DrawEvent& event) { TLRENDER_P(); + if (_timeRange != time::invalidTimeRange) + { + const math::Box2i& g = _geometry; - const math::Box2i& g = _geometry; - - const std::string labelMax = _data->timeUnitsModel->getLabel(_timeRange.duration()); - const math::Size2i labelMaxSize = event.fontSystem->getSize(labelMax, p.size.fontInfo); - const int distanceMin = p.size.border + p.size.margin + labelMaxSize.w; + const std::string labelMax = _data->timeUnitsModel->getLabel(_timeRange.duration()); + const math::Size2i labelMaxSize = event.fontSystem->getSize(labelMax, p.size.fontInfo); + const int distanceMin = p.size.border + p.size.margin + labelMaxSize.w; - const int w = _sizeHint.w; - const float duration = _timeRange.duration().rescaled_to(1.0).value(); - const int frameTick = 1.0 / _timeRange.duration().value() * w; - if (frameTick >= p.size.handle) - { - geom::TriangleMesh2 mesh; - size_t i = 1; - for (double t = 0.0; t < duration; t += 1.0 / _timeRange.duration().rate()) + const int w = _sizeHint.w; + const float duration = _timeRange.duration().rescaled_to(1.0).value(); + const int frameTick = 1.0 / _timeRange.duration().value() * w; + if (frameTick >= p.size.handle) { - const math::Box2i box( - g.min.x + - t / duration * w, - p.size.scrollPos.y + - g.min.y + - p.size.margin + - p.size.fontMetrics.lineHeight, - p.size.border, - p.size.margin + - p.size.border * 4); - if (box.intersects(drawRect)) + geom::TriangleMesh2 mesh; + size_t i = 1; + for (double t = 0.0; t < duration; t += 1.0 / _timeRange.duration().rate()) { - mesh.v.push_back(math::Vector2f(box.min.x, box.min.y)); - mesh.v.push_back(math::Vector2f(box.max.x + 1, box.min.y)); - mesh.v.push_back(math::Vector2f(box.max.x + 1, box.max.y + 1)); - mesh.v.push_back(math::Vector2f(box.min.x, box.max.y + 1)); - mesh.triangles.push_back({ i + 0, i + 1, i + 2 }); - mesh.triangles.push_back({ i + 2, i + 3, i + 0 }); - i += 4; + const math::Box2i box( + g.min.x + + t / duration * w, + p.size.scrollPos.y + + g.min.y + + p.size.margin + + p.size.fontMetrics.lineHeight, + p.size.border, + p.size.margin + + p.size.border * 4); + if (box.intersects(drawRect)) + { + mesh.v.push_back(math::Vector2f(box.min.x, box.min.y)); + mesh.v.push_back(math::Vector2f(box.max.x + 1, box.min.y)); + mesh.v.push_back(math::Vector2f(box.max.x + 1, box.max.y + 1)); + mesh.v.push_back(math::Vector2f(box.min.x, box.max.y + 1)); + mesh.triangles.push_back({ i + 0, i + 1, i + 2 }); + mesh.triangles.push_back({ i + 2, i + 3, i + 0 }); + i += 4; + } + } + if (!mesh.v.empty()) + { + event.render->drawMesh( + mesh, + math::Vector2i(), + event.style->getColorRole(ui::ColorRole::Button)); } } - if (!mesh.v.empty()) + + const int secondsTick = 1.0 / duration * w; + const int minutesTick = 60.0 / duration * w; + const int hoursTick = 3600.0 / duration * w; + double seconds = 0; + int tick = 0; + if (secondsTick >= distanceMin) { - event.render->drawMesh( - mesh, - math::Vector2i(), - event.style->getColorRole(ui::ColorRole::Button)); + seconds = 1.0; + tick = secondsTick; } - } - - const int secondsTick = 1.0 / duration * w; - const int minutesTick = 60.0 / duration * w; - const int hoursTick = 3600.0 / duration * w; - double seconds = 0; - int tick = 0; - if (secondsTick >= distanceMin) - { - seconds = 1.0; - tick = secondsTick; - } - else if (minutesTick >= distanceMin) - { - seconds = 60.0; - tick = minutesTick; - } - else if (hoursTick >= distanceMin) - { - seconds = 3600.0; - tick = hoursTick; - } - if (seconds > 0.0 && tick > 0) - { - geom::TriangleMesh2 mesh; - size_t i = 1; - for (double t = 0.0; t < duration; t += seconds) + else if (minutesTick >= distanceMin) { - const math::Box2i box( - g.min.x + - t / duration * w, - p.size.scrollPos.y + - g.min.y, - p.size.border, - p.size.margin + - p.size.fontMetrics.lineHeight + - p.size.margin + - p.size.border * 4); - if (box.intersects(drawRect)) - { - mesh.v.push_back(math::Vector2f(box.min.x, box.min.y)); - mesh.v.push_back(math::Vector2f(box.max.x + 1, box.min.y)); - mesh.v.push_back(math::Vector2f(box.max.x + 1, box.max.y + 1)); - mesh.v.push_back(math::Vector2f(box.min.x, box.max.y + 1)); - mesh.triangles.push_back({ i + 0, i + 1, i + 2 }); - mesh.triangles.push_back({ i + 2, i + 3, i + 0 }); - i += 4; - } + seconds = 60.0; + tick = minutesTick; } - if (!mesh.v.empty()) + else if (hoursTick >= distanceMin) { - event.render->drawMesh( - mesh, - math::Vector2i(), - event.style->getColorRole(ui::ColorRole::Button)); + seconds = 3600.0; + tick = hoursTick; } - - const otime::RationalTime& currentTime = p.player->observeCurrentTime()->get(); - for (double t = 0.0; t < duration; t += seconds) + if (seconds > 0.0 && tick > 0) { - const otime::RationalTime time = _timeRange.start_time() + - otime::RationalTime(t, 1.0).rescaled_to(_timeRange.duration().rate()); - const math::Box2i box( - g.min.x + - t / duration * w + - p.size.border + - p.size.margin, - p.size.scrollPos.y + - g.min.y + - p.size.margin, - labelMaxSize.w, - p.size.fontMetrics.lineHeight); - if (time != currentTime && box.intersects(drawRect)) + geom::TriangleMesh2 mesh; + size_t i = 1; + for (double t = 0.0; t < duration; t += seconds) { - const std::string label = _data->timeUnitsModel->getLabel(time); - event.render->drawText( - event.fontSystem->getGlyphs(label, p.size.fontInfo), - math::Vector2i( - box.min.x, - box.min.y + - p.size.fontMetrics.ascender), - event.style->getColorRole(ui::ColorRole::TextDisabled)); + const math::Box2i box( + g.min.x + + t / duration * w, + p.size.scrollPos.y + + g.min.y, + p.size.border, + p.size.margin + + p.size.fontMetrics.lineHeight + + p.size.margin + + p.size.border * 4); + if (box.intersects(drawRect)) + { + mesh.v.push_back(math::Vector2f(box.min.x, box.min.y)); + mesh.v.push_back(math::Vector2f(box.max.x + 1, box.min.y)); + mesh.v.push_back(math::Vector2f(box.max.x + 1, box.max.y + 1)); + mesh.v.push_back(math::Vector2f(box.min.x, box.max.y + 1)); + mesh.triangles.push_back({ i + 0, i + 1, i + 2 }); + mesh.triangles.push_back({ i + 2, i + 3, i + 0 }); + i += 4; + } + } + if (!mesh.v.empty()) + { + event.render->drawMesh( + mesh, + math::Vector2i(), + event.style->getColorRole(ui::ColorRole::Button)); + } + + const otime::RationalTime& currentTime = p.player->observeCurrentTime()->get(); + for (double t = 0.0; t < duration; t += seconds) + { + const otime::RationalTime time = _timeRange.start_time() + + otime::RationalTime(t, 1.0).rescaled_to(_timeRange.duration().rate()); + const math::Box2i box( + g.min.x + + t / duration * w + + p.size.border + + p.size.margin, + p.size.scrollPos.y + + g.min.y + + p.size.margin, + labelMaxSize.w, + p.size.fontMetrics.lineHeight); + if (time != currentTime && box.intersects(drawRect)) + { + const std::string label = _data->timeUnitsModel->getLabel(time); + event.render->drawText( + event.fontSystem->getGlyphs(label, p.size.fontInfo), + math::Vector2i( + box.min.x, + box.min.y + + p.size.fontMetrics.ascender), + event.style->getColorRole(ui::ColorRole::TextDisabled)); + } } } } diff --git a/lib/tlTimelineUI/TimelineViewport.cpp b/lib/tlTimelineUI/TimelineViewport.cpp index ebfd54317..c4636aa47 100644 --- a/lib/tlTimelineUI/TimelineViewport.cpp +++ b/lib/tlTimelineUI/TimelineViewport.cpp @@ -27,8 +27,7 @@ namespace tl timeline::CompareOptions compareOptions; std::function compareCallback; timeline::BackgroundOptions backgroundOptions; - std::vector > players; - std::vector timelineSizes; + std::shared_ptr player; std::vector videoData; math::Vector2i viewPos; double viewZoom = 1.0; @@ -62,7 +61,7 @@ namespace tl MouseData mouse; std::shared_ptr > playbackObserver; - std::vector > > videoDataObservers; + std::shared_ptr > videoDataObserver; }; void TimelineViewport::_init( @@ -162,59 +161,44 @@ namespace tl _updates |= ui::Update::Draw; } - void TimelineViewport::setPlayers(const std::vector >& value) + void TimelineViewport::setPlayer(const std::shared_ptr& value) { TLRENDER_P(); p.playbackObserver.reset(); - p.videoDataObservers.clear(); + p.videoDataObserver.reset(); - p.players = value; + p.player = value; - p.timelineSizes.clear(); - for (const auto& player : p.players) + if (p.player) { - if (player) - { - const auto& ioInfo = player->getIOInfo(); - if (!ioInfo.video.empty()) + p.playbackObserver = observer::ValueObserver::create( + p.player->observePlayback(), + [this](timeline::Playback value) { - p.timelineSizes.push_back(ioInfo.video[0].size); - } - } + switch (value) + { + case timeline::Playback::Forward: + case timeline::Playback::Reverse: + _p->droppedFrames.init = true; + break; + default: break; + } + }); + p.videoDataObserver = observer::ListObserver::create( + p.player->observeCurrentVideo(), + [this](const std::vector& value) + { + _p->videoData = value; + _p->doRender = true; + _updates |= ui::Update::Draw; + }); } - - p.videoData.clear(); - p.doRender = true; - _updates |= ui::Update::Draw; - for (size_t i = 0; i < p.players.size(); ++i) + else if (!p.videoData.empty()) { - if (p.players[i]) - { - if (0 == i) - { - p.playbackObserver = observer::ValueObserver::create( - p.players[i]->observePlayback(), - [this](timeline::Playback value) - { - switch (value) - { - case timeline::Playback::Forward: - case timeline::Playback::Reverse: - _p->droppedFrames.init = true; - break; - default: break; - } - }); - } - p.videoDataObservers.push_back( - observer::ValueObserver::create( - p.players[i]->observeCurrentVideo(), - [this, i](const timeline::VideoData& value) - { - _videoDataUpdate(value, i); - })); - } + p.videoData.clear(); + p.doRender = true; + _updates |= ui::Update::Draw; } } @@ -285,19 +269,19 @@ namespace tl void TimelineViewport::viewZoom1To1() { TLRENDER_P(); - setViewZoom(1.F, _viewportCenter()); + setViewZoom(1.F, _getViewportCenter()); } void TimelineViewport::viewZoomIn() { TLRENDER_P(); - setViewZoom(p.viewZoom * 2.0, _viewportCenter()); + setViewZoom(p.viewZoom * 2.0, _getViewportCenter()); } void TimelineViewport::viewZoomOut() { TLRENDER_P(); - setViewZoom(p.viewZoom / 2.0, _viewportCenter()); + setViewZoom(p.viewZoom / 2.0, _getViewportCenter()); } void TimelineViewport::setViewPosAndZoomCallback( @@ -374,7 +358,7 @@ namespace tl event.render->clearViewport(image::Color4f(0.F, 0.F, 0.F)); event.render->setOCIOOptions(p.ocioOptions); event.render->setLUTOptions(p.lutOptions); - if (!p.videoData.empty()) + if (p.player && !p.videoData.empty()) { math::Matrix4x4f vm; vm = vm * math::translate(math::Vector3f(p.viewPos.x, p.viewPos.y, 0.F)); @@ -389,7 +373,7 @@ namespace tl event.render->setTransform(pm * vm); event.render->drawVideo( p.videoData, - timeline::getBoxes(p.compareOptions.mode, p.timelineSizes), + timeline::getBoxes(p.compareOptions.mode, p.player->getSizes()), p.imageOptions, p.displayOptions, p.compareOptions, @@ -426,9 +410,9 @@ namespace tl break; case Private::MouseMode::Wipe: { - if (!p.players.empty() && p.players[0]) + if (p.player) { - const auto& ioInfo = p.players[0]->getIOInfo(); + const io::Info& ioInfo = p.player->getIOInfo(); if (!ioInfo.video.empty()) { const auto& imageInfo = ioInfo.video[0]; @@ -491,10 +475,10 @@ namespace tl else if (event.modifiers & static_cast(ui::KeyModifier::Control)) { event.accept = true; - if (!p.players.empty() && p.players[0]) + if (p.player) { - const otime::RationalTime t = p.players[0]->getCurrentTime(); - p.players[0]->seek(t + otime::RationalTime(event.value.y, t.rate())); + const otime::RationalTime t = p.player->getCurrentTime(); + p.player->seek(t + otime::RationalTime(event.value.y, t.rate())); } } } @@ -539,13 +523,18 @@ namespace tl p.mouse.mode = Private::MouseMode::None; } - math::Size2i TimelineViewport::_renderSize() const + math::Size2i TimelineViewport::_getRenderSize() const { TLRENDER_P(); - return timeline::getRenderSize(p.compareOptions.mode, p.timelineSizes); + math::Size2i out; + if (p.player) + { + out = timeline::getRenderSize(p.compareOptions.mode, p.player->getSizes()); + } + return out; } - math::Vector2i TimelineViewport::_viewportCenter() const + math::Vector2i TimelineViewport::_getViewportCenter() const { return math::Vector2i(_geometry.w() / 2, _geometry.h() / 2); } @@ -554,7 +543,7 @@ namespace tl { TLRENDER_P(); const math::Size2i viewportSize(_geometry.w(), _geometry.h()); - const math::Size2i renderSize = _renderSize(); + const math::Size2i renderSize = _getRenderSize(); double zoom = viewportSize.w / static_cast(renderSize.w); if (zoom * renderSize.h > viewportSize.h) { @@ -601,24 +590,5 @@ namespace tl } p.droppedFrames.frame = value.value(); } - - void TimelineViewport::_videoDataUpdate(const timeline::VideoData& value, size_t index) - { - TLRENDER_P(); - if (p.videoData.size() != p.players.size()) - { - p.videoData = std::vector(p.players.size()); - } - for (size_t i = 0; i < p.videoData.size(); ++i) - { - if (!p.players[i]->getTimeRange().contains(p.videoData[i].time)) - { - p.videoData[i] = timeline::VideoData(); - } - } - p.videoData[index] = value; - p.doRender = true; - _updates |= ui::Update::Draw; - } } } diff --git a/lib/tlTimelineUI/TimelineViewport.h b/lib/tlTimelineUI/TimelineViewport.h index 9aa69b904..6a2915833 100644 --- a/lib/tlTimelineUI/TimelineViewport.h +++ b/lib/tlTimelineUI/TimelineViewport.h @@ -54,8 +54,8 @@ namespace tl //! Set the background options. void setBackgroundOptions(const timeline::BackgroundOptions&); - //! Set the timeline players. - void setPlayers(const std::vector >&); + //! Set the timeline player. + void setPlayer(const std::shared_ptr&); //! Get the view position. const math::Vector2i& getViewPos() const; @@ -111,14 +111,12 @@ namespace tl void _releaseMouse() override; private: - math::Size2i _renderSize() const; - math::Vector2i _viewportCenter() const; + math::Size2i _getRenderSize() const; + math::Vector2i _getViewportCenter() const; void _frameView(); void _droppedFramesUpdate(const otime::RationalTime&); - void _videoDataUpdate(const timeline::VideoData&, size_t); - TLRENDER_PRIVATE(); }; } diff --git a/lib/tlTimelineUI/VideoClipItem.cpp b/lib/tlTimelineUI/VideoClipItem.cpp index 5f5c61858..f0bd19f1f 100644 --- a/lib/tlTimelineUI/VideoClipItem.cpp +++ b/lib/tlTimelineUI/VideoClipItem.cpp @@ -9,6 +9,8 @@ #include #include +#include + #include #include diff --git a/lib/tlUI/ThumbnailSystem.cpp b/lib/tlUI/ThumbnailSystem.cpp index 0d59d09ce..32dc616c7 100644 --- a/lib/tlUI/ThumbnailSystem.cpp +++ b/lib/tlUI/ThumbnailSystem.cpp @@ -24,9 +24,9 @@ namespace tl { namespace { - const size_t infoRequestsMax = 3; - const size_t thumbnailRequestsMax = 3; - const size_t waveformRequestsMax = 3; + const size_t infoRequestsMax = 10; + const size_t thumbnailRequestsMax = 10; + const size_t waveformRequestsMax = 10; } struct ThumbnailCache::Private @@ -490,50 +490,53 @@ namespace tl return out; } - void ThumbnailGenerator::cancelRequests(std::vector ids) + void ThumbnailGenerator::cancelRequests(const std::vector& ids) { TLRENDER_P(); std::unique_lock lock(p.mutex.mutex); - auto infoRequest = p.mutex.infoRequests.begin(); - while (infoRequest != p.mutex.infoRequests.end()) { - const auto id = std::find(ids.begin(), ids.end(), (*infoRequest)->id); - if (id != ids.end()) + auto i = p.mutex.infoRequests.begin(); + while (i != p.mutex.infoRequests.end()) { - infoRequest = p.mutex.infoRequests.erase(infoRequest); - ids.erase(id); - } - else - { - ++infoRequest; + const auto j = std::find(ids.begin(), ids.end(), (*i)->id); + if (j != ids.end()) + { + i = p.mutex.infoRequests.erase(i); + } + else + { + ++i; + } } } - auto thumbnailRequest = p.mutex.thumbnailRequests.begin(); - while (thumbnailRequest != p.mutex.thumbnailRequests.end()) { - const auto id = std::find(ids.begin(), ids.end(), (*thumbnailRequest)->id); - if (id != ids.end()) - { - thumbnailRequest = p.mutex.thumbnailRequests.erase(thumbnailRequest); - ids.erase(id); - } - else + auto i = p.mutex.thumbnailRequests.begin(); + while (i != p.mutex.thumbnailRequests.end()) { - ++thumbnailRequest; + const auto j = std::find(ids.begin(), ids.end(), (*i)->id); + if (j != ids.end()) + { + i = p.mutex.thumbnailRequests.erase(i); + } + else + { + ++i; + } } } - auto waveformRequest = p.mutex.waveformRequests.begin(); - while (waveformRequest != p.mutex.waveformRequests.end()) { - const auto id = std::find(ids.begin(), ids.end(), (*waveformRequest)->id); - if (id != ids.end()) - { - waveformRequest = p.mutex.waveformRequests.erase(waveformRequest); - ids.erase(id); - } - else + auto i = p.mutex.waveformRequests.begin(); + while (i != p.mutex.waveformRequests.end()) { - ++waveformRequest; + const auto j = std::find(ids.begin(), ids.end(), (*i)->id); + if (j != ids.end()) + { + i = p.mutex.waveformRequests.erase(i); + } + else + { + ++i; + } } } } diff --git a/lib/tlUI/ThumbnailSystem.h b/lib/tlUI/ThumbnailSystem.h index ecb263e15..17ddc220c 100644 --- a/lib/tlUI/ThumbnailSystem.h +++ b/lib/tlUI/ThumbnailSystem.h @@ -27,14 +27,14 @@ namespace tl //! Information request. struct InfoRequest { - uint64_t id; + uint64_t id = 0; std::future future; }; //! Video thumbnail request. struct ThumbnailRequest { - uint64_t id; + uint64_t id = 0; int height = 0; otime::RationalTime time = time::invalidTime; std::future > future; @@ -43,7 +43,7 @@ namespace tl //! Audio waveform request. struct WaveformRequest { - uint64_t id; + uint64_t id = 0; math::Size2i size; otime::TimeRange timeRange = time::invalidTimeRange; std::future > future; @@ -196,7 +196,7 @@ namespace tl const io::Options& = io::Options()); //! Cancel pending requests. - void cancelRequests(std::vector); + void cancelRequests(const std::vector&); private: void _run(); diff --git a/tests/tlTimelineTest/CompareOptionsTest.cpp b/tests/tlTimelineTest/CompareOptionsTest.cpp index 0d8735043..bae5d4078 100644 --- a/tests/tlTimelineTest/CompareOptionsTest.cpp +++ b/tests/tlTimelineTest/CompareOptionsTest.cpp @@ -28,6 +28,7 @@ namespace tl { { _enum("CompareMode", getCompareModeEnums); + _enum("CompareTimeMode", getCompareTimeModeEnums); } { CompareOptions options; @@ -84,6 +85,30 @@ namespace tl renderSize = getRenderSize(CompareMode::Tile, sizes); TLRENDER_ASSERT(math::Size2i(1920 * 2, 1080 * 2) == renderSize); } + { + const auto time = getCompareTime( + otime::RationalTime(0.0, 24.0), + otime::TimeRange( + otime::RationalTime(0.0, 24.0), + otime::RationalTime(24.0, 24.0)), + otime::TimeRange( + otime::RationalTime(0.0, 24.0), + otime::RationalTime(24.0, 24.0)), + CompareTimeMode::Absolute); + TLRENDER_ASSERT(time == otime::RationalTime(0.0, 24.0)); + } + { + const auto time = getCompareTime( + otime::RationalTime(0.0, 24.0), + otime::TimeRange( + otime::RationalTime(0.0, 24.0), + otime::RationalTime(24.0, 24.0)), + otime::TimeRange( + otime::RationalTime(24.0, 24.0), + otime::RationalTime(24.0, 24.0)), + CompareTimeMode::Relative); + TLRENDER_ASSERT(time == otime::RationalTime(24.0, 24.0)); + } } } } diff --git a/tests/tlTimelineTest/PlayerOptionsTest.cpp b/tests/tlTimelineTest/PlayerOptionsTest.cpp index 4342a0c95..7dd4b5b78 100644 --- a/tests/tlTimelineTest/PlayerOptionsTest.cpp +++ b/tests/tlTimelineTest/PlayerOptionsTest.cpp @@ -28,7 +28,6 @@ namespace tl { { _enum("TimerMode", getTimerModeEnums); - _enum("ExternalTimeMode", getExternalTimeModeEnums); } { PlayerCacheOptions v; @@ -42,30 +41,6 @@ namespace tl TLRENDER_ASSERT(v == v); TLRENDER_ASSERT(v != PlayerOptions()); } - { - const auto time = getExternalTime( - otime::RationalTime(0.0, 24.0), - otime::TimeRange( - otime::RationalTime(0.0, 24.0), - otime::RationalTime(24.0, 24.0)), - otime::TimeRange( - otime::RationalTime(0.0, 24.0), - otime::RationalTime(24.0, 24.0)), - ExternalTimeMode::Absolute); - TLRENDER_ASSERT(time == otime::RationalTime(0.0, 24.0)); - } - { - const auto time = getExternalTime( - otime::RationalTime(0.0, 24.0), - otime::TimeRange( - otime::RationalTime(0.0, 24.0), - otime::RationalTime(24.0, 24.0)), - otime::TimeRange( - otime::RationalTime(24.0, 24.0), - otime::RationalTime(24.0, 24.0)), - ExternalTimeMode::Relative); - TLRENDER_ASSERT(time == otime::RationalTime(24.0, 24.0)); - } } } } diff --git a/tests/tlTimelineTest/PlayerTest.cpp b/tests/tlTimelineTest/PlayerTest.cpp index c5b0b8dae..312971cba 100644 --- a/tests/tlTimelineTest/PlayerTest.cpp +++ b/tests/tlTimelineTest/PlayerTest.cpp @@ -39,7 +39,6 @@ namespace tl _enums(); _loop(); _player(); - _externalTime(); } void PlayerTest::_enums() @@ -308,6 +307,32 @@ namespace tl TLRENDER_ASSERT(ioOptions2 == ioOptions); player->setIOOptions({}); + // Test the video layers. + int videoLayer = 0; + std::vector compareVideoLayers; + auto videoLayerObserver = observer::ValueObserver::create( + player->observeVideoLayer(), + [&videoLayer](int value) + { + videoLayer = value; + }); + auto compareVideoLayersObserver = observer::ListObserver::create( + player->observeCompareVideoLayers(), + [&compareVideoLayers](const std::vector& value) + { + compareVideoLayers = value; + }); + int videoLayer2 = 1; + player->setVideoLayer(videoLayer2); + TLRENDER_ASSERT(videoLayer2 == player->getVideoLayer()); + TLRENDER_ASSERT(videoLayer2 == videoLayer); + std::vector compareVideoLayers2 = { 2, 3 }; + player->setCompareVideoLayers(compareVideoLayers2); + TLRENDER_ASSERT(compareVideoLayers2 == player->getCompareVideoLayers()); + TLRENDER_ASSERT(compareVideoLayers2 == compareVideoLayers); + player->setVideoLayer(0); + player->setCompareVideoLayers({}); + // Test audio. float volume = 1.F; auto volumeObserver = observer::ValueObserver::create( @@ -356,12 +381,16 @@ namespace tl player->setCacheOptions(cacheOptions); TLRENDER_ASSERT(cacheOptions == player->getCacheOptions()); - auto currentVideoObserver = observer::ValueObserver::create( + auto currentVideoObserver = observer::ListObserver::create( player->observeCurrentVideo(), - [this](const timeline::VideoData& value) + [this](const std::vector& value) { std::stringstream ss; - ss << "Video time: " << value.time; + ss << "Video time: "; + if (!value.empty()) + { + ss << value.front().time; + } _print(ss.str()); }); auto currentAudioObserver = observer::ListObserver::create( @@ -438,28 +467,5 @@ namespace tl player->clearCache(); } } - - void PlayerTest::_externalTime() - { - const file::Path path(TLRENDER_SAMPLE_DATA, "MultipleClips.otio"); - auto timeline = Timeline::create(path, _context); - auto player = Player::create(timeline, _context); - const otime::TimeRange& timeRange = player->getTimeRange(); - - const file::Path path2(TLRENDER_SAMPLE_DATA, "SingleClip.otio"); - auto timeline2 = Timeline::create(path2, _context); - auto player2 = Player::create(timeline2, _context); - player2->setExternalTime(player); - player2->setExternalTime(player); - - player->setPlayback(Playback::Forward); - for (size_t i = 0; i < timeRange.duration().rate(); ++i) - { - player->tick(); - time::sleep(std::chrono::milliseconds(1)); - } - - player2->setExternalTime(nullptr); - } } } diff --git a/tests/tlTimelineTest/PlayerTest.h b/tests/tlTimelineTest/PlayerTest.h index 0ce9f40a2..9ce9e92d2 100644 --- a/tests/tlTimelineTest/PlayerTest.h +++ b/tests/tlTimelineTest/PlayerTest.h @@ -27,7 +27,6 @@ namespace tl void _loop(); void _player(); void _player(const std::shared_ptr&); - void _externalTime(); }; } } diff --git a/tests/tlTimelineTest/TimelineTest.cpp b/tests/tlTimelineTest/TimelineTest.cpp index 9d5788dac..8c7ff6754 100644 --- a/tests/tlTimelineTest/TimelineTest.cpp +++ b/tests/tlTimelineTest/TimelineTest.cpp @@ -134,27 +134,27 @@ namespace tl // Get video from the timeline. const otime::TimeRange& timeRange = timeline->getTimeRange(); std::vector videoData; - std::vector > videoFutures; + std::vector videoRequests; for (size_t i = 0; i < static_cast(timeRange.duration().value()); ++i) { - videoFutures.push_back(timeline->getVideo(otime::RationalTime(i, 24.0))); + videoRequests.push_back(timeline->getVideo(otime::RationalTime(i, 24.0))); } io::Options ioOptions; ioOptions["Layer"] = "1"; for (size_t i = 0; i < static_cast(timeRange.duration().value()); ++i) { - videoFutures.push_back(timeline->getVideo(otime::RationalTime(i, 24.0), ioOptions)); + videoRequests.push_back(timeline->getVideo(otime::RationalTime(i, 24.0), ioOptions)); } while (videoData.size() < static_cast(timeRange.duration().value()) * 2) { - auto i = videoFutures.begin(); - while (i != videoFutures.end()) + auto i = videoRequests.begin(); + while (i != videoRequests.end()) { - if (i->valid() && - i->wait_for(std::chrono::seconds(0)) == std::future_status::ready) + if (i->future.valid() && + i->future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - videoData.push_back(i->get()); - i = videoFutures.erase(i); + videoData.push_back(i->future.get()); + i = videoRequests.erase(i); } else { @@ -162,25 +162,25 @@ namespace tl } } } - TLRENDER_ASSERT(videoFutures.empty()); + TLRENDER_ASSERT(videoRequests.empty()); // Get audio from the timeline. std::vector audioData; - std::vector > audioFutures; + std::vector audioRequests; for (size_t i = 0; i < static_cast(timeRange.duration().rescaled_to(1.0).value()); ++i) { - audioFutures.push_back(timeline->getAudio(i)); + audioRequests.push_back(timeline->getAudio(i)); } while (audioData.size() < static_cast(timeRange.duration().rescaled_to(1.0).value())) { - auto i = audioFutures.begin(); - while (i != audioFutures.end()) + auto i = audioRequests.begin(); + while (i != audioRequests.end()) { - if (i->valid() && - i->wait_for(std::chrono::seconds(0)) == std::future_status::ready) + if (i->future.valid() && + i->future.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { - audioData.push_back(i->get()); - i = audioFutures.erase(i); + audioData.push_back(i->future.get()); + i = audioRequests.erase(i); } else { @@ -188,26 +188,35 @@ namespace tl } } } - TLRENDER_ASSERT(audioFutures.empty()); + TLRENDER_ASSERT(audioRequests.empty()); // Cancel requests. videoData.clear(); - videoFutures.clear(); + videoRequests.clear(); audioData.clear(); - audioFutures.clear(); + audioRequests.clear(); for (size_t i = 0; i < static_cast(timeRange.duration().value()); ++i) { - videoFutures.push_back(timeline->getVideo(otime::RationalTime(i, 24.0))); + videoRequests.push_back(timeline->getVideo(otime::RationalTime(i, 24.0))); } for (size_t i = 0; i < static_cast(timeRange.duration().value()); ++i) { - videoFutures.push_back(timeline->getVideo(otime::RationalTime(i, 24.0), ioOptions)); + videoRequests.push_back(timeline->getVideo(otime::RationalTime(i, 24.0), ioOptions)); } for (size_t i = 0; i < static_cast(timeRange.duration().rescaled_to(1.0).value()); ++i) { - audioFutures.push_back(timeline->getAudio(i)); + audioRequests.push_back(timeline->getAudio(i)); } - timeline->cancelRequests(); + std::vector ids; + for (const auto& i : videoRequests) + { + ids.push_back(i.id); + } + for (const auto& i : audioRequests) + { + ids.push_back(i.id); + } + timeline->cancelRequests(ids); } void TimelineTest::_separateAudio()